上一節中介紹了master-slave模式,在最小配置:master、slave各一個節點的情況下,不管是master還是slave down掉一個,“完整的”讀/寫功能都將受影響,這在生產環境中顯然不能接受。幸好redis提供了sentinel(哨兵)機制,通過sentinel模式啟動redis后,自動監控master/slave的運行狀態,基本原理是:心跳機制+投票裁決
每個sentinel會向其它sentinal、master、slave定時發送消息,以確認對方是否“活”着,如果發現對方在指定時間(可配置)內未回應,則暫時認為對方已掛(所謂的“主觀認為宕機” Subjective Down,簡稱SDOWN)。
若“哨兵群”中的多數sentinel,都報告某一master沒響應,系統才認為該master"徹底死亡"(即:客觀上的真正down機,Objective Down,簡稱ODOWN),通過一定的vote算法,從剩下的slave節點中,選一台提升為master,然后自動修改相關配置。
最小化的sentinel配置文件為:
1 port 7031 2 3 dir /opt/app/redis/redis-2.8.17/tmp 4 5 sentinel monitor mymaster 10.6.144.155 7030 1 6 sentinel down-after-milliseconds mymaster 5000 7 sentinel parallel-syncs mymaster 1 8 sentinel failover-timeout mymaster 15000
第1行,指定sentinel使用的端口,不能與redis-server運行實例的端口沖突
第3行,指定工作目錄
第5行,顯示監控master節點10.6.144.155,master節點使用端口7030,最后一個數字表示投票需要的"最少法定人數",比如有10個sentinal哨兵都在監控某一個master節點,如果需要至少6個哨兵發現master掛掉后,才認為master真正down掉,那么這里就配置為6,最小配置1台master,1台slave,在二個機器上都啟動sentinal的情況下,哨兵數只有2個,如果一台機器物理掛掉,只剩一個sentinal能發現該問題,所以這里配置成1,至於mymaster只是一個名字,可以隨便起,但要保證5-8行都使用同一個名字
第6行,表示如果5s內mymaster沒響應,就認為SDOWN
第8行,表示如果15秒后,mysater仍沒活過來,則啟動failover,從剩下的slave中選一個升級為master
第7行,表示如果master重新選出來后,其它slave節點能同時並行從新master同步緩存的台數有多少個,顯然該值越大,所有slave節點完成同步切換的整體速度越快,但如果此時正好有人在訪問這些slave,可能造成讀取失敗,影響面會更廣。最保定的設置為1,只同一時間,只能有一台干這件事,這樣其它slave還能繼續服務,但是所有slave全部完成緩存更新同步的進程將變慢。
另:一個sentinal可同時監控多個master,只要把5-8行重復多段,加以修改即可。
具體使用步驟:(約定7030是redis-server端口,7031是redis-sentinel端口,且master、slave上的redis-server均已正常啟動)
1、先在redis根目錄下創建conf子目錄,新建配置文件sentinel.conf,內容參考前面的內容(master和slave上都做相同的配置)
2、./redis-sentinel ../conf/sentinel.conf 即可(master和slave上都啟用sentinel,即最終有二個哨兵)
3、./redis-cli -p 7031 sentinel masters 可通過該命令查看當前的master節點情況(注,這里一定要帶sentinel的端口)
4、在master上,./redis-cli -p 7030 shutdown ,手動把master停掉,觀察sentinel的輸出
[17569] 21 Nov 11:06:56.277 # +odown master mymaster 10.6.144.155 7030 #quorum 1/1
[17569] 21 Nov 11:06:56.277 # Next failover delay: I will not start a failover before Fri Nov 21 11:07:26 2014
[17569] 21 Nov 11:06:57.389 # +config-update-from sentinel 10.6.144.156:7031 10.6.144.156 7031 @ mymaster 10.6.144.155 7030
[17569] 21 Nov 11:06:57.389 # +switch-master mymaster 10.6.144.155 7030 10.6.144.156 7030
[17569] 21 Nov 11:06:57.389 * +slave slave 10.6.53.131:7030 10.6.53.131 7030 @ mymaster 10.6.144.156 7030
從紅線部分可以看出,master發生了遷移,等剛才停掉的master再重啟后,可以觀察到它將被當作slave加入,類似以下輸出:
[36444] 21 Nov 11:11:14.540 * +convert-to-slave slave 10.6.144.155:7030 10.6.144.155 7030 @ mymaster 10.6.144.156 7030
注意事項:發生master遷移后,如果遇到運維需要,想重啟所有redis,必須最先重啟“新的”master節點,否則sentinel會一直找不到master。
最后,如果想停止sentinel,可輸入命令./redis-cli -p 7031 shutdown
客戶端的使用:
一、Jedis

