主從技術的切換方法是:當主節點當機後,需要人工将從節點切換為主節點,這樣的方式費事費力還會造成一段時間内服務不可用,這樣的方式顯然不是我們需要的,是以我們必須要有一個高可用的方案來抵抗節點的故障,當節點發生故障時可以自動進行主從切換,程式可以不用重新開機,仿佛什麼事情都沒有發生一樣。Redis官方提供了這樣一種方案-——Redis Sentinel(Sentinel的含義是哨兵)。
一、Redis Sentinel 架構

如上圖所示,我們可以将Redis Sentinel 叢集看成是一個zookeeper叢集,它是叢集高可用的心髒,一般有3~5個節點組成,這樣即使個别節點挂了,叢集還可以正常運轉。
Sentinel 負責監控主從節點的健康,當主節點挂掉時自動選擇一個最優的從節點切換為主節點。當用戶端來連接配接叢集時會首先連接配接Sentinel,通過Sentinel來查詢主節點的位址,然後再連接配接主節點進行資料互動。當主節點發生故障時,用戶端會重新向Sentinel要位址,Sentinel會将最新的主節點位址告訴用戶端,如此應用将無須重新啟動就可以自動完成節點切換。
二、Redis Sentinel 故障轉移
如上圖所示,如果主節點挂掉了,原先的主從複制也斷開了,一個從節點被提升為新的主節點,其他的從節點開始和新的主節點建立複制關系。用戶端通過新的主節點繼續進行互動,Sentinel會持續監聽已經挂掉的主節點,待它恢變成從節點,從新的主節點哪裡建立 複制關系。
故障轉移可以概括為下面的幾個步驟:
- 當多個sentinel發現master有問題會選舉出一個sentinel為上司。
- 被選為上司的sentinel會選擇出一個slave為新的master。
- 通知其餘的slave成為新的master的slave。
- 通知用戶端新的master的位址
- 等待原來的master複活成為新master的slave。
三、Redis Sentinel 配置與安裝
1. redis 主節點
2. redis從節點
3. sentinel配置
配置說明:
- port: 端口号
- dir:工作目錄
- logfile:日志
- sentinel monitor mymaster 127.0.0.1 7000 2 :mymaster代表的是master的名字,127.0.0.1 7000代表的是master的ip和端口,2代表的是至少有多少個sentinel發現master是有問題的才會進行下一步的故障轉移
- sentinel down-after-milliseconds mymaster 3000:sentinel對master的進行一個判斷,如果超過規定的時間master還沒有響應就代表master是有問題的,這裡是30000毫秒。
- sentinel parallel-syncs mymaster 1:老的slave對新的master複制同時是串行的還是并發的,這裡的1 代表每次隻能複制一個。
- sentinel failover-timeout mymastere 180000:故障轉移的時間
四、使用用戶端連接配接redis sentinel
1. 用戶端實作基本原理
a. 用戶端周遊sentinel節點集合擷取一個可用的sentinel節點
b. 對可用sentinel擷取master節點的名字、位址和端口
c. 擷取到master節點後使用role/role application驗證是否是真正的master
d. 當master發生變化時通知用戶端
2.jedis
public class TestSentinel {
@SuppressWarnings("resource")
@Test
public void testSentinel() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10);
jedisPoolConfig.setMaxIdle(5);
jedisPoolConfig.setMinIdle(5);
// 哨兵資訊
Set<String> sentinels = new HashSet<>(Arrays.asList("192.168.11.128:26379",
"192.168.11.129:26379","192.168.11.130:26379"));
// 建立連接配接池
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels,jedisPoolConfig,"123456");
// 擷取用戶端
Jedis jedis = pool.getResource();
// 執行兩個指令
jedis.set("mykey", "myvalue");
String value = jedis.get("mykey");
System.out.println(value);
}
}
3. 通過spring進行配置
<bean id = "poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空閑數 -->
<property name="maxIdle" value="50"></property>
<!-- 最大連接配接數 -->
<property name="maxTotal" value="100"></property>
<!-- 最大等待時間 -->
<property name="maxWaitMillis" value="20000"></property>
</bean>
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
<constructor-arg name="sentinelConfig" ref="sentinelConfig"></constructor-arg>
<property name="password" value="123456"></property>
</bean>
<!-- JDK序列化器 -->
<bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
<!-- String序列化器 -->
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
<property name="keySerializer" ref="stringRedisSerializer"></property>
<property name="defaultSerializer" ref="stringRedisSerializer"></property>
<property name="valueSerializer" ref="jdkSerializationRedisSerializer"></property>
</bean>
<!-- 哨兵配置 -->
<bean id="sentinelConfig" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<!-- 服務名稱 -->
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster"></property>
</bean>
</property>
<!-- 哨兵服務IP和端口 -->
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.11.128"></constructor-arg>
<constructor-arg name="port" value="26379"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.11.129"></constructor-arg>
<constructor-arg name="port" value="26379"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.11.130"></constructor-arg>
<constructor-arg name="port" value="26379"></constructor-arg>
</bean>
</set>
</property>
</bean>
五、三個定時任務
為了保證可以為redis進行故障轉移,redis sentinel内部有三個定時任務
1. 每10秒每個sentinel對master和slave執行info
這個定時任務的主要目的如下:
- 發現slave節點:在配置中并沒有配置slave節點的資訊,實際上是不需要的配置的 ,sentinel會對master執行info操作,會在info replication 中發現slave資訊進而發現slave節點。
- 确認主從關系:對每個執行info就可以确認他們之前的關系和關系的變化。
2.每2秒每個sentinel通過master節點的channel交換資訊(pub/sub)
mster節點上會有一個釋出訂閱的channel(頻道)用于讓sentinel節點進行資訊交換利用的原理就是每個sentinel釋出資訊,其他的sentinel可以收到資訊(資訊中會包含目前自身的資訊、master的資訊和對master和slave的判斷),最後sentinel之間會達成一個共識,他們之前的資訊交換是利用的master的 channel,利用的是釋出訂閱。
這個定時任務的主要目的如下:
- 通過_sentinel_:hello頻道互動
- 互動對節點的看法和自身的資訊
3.每1秒每個sentinel對其他的sentinel執行和redis執行ping
這個定時任務的主要目的如下:
- 心跳檢測,失敗判斷的依據
六、其他
1. 主觀下線和可觀下線
- 主觀下線:每個sentinel節點對節點失敗的偏見
- 客觀下線:所有sentinel對節點的失敗達成共識
2.上司者選擇
- 原因:隻有一個sentinel完成故障轉移
- 選舉:通過sentinel is-master-down-by-addr 指令都希望成為上司者
選舉過程如下:
- 每個主觀下線的sentinel向其他sentinel發送指令,要求将他設定為上司者。
- 收到指令的sentinel節點如果沒有同意通過其他sentinel節點發送的指令,那麼将同意該請求否則拒絕。
- 如果該sentinel節點發現自己已經超過sentinel集合的半數并且超過 sentinel monitor這個配置中所設定的數量,那麼它将成為上司者。
- 如果此過程有多個sentinel成為上司者,那麼将等待一段時間再次選舉。
3.故障轉移
故障轉移有如下幾個步驟:
- 從slave節點選出一個“合适”的節點作為新的matser
- 對上面的slave節點執行 slaveof no one 指令讓其成為master節點。
- 向剩餘的slave節點發送指令,讓他們成為新master節點的slave節點,複制規則和parallel-sync參數有關
- 更新對原來master節點配置為slave,并保持着對其關注,當其恢複後指令它去複制新的master節點。
什麼是合适的slave節點?
- 選擇 save-priority(slave節點優先級)最高的slave節點,如果存在則傳回,不存在則繼續。
- 選擇複制偏移量最大的slave節點(複制的最完整),如果存在則傳回,不存在則繼續。
- 選擇runId最小的slave節點
4.消息丢失
Redis主從采用異步複制,意味着主節點挂掉時,從節點可能沒有收到全部的同步消息,這部分未同步的消息就丢失了,如果主從延遲特别大,那麼丢失的資料就可能會特别多。Sentinel無法保證資料不丢失,但也能保證消息少丢失,它有兩個選項可以限制主從延遲過大。
min-slaves-to-write 1
min-slaves-max-lag 10
第一個參數表示主節點必須至少有一個從節點在進行正常複制,否則就停止對外寫服務,喪失可用性。
第二個參數控制什麼是正常複制,什麼是異常複制,它的機關是秒(s),表示如果在10s沒有收到從節點回報,就意味着從節點同步不正常,要麼是網絡斷開了,要麼是一直沒有給回報。