1. 程式人生 > 其它 >搭建一個Redis叢集

搭建一個Redis叢集

Redis主從以及哨兵模式的叢集搭建

宣告:本文參考自:https://www.cnblogs.com/cunkouzh/p/9242292.html

        :https://blog.csdn.net/liguangyan_neu/article/details/78027105

        :https://blog.csdn.net/u014513883/article/details/77036890?locationNum=9&fps=1

本文主要是通過在windows環境上搭建一個本機的叢集環境

1.先配置伺服器(本地)哨兵模式,直接從redis官網下載安裝或者解壓版,安裝後的目錄結構

然後配置哨兵模式

測試採用3個哨兵,1個主redis,2個從redis。 複製6份redis.windows.conf檔案並重命名如下(開發者可根據自己的開發習慣進行重新命名)

配置master.6379.conf

port:6379
#設定連線密碼
requirepass:grs #密碼可以不設定,如果設定了密碼,兩個從機也要做相同的配置
#連線密碼
masterauth:grs #密碼可以不設定,如果設定了密碼,兩個從機也要做相同的配置

slave.6380.conf配置

port:6380
dbfilename dump6380.rdb
requirepass:grs #如果主機進行了密碼配置,則從機也要做相同配置
#連線密碼
masterauth:grs #如果主機進行了密碼配置,則從機也要做相同配置
#配置master 
slaveof
127.0.0.1 6379

slave.6381.conf配置

port 6381
requirepass:grs #如果主機進行了密碼配置,則從機也要做相同配置
#連線密碼
masterauth:grs #如果主機進行了密碼配置,則從機也要做相同配置
slaveof 127.0.0.1 6379
dbfilename dump6381.rdb

配置哨兵sentinel.63791.conf(其他兩個哨兵配置檔案一致,只修改埠號碼即可)

port 63791
#主master,2個sentinel選舉成功後才有效,這裡的master-1是名稱,在整合的時候需要一致,這裡可以隨便更改
sentinel monitor master-1 127.0.0.1 6379
2 #判斷主master的掛機時間(毫秒),超時未返回正確資訊後標記為sdown狀態 sentinel down-after-milliseconds master-1 5000 #若sentinel在該配置值內未能完成failover操作(即故障時master/slave自動切換),則認為本次failover失敗。 sentinel failover-timeout master-1 18000 #選項指定了在執行故障轉移時, 最多可以有多少個從伺服器同時對新的主伺服器進行同步,這個數字越小,完成故障轉移所需的時間就越長 sentinel config-epoch master-1 2

需要注意的地方

1、若通過redis-cli -h 127.0.0.1 -p 6379連線,無需改變配置檔案,配置檔案預設配置為bind 127.0.0.1(只允許127.0.0.1連線訪問)若通過redis-cli -h 192.168.180.78 -p 6379連線,需改變配置檔案,配置資訊為bind 127.0.0.1 192.168.180.78(只允許127.0.0.1和192.168.180.78訪問)或者將bind 127.0.0.1註釋掉(允許所有遠端訪問)

2、masterauth為所要連線的master伺服器的requirepass,如果一個redis叢集中有一個master伺服器,兩個slave伺服器,當master伺服器掛掉時,sentinel哨兵會隨機選擇一個slave伺服器充當master伺服器,鑑於這種機制,解決辦法是將所有的主從伺服器的requirepass和masterauth都設定為一樣。

3、sentinel monitor master-1 127.0.0.1 6379 2 行尾最後的一個2代表什麼意思呢?我們知道,網路是不可靠的,有時候一個sentinel會因為網路堵塞而誤以為一個master redis已經死掉了,當sentinel叢集式,解決這個問題的方法就變得很簡單,只需要多個sentinel互相溝通來確認某個master是否真的死了,這個2代表,當叢集中有2個sentinel認為master死了時,才能真正認為該master已經不可用了。(sentinel叢集中各個sentinel也有互相通訊,通過gossip協議)。

依次啟動redis