1 @Test 2 public void testJedis() throws InterruptedException { 3 4 Set<String> sentinels = new HashSet<String>(); 5 sentinels.add("10.6.144.155:7031"); 6 sentinels.add("10.6.144.156:7031"); 7 8 JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", 9 sentinels); 10 11 Jedis jedis = sentinelPool.getResource(); 12 13 System.out.println("current Host:" 14 + sentinelPool.getCurrentHostMaster()); 15 16 String key = "a"; 17 18 String cacheData = jedis.get(key); 19 20 if (cacheData == null) { 21 jedis.del(key); 22 } 23 24 jedis.set(key, "aaa");// 寫入 25 26 System.out.println(jedis.get(key));// 讀取 27 28 System.out.println("current Host:" 29 + sentinelPool.getCurrentHostMaster());// down掉master,觀察slave是否被提升為master 30 31 jedis.set(key, "bbb");// 測試新master的寫入 32 33 System.out.println(jedis.get(key));// 觀察讀取是否正常 34 35 sentinelPool.close(); 36 jedis.close(); 37 38 }
4-6行是關鍵,這里指定了sentinel節點信息。但這段代碼在運行時發現一個問題:對於1主1從的最小化配置,如果連續發生兩次寫操作,第1次set成功后,如果斷點停在這里,down掉master,這時剩下的slave會提升為master,但是第2次set時,會拋異常,類似:連接已斷開。(注:通過Spring-Data-Redis整合Jedis與redis時,利用RedisTemplate調用不會有這個問題,看來Spring-Data-Redis針對這個問題做過優化,所以建議正式項目中,通過Spring-Data-Redis整合Redis來調用相關功能,而不是自己直接引用Jedis的jar包來使用)
二、Redisson

1 @Test 2 public void testRedisson() throws InterruptedException, ExecutionException, 3 TimeoutException { 4 5 Config config = new Config(); 6 7 config.useSentinelConnection().setMasterName("mymaster") 8 .addSentinelAddress("10.6.144.155:7031", "10.6.144.156:7031"); 9 config.useSentinelConnection().setRetryInterval(1000); 10 config.useSentinelConnection().setRetryAttempts(1); 11 12 Redisson redisson = Redisson.create(config); 13 14 String key = "test"; 15 16 RBucket<String> myObj = redisson.getBucket(key); 17 if (myObj != null) { 18 myObj.delete(); 19 } 20 21 myObj.set("aaa");// 寫入 22 23 System.out.println(myObj.get());// 讀取 24 25 myObj.set("bbb");// down掉master,觀察是否能寫入新master 26 27 System.out.println(myObj.get()); 28 29 redisson.shutdown(); 30 31 }
同樣做類似的測試,二次寫,二次讀,如果第1次寫后,人工down掉master,剩下的slave會提升成master,第二次寫ok,但此時redis節點中,只剩master,沒有slave了,從測試結果上看,第二次get還是嘗試去找slave節點,但是此時已經不存在了,所以一直在等候,導致后面的的處理被阻塞。
這不是redis的問題,而是Redisson客戶端設計不夠智能。
鑒於這種現狀,如果要使用Redisson,最好做成1主2從的部署結構:(sentinel.conf中的“法定人數”,建議調整成2)
這樣的好處是,1個master掛掉后,剩下的2台slave中,會有1台提升為master,整體仍然保證有1個master和1個slave,讀寫均不受影響。
關於Sentinel的更多細節,可參考官網文檔:http://www.redis.io/topics/sentinel