搭建一個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 63792 #判斷主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; }
到這裡可以測試一下,你快取的資料,是否可以直接使用客戶端獲取。如有不對的地方歡迎大家扔雞蛋,以免誤人子弟,謝謝!