redis-server master.6379.conf(如果設定了密碼,需要使用auth grs(你的密碼) 在連線後進行驗證)

redis-server slave.6380.conf

redis-server slave.6381.conf

redis-server sentinel.63791.conf --sentinel(linux:redis-sentinel sentinel.63791.conf)其他兩個哨兵也這樣啟動

使用客戶端檢視一下master狀態

檢視一下哨兵狀態

現在就可以在master插入資料,所有的redis服務都可以獲取到,slave只能讀

整合spring,匯入依賴

<dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.8.0</version>
    </dependency>
    <!-- spring-redis -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.6.4.RELEASE</version>
    </dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

redis.properties

#redis中心  
redis.host=127.0.0.1
#redis.host=10.75.202.11  
redis.port=6379
redis.password=  
#redis.password=123456  
redis.maxTotal=200
redis.maxIdle=100
redis.minIdle=8
redis.maxWaitMillis=100000
redis.maxActive=300
redis.testOnBorrow=true
redis.testOnReturn=true
#Idle時進行連線掃描
redis.testWhileIdle=true
#表示idle object evitor兩次掃描之間要sleep的毫秒數
redis.timeBetweenEvictionRunsMillis=30000
#表示idle object evitor每次掃描的最多的物件數
redis.numTestsPerEvictionRun=10
#表示一個物件至少停留在idle狀態的最短時間,然後才能被idle object evitor掃描並驅逐;這一項只有在timeBetweenEvictionRunsMillis大於0時才有意義
redis.minEvictableIdleTimeMillis=60000
 
redis.timeout=100000

配置sentinel方式一

<!-- 這個這個bean是繼承RedisSentinelConfiguration,原因是我直接使用時,在注入哨兵的時候spring獲取不到注入屬性的方法,老是報引數異常 -->
    <bean id="redisSentinelConfiguration" class="com.uec.village.redis.RedisConfiguration"><!-- 這裡是配置哨兵模式 -->
        <constructor-arg name="sentinels">
            <set>
                <bean  class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host}"/>
                    <constructor-arg name="port" value="63791"/>
                </bean>
                <bean  class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host}"/>
                    <constructor-arg name="port" value="63792"/>
                </bean>
                <bean  class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host}"/>
                    <constructor-arg name="port" value="63793"/>
                </bean>
            </set>
        </constructor-arg>
        <property name="master">
            <bean class="org.springframework.data.redis.connection.RedisNode">
                <!--必須指定主節點名稱,與服務中的master名稱一致-->
                <property name="name" value="master-1"/>
<!--                 <constructor-arg name="host" value="${redis.host}"/> -->
<!--                 <constructor-arg name="port" value="6379"/> -->
            </bean>
        </property>
    </bean><!-- 可以直接配置sentinel -->

<!-- 
<bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
    <property name="sentinels">
        <set>
            <bean name="sentinelNode1" class="org.springframework.data.redis.connection.RedisNode">
                <constructor-arg name="host" value="${redis.host}"/>
                <constructor-arg name="port" value="63791"/>
            </bean>
            <bean name="sentinelNode2" class="org.springframework.data.redis.connection.RedisNode">
                <constructor-arg name="host" value="${redis.host}"/>
                <constructor-arg name="port" value="63792"/>
            </bean>
            <bean name="sentinelNode3" class="org.springframework.data.redis.connection.RedisNode">
                <constructor-arg name="host" value="${redis.host}"/>
                <constructor-arg name="port" value="63793"/>
            </bean>
        </set>
    </property>
    <property name="master">
        <bean name="masterNode" class="org.springframework.data.redis.connection.RedisNode">
            <!--必須指定主節點名稱-->
            <property name="name" value="mymaster"/>
            <!-- <constructor-arg name="host" value="${redis.host}"/>
            <constructor-arg name="port" value="6379"/>
-->
        </bean>
    </property>
</bean>
-->

配置sentinel方式二

<bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
    <constructor-arg name="propertySource" ref="propertySource"/>
</bean>

