本文主要介紹一種通過Jedis&Sentinel實現Redis集群高可用方案,該方案需要使用Jedis2.2.2及以上版本(強制),Redis2.8及以上版本(可選,Sentinel最早出現在Redis2.4中,Redis2.8中Sentinel更加穩定),Redis集群是以分片(Sharding)加主從的方式搭建,滿足可擴展性的要求;
Redis Sentinel介紹
Redis Sentinel是Redis官方提供的集群管理工具,主要有三大功能:
監控,能持續監控Redis的主從實例是否正常工作;
通知,當被監控的Redis實例出問題時,能通過API通知系統管理員或其他程序;
自動故障恢復,如果主實例無法正常工作,Sentinel將啟動故障恢復機制把一個從實例提升為主實例,其他的從實例將會被重新配置到新的主實例,且應用程序會得到一個更換新地址的通知。
Redis Sentinel是一個分布式系統,可以部署多個Sentinel實例來監控同一組Redis實例,它們通過Gossip協議來確定一個主實例宕機,通過Agreement協議來執行故障恢復和配置變更,一般在生產環境中部署多個實例來提高系統可用性,只要有一個Sentinel實例運行正常,就能保證被監控的Redis實例運行正常(類似Zookeeper,通過多個Zookeeper來提高系統可用性);
本文不涉及Sentinel的實現細節和工作原理,讀者可以閱讀其他文章了解;
Redis HA方案
HA的關鍵在於避免單點故障及故障恢復,在Redis Cluster未發布之前,Redis一般以主/從方式部署(這里討論的應用從實例主要用於備份,主實例提供讀寫,有不少應用是讀寫分離的,讀寫操作需要取不同的Redis實例,該方案也可用於此種應用,原理都是相通的,區別在於數據操作層如何封裝),該方式要實現HA主要有如下幾種方案:
1,keepalived:通過keepalived的虛擬IP,提供主從的統一訪問,在主出現問題時,通過keepalived運行腳本將從提升為主,待主恢復后先同步后自動變為主,該方案的好處是主從切換后,應用程序不需要知道(因為訪問的虛擬IP不變),壞處是引入keepalived增加部署復雜性;
2,zookeeper:通過zookeeper來監控主從實例,維護最新有效的IP,應用通過zookeeper取得IP,對Redis進行訪問;
3,sentinel:通過Sentinel監控主從實例,自動進行故障恢復,該方案有個缺陷:因為主從實例地址(IP&PORT)是不同的,當故障發生進行主從切換后,應用程序無法知道新地址,故在Jedis2.2.2中新增了對Sentinel的支持,應用通過redis.clients.jedis.JedisSentinelPool.getResource()取得的Jedis實例會及時更新到新的主實例地址。
筆者所在的公司先使用了方案1一段時間后,發現keepalived在有些情況下會導致數據丟失,keepalived通過shell腳本進行主從切換,配置復雜,而且keepalived成為新的單點,后來選用了方案3,使用Redis官方解決方案;(方案2需要編寫大量的監控代碼,沒有方案3簡便,網上有人使用方案2讀者可自行查看)
選用Sentinel出現的問題
Sentinel&Jedis看上去是個完美的解決方案,這句話只說對了一半,在無分片的情況是這樣,但我們的應用使用了數據分片-sharing,數據被平均分布到4個不同的實例上,每個實例以主從結構部署,Jedis沒有提供基於Sentinel的ShardedJedisPool,也就是說在4個分片中,如果其中一個分片發生主從切換,應用所使用的ShardedJedisPool無法獲得通知,所有對那個分片的操作將會失敗。
本文提供一個基於Sentinel的ShardedJedisPool,能及時感知所有分片主從切換行為,進行連接池重建,源碼見ShardedJedisSentinelPool.java
ShardedJedisSentinelPool實現分析
構造函數
類似之前的Jedis Pool的構造方法,需要參數poolConfig提供諸如maxIdle,maxTotal之類的配置,masters是一個List,用來保存所有分片Master在Sentinel中配置的名字(注意master的順序不能改變,因為Shard算法是依據分片位置進行計算,如果順序錯誤將導致數據存儲混亂),sentinels是一個Set,其中存放所有Sentinel的地址(格式:IP:PORT,如127.0.0.1:26379),順序無關;
初始化連接池
在構造函數中,通過方法
取得當前所有分片的master地址(IP&PORT),對每個分片,通過順次連接Sentinel實例,獲取該分片的master地址,如果無法獲得,即所有Sentinel都無法連接,將休眠1秒后繼續重試,直到取得所有分片的master地址,代碼塊如下:
通過
初始化連接池,到此連接池中的所有連接都指向分片的master;
監控每個Sentinel
在方法
最后,會為每個Sentinel啟動一個Thread來監控Sentinel做出的更改:
該線程的run方法通過Jedis Pub/Sub API(實現JedisPubSub接口,並通過jedis.subscribe進行訂閱)向Sentinel實例訂閱“+switch-master”頻道,當Sentinel進行主從切換時,該線程會得到新Master地址的通知,通過master name判斷哪個分片進行了切換,將新master地址替換原來位置的地址,並調用initPool(List masters)進行Jedis連接池重建;后續所有通過該連接池取得的連接都指向新Master地址,對應用程序透明;
應用示例

總結
本文通過現實中遇到的問題,即在Redis數據分片的情況下,在使用Sentinel做HA時,如何做到主從的切換對應用程序透明,通過Jedis的Pub/Sub功能,能同時監控多個分片的主從切換情況,並通過監聽到的新地址重新構造連接池,后續從連接池中取得的所有連接都指向新地址。該方案的關鍵是:使用sentinel做HA,Jedis版本必須2.2.2及以上,所有訪問Redis實例的連接都必須從連接池中獲取;
該項目的GitHub主頁: https://github.com/warmbreeze/sharded-jedis-sentinel-pool
評論
讀寫分離不支持,只支持slave作為備份,文章中已經說明過了
兄弟你這是生產已經用了,還是學些寫的,你看你的那個 while那一部分 那個定義的范圍明顯錯了,應該在for循環外層才對,
不知道你說的是什么的定義范圍有誤?如果方便能說的詳細些嗎?或者把代碼貼一下看看怎么改
兄弟你這是生產已經用了,還是學些寫的,你看你的那個 while那一部分 那個定義的范圍明顯錯了,應該在for循環外層才對,