<bean name="propertySource" class="org.springframework.core.io.support.ResourcePropertySource">
    <constructor-arg name="location" value="classpath:spring-redis-sentinel.properties" />
</bean>

spring-redis-sentinel.properties內容:

#哨兵監控主redis節點名稱,必選
spring.redis.sentinel.master=mymaster
#哨兵節點
spring.redis.sentinel.nodes=192.168.48.31:26379,192.168.48.32:26379,192.168.48.33:26379

xml配置

<!-- jedis 配置 -->  
  <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >  
         <property name="maxTotal" value="${redis.maxTotal}" />  
         <property name="maxIdle" value="${redis.maxIdle}" />  
         <property name="minIdle" value="${redis.minIdle}" />  
         <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />  
         <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
         <property name="testOnReturn" value="${redis.testOnReturn}" />  
         <property name="testWhileIdle" value="${redis.testWhileIdle}" />  
         <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />  
         <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />  
         <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />  
   </bean >  
  <!-- redis伺服器中心 -->  
   <bean id="connectionFactory"  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
            <constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration"/>
         <constructor-arg name="poolConfig" ref="poolConfig" />  
<!--          <property name="poolConfig" ref="poolConfig" />   如果不是哨兵模式,把這行放開,註釋掉上面兩行的構造方法注入-->
         <property name="port" value="${redis.port}" />  
         <property name="hostName" value="${redis.host}" />  
         <property name="password" value="${redis.password}" />  
         <property name="timeout" value="${redis.timeout}" ></property>  
   </bean >  
   <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >  
         <property name="connectionFactory" ref="connectionFactory" />  
         <property name="keySerializer" >  
             <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />  
         </property>  
         <property name="valueSerializer" >  
             <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />  
         </property>  
   </bean >  
     
    <!-- cache配置 -->  
   <bean id="redisUtil" class="com.uec.village.util.RedisUtil" >  
         <property name="redisTemplate" ref="redisTemplate" />  
   </bean >  
   <bean id="methodCacheInterceptor" class="com.uec.village.interceptor.MethodCacheInterceptor" >  
         <property name="redisUtil" ref="redisUtil" />  
   </bean >    <!-- 配置攔截需要快取的方法,根絕註解決定 -->
   <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" > 
       <property name="advice" > 
         <ref local="methodCacheInterceptor" /> 
       </property> 
       <property name="patterns" > 
         <set>  
          <!-- 確定正則表示式列表 -->
           <value>com.service.impl...*ServiceImpl.*</value > 
         </set> 
       </property> 
    </bean >

RedisConfiguration類

import static org.springframework.util.Assert.notNull;

import java.util.Set;

import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;

public class RedisConfiguration extends RedisSentinelConfiguration{
    
    public RedisConfiguration(){}
    public RedisConfiguration(Iterable<RedisNode> sentinels){
        notNull(sentinels, "Cannot set sentinels to 'null'.");

        Set<RedisNode> sentinels2 = getSentinels();
        if(!sentinels2.isEmpty()){
            sentinels2.clear();
        }

        for (RedisNode sentinel : sentinels) {
            addSentinel(sentinel);
        }
    }

}

MethodCacheInterceptor攔截器

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import com.uec.village.annotation.RedisCache;
import com.uec.village.util.RedisUtil;

/**
 * 使用者登入過濾器
 * @author snw
 *
 */
public class MethodCacheInterceptor implements MethodInterceptor {  
    private RedisUtil redisUtil;  
  
  
    /** 
     * 初始化讀取不需要加入快取的類名和方法名稱 
     */  
    public MethodCacheInterceptor() { 
        
    }  
  
    @Override  
    public Object invoke(MethodInvocation invocation) throws Throwable {
        
        Object value = null;  
        String targetName = invocation.getThis().getClass().getName();  
        Method method = invocation.getMethod();
        String methodName = method.getName();  
        RedisCache annotation = method.getAnnotation(RedisCache.class);
        //說明當前方法不需要快取的,
        if(annotation == null){
            return invocation.proceed();  
        }
        Object[] arguments = invocation.getArguments();  
        String key = getCacheKey(targetName, methodName, arguments);  
        System.out.println(key);  
        try {  
            // 判斷是否有快取  
            if (redisUtil.exists(key)) {  
                System.out.println("方法名稱為:"+methodName+",根據:"+key+",從快取中獲取");
                return redisUtil.get(key);  
            }  
            // 寫入快取  
            value = invocation.proceed();  
            if (value != null) {  
                final String tkey = key;  
                final Object tvalue = value;  
                if(annotation.isTime()){
                    redisUtil.set(tkey, tvalue);
                    
                }  
            }  
            return value;
        } catch (Exception e) {  
            e.printStackTrace();  
            if (value == null) {  
                return invocation.proceed();  
            }  
        }  
        return value;  
    }  
  
    /** 
     * 建立快取key 
     * 
     * @param targetName 
     * @param methodName 
     * @param arguments 
     */  
    private String getCacheKey(String targetName, String methodName,  
            Object[] arguments) {  
        StringBuffer sbu = new StringBuffer();  
        sbu.append(targetName).append("_").append(methodName);  
        if ((arguments != null) && (arguments.length != 0)) {  
            for (int i = 0; i < arguments.length; i++) {  
                sbu.append("_").append(arguments[i]);  
            }  
        }  
        return sbu.toString();  
    }  
  
    public void setRedisUtil(RedisUtil redisUtil) {  
        this.redisUtil = redisUtil;  
    }  
 }

redis工具類

import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

/**
 * redis工具
 * @author snw
 *
 */
public class RedisUtil { 

    private RedisTemplate<Serializable, Object> redisTemplate;  
  
    /** 
     * 批量刪除對應的value 
     *  
     * @param keys 
     */  
    public void remove(final String... keys) {  
        for (String key : keys) {  
            remove(key);  
        }  
    }  
  
    /** 
     * 批量刪除key 
     *  
     * @param pattern 
     */  
    public void removePattern(final String pattern) {  
        Set<Serializable> keys = redisTemplate.keys(pattern);  
        if (keys.size() > 0)  
            redisTemplate.delete(keys);  
    }  
  
    /** 
     * 刪除對應的value 
     *  
     * @param key 
     */  
    public void remove(final String key) {  
        if (exists(key)) {  
            redisTemplate.delete(key);  
        }  
    }  
  
    /** 
     * 判斷快取中是否有對應的value 
     *  
     * @param key 
     * @return 
     */  
    public boolean exists(final String key) {  
        return redisTemplate.hasKey(key);  
    }  
  
    /** 
     * 讀取快取 
     *  
     * @param key 
     * @return 
     */  
    public Object get(final String key) {  
        Object result = null;  
        ValueOperations<Serializable, Object> operations = redisTemplate  
                .opsForValue();  
        result = operations.get(key);  
        return result;  
    }  
  
    /** 
     * 寫入快取 
     *  
     * @param key 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, Object value) {  
        boolean result = false;  
        try {  
            ValueOperations<Serializable, Object> operations = redisTemplate  
                    .opsForValue();  
            operations.set(key, value);  
            result = true;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return result;  
    }  
  
    /** 
     * 寫入快取 
     *  
     * @param key 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, Object value, Long expireTime) {  
        boolean result = false;  
        try {  
            ValueOperations<Serializable, Object> operations = redisTemplate  
                    .opsForValue();  
            operations.set(key, value);  
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);  
            result = true;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return result;  
    }  
  
    public void setRedisTemplate(  
            RedisTemplate<Serializable, Object> redisTemplate) {  
        this.redisTemplate = redisTemplate;  
    }  
}

自定義註解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache { 
    boolean isCache() default false;
}

到這裡可以測試一下,你快取的資料,是否可以直接使用客戶端獲取。如有不對的地方歡迎大家扔雞蛋,以免誤人子弟,謝謝!