Redis高可用原理總結(jié)
因?yàn)樗趫F(tuán)隊(duì)主要負(fù)責(zé)Infra高可用相關(guān)的內(nèi)容,對(duì)項(xiàng)目中所有用到的open source的高可用原理進(jìn)行了學(xué)習(xí)。最近學(xué)習(xí)了一下Redis的高可用原理,這里通過筆記整理一下。
Redis高可用架構(gòu)
Redis相對(duì)于之前的PostgreSQL在高可用方面做的更加成熟,并且實(shí)現(xiàn)了分布式的高可用架構(gòu),其架構(gòu)如下圖所示:

Redis的高可用架構(gòu)主要分為兩個(gè)集群,一個(gè)是Redis Server集群,另一個(gè)是Sentinel集群。
Redis Server集群:
-
master/slave(1+N)模式。Redis采用master/slave的模式作高可用,一個(gè)master可以有多個(gè)slave,并且slave還可以級(jí)聯(lián)子slave - 異步同步。出于效率的原因,Redis的同步方式采用的
異步同步。master上數(shù)據(jù)的變化(包括:客戶端寫數(shù)據(jù),key過期和以及其他的操作)都會(huì)被sync到slave上。
Sentinel集群的功能:
- 監(jiān)控。對(duì)master和slave節(jié)點(diǎn)作心跳檢測(cè),更新其狀態(tài)
- 通知。當(dāng)master和slave節(jié)點(diǎn)發(fā)生故障時(shí),提供API用于通知其他process或者管理者
- 自動(dòng)Failover。當(dāng)master出現(xiàn)故障時(shí),能夠自動(dòng)failover,promote最佳的slave作為新的master(先比較優(yōu)先級(jí),然后比較lag,如果都一樣則選擇最小runid的slave)
- 提供訪問配置。提供master的
ip/port以及密碼信息,用于客戶端訪問master。
Note:
- Sentinel是采用
Raft協(xié)議選舉新的master,因此需要建立奇數(shù)個(gè)Sentinel- 一個(gè)Sentinel集群是可以管理多個(gè)Redis Server集群(由于Redis是一個(gè)單線程的應(yīng)用,只能使用單核處理,當(dāng)業(yè)務(wù)較大時(shí),可以通過創(chuàng)建多個(gè)master/slave集群作負(fù)載均衡)
重要過程和配置
1. Sentinel初始化和配置
通常Sentinel可以通過下面指令運(yùn)行:
redis-sentinel /path/to/sentinel.conf
#or
redis-server /path/to/sentinel.conf --sentinel
Sentinel的配置格式為sentinel <option_name> <master_name> <option_value>,而一個(gè)簡(jiǎn)單的sentinel.conf如下:
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
解釋一下其中配置的含義:
-
sentinel monitor mymaster 127.0.0.1 6379 2表示sentinel監(jiān)控mymaster集群,其當(dāng)前master為127.0.0.1,其服務(wù)端口為6379,發(fā)生failover需要2個(gè)及以上的Sentinel同意; -
sentinel down-after-milliseconds mymaster 60000表示master超過60s健康監(jiān)測(cè)失敗,就認(rèn)為該master down; -
sentinel failover-timeout mymaster 180000當(dāng)前failover過程超過180s,才認(rèn)為failover失敗,重新選舉; -
sentinel parallel-syncs mymaster 1表示同一時(shí)間只有一個(gè)slave可以跟master進(jìn)行sync(數(shù)字設(shè)置大,那么可以sync更快)
當(dāng)然所有配置都可以通過SENTINEL SET command在運(yùn)行時(shí)動(dòng)態(tài)修改。
Ok,當(dāng)我們通過上面的配置啟動(dòng)Sentinel之后,它會(huì)做以下的操作:
- 通過配置文件更新
mymaster集群的信息; - 連接到
mymaster當(dāng)前master127.0.0.1:6379,創(chuàng)建兩個(gè)連接:- 一個(gè)用于發(fā)送命令
- 另一個(gè)為master的PUB/SUB的channel—
__sentinel__:hello(所有的Sentinel都會(huì)在該channel發(fā)送hello信息,包含當(dāng)前集群的最新配置,新加入的Sentinel可以根據(jù)該配置更新自己的配置文件);
- 通過前面跟master建立的連接,發(fā)送
INFO命令,并且解析結(jié)果,截取所需信息,更新當(dāng)前集群的topo信息(下面會(huì)有詳細(xì)介紹); - Sentinel會(huì)根據(jù)添加的slave信息,同樣建立兩個(gè)連接,用于slave的健康監(jiān)測(cè),failover以及發(fā)布配置更新。
Step 3中INFO返回的內(nèi)容如下:
127.0.0.1:6379> INFO
...
run_id:27e26bb8d6d1ec66fa5d8a15e080a17dcb6cb002
...
# Replication
role:master
connected_slaves:2
slave0:ip=172.17.0.6,port=6379,state=online,offset=3578171,lag=1
slave1:ip=172.17.0.8,port=6379,state=online,offset=3578171,lag=1
...
根據(jù)上面的信息,Sentinel會(huì)修改它的配置文件,添加其slave信息,并且修改epoch,用來表示版本號(hào)(新加入的節(jié)點(diǎn)會(huì)發(fā)現(xiàn)自己版本號(hào)小于集群中的版本號(hào),會(huì)用大版本號(hào)的配置更新自己的配置文件):
cat /redis/sentinel.conf
...
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 172.17.0.6 6379
sentinel known-slave mymaster 172.17.0.8 6379
sentinel known-sentinel mymaster 172.17.0.7 26379 33b728312e9fbd1323e5b2bcf07ff48aacbc87e4
sentinel known-sentinel mymaster 172.17.0.12 26379 a21447632bbc6bc602f6532103a0fcdeec470356
sentinel current-epoch 1
2. Failover過程
首先說明一下,Sentinel會(huì)運(yùn)行三個(gè)定時(shí)器:
- 每10秒每個(gè)sentinel會(huì)對(duì)master和slave執(zhí)行之前提到的
INFO命令,用于:
a)發(fā)現(xiàn)slave節(jié)點(diǎn)
b)確認(rèn)主從關(guān)系 - 每2秒每個(gè)sentinel通過
PUB/SUB的頻道(__sentinel__:hello)交換信息(對(duì)節(jié)點(diǎn)的"看法"和自身的信息),達(dá)成共識(shí)。 - 每1秒每個(gè)sentinel對(duì)其他sentinel和redis節(jié)點(diǎn)執(zhí)行
PING操作(相互監(jiān)控),做心跳檢測(cè)。
一個(gè)完整的Failover包含下面的過程:
- 對(duì)于第3個(gè)定時(shí)器,當(dāng)一個(gè)Sentinel發(fā)現(xiàn)master心跳檢測(cè)失敗超過
down-after-milliseconds之后,則會(huì)將該master置為SDOWN(主觀下線),并且通過PUB/SUB廣播(使用的gossip); - 如果足夠數(shù)量的Sentinel認(rèn)為該master已經(jīng)down,則會(huì)將master狀態(tài)置為
ODOWN(客觀下線); - 置為
ODOWN的Sentinel,如果沒有給其他leader投過票,則會(huì)申請(qǐng)成為candidate,開始請(qǐng)求成為leader,并增加自己的epoch; - 在leader選舉周期結(jié)束之前,收到足夠數(shù)量投票(超過一半,并且滿足quorum數(shù)量)的節(jié)點(diǎn)就會(huì)成為L(zhǎng)eader從slave中選取最佳的節(jié)點(diǎn);
- 將選取的slave,執(zhí)行
SLAVE OF NO ONE命令,將該slave提升為新master; - 將配置的
epoch+1,表示版本號(hào)已經(jīng)更新,并且在PUB/SUB頻道內(nèi)廣播該配置。
Note:
- 對(duì)于上面的第4步,如果定時(shí)器結(jié)束前,沒有節(jié)點(diǎn)收到足夠票數(shù),則節(jié)點(diǎn)會(huì)退回follower狀態(tài),通過一個(gè)隨機(jī)定時(shí)器進(jìn)行退避,退避過程中沒有給其它節(jié)點(diǎn)投票,就會(huì)又成為candidate。由于是一個(gè)隨機(jī)定時(shí)器,所以所有節(jié)點(diǎn)都成為candidate的概率比較小,通常一個(gè)epoch就可以選出leader;
- 如果leader超過
failover-timeout時(shí)間仍未完成failover,Sentinel集群同樣需要重新開始leader選舉;- epoch機(jī)制是
Raft保證時(shí)間同步的一個(gè)機(jī)制,詳細(xì)原理可以參見Raft的論文
看困了?手動(dòng)做個(gè)failover實(shí)驗(yàn)動(dòng)手驗(yàn)證一下:首先在master節(jié)點(diǎn)上執(zhí)行DEBUG SLEEP 30模擬節(jié)點(diǎn)hang住30s,因此Sentinel會(huì)進(jìn)行Failover(為了方便,這里我直接用redis-operator在k8s上部署了一個(gè)集群):
? ~ kubectl exec -it rfr-redisfailover-0 -- redis-cli DEBUG SLEEP 30
查看任意的Sentinel的log:
kubectl log rfs-redisfailover-64c54477f6-gkqvq -f
1:X 14 Apr 15:36:47.315 # +sdown master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:47.399 # +odown master mymaster 172.17.0.5 6379 #quorum 2/2
1:X 14 Apr 15:36:47.399 # +new-epoch 2
1:X 14 Apr 15:36:47.399 # +try-failover master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:47.401 # +vote-for-leader b864933d1fad0ef1dfb58b4faabf75c97f13d91c 2
1:X 14 Apr 15:36:47.404 # 33b728312e9fbd1323e5b2bcf07ff48aacbc87e4 voted for b864933d1fad0ef1dfb58b4faabf75c97f13d91c 2
1:X 14 Apr 15:36:47.404 # a21447632bbc6bc602f6532103a0fcdeec470356 voted for b864933d1fad0ef1dfb58b4faabf75c97f13d91c 2
1:X 14 Apr 15:36:47.477 # +elected-leader master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:47.477 # +failover-state-select-slave master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:47.536 # +selected-slave slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:47.536 * +failover-state-send-slaveof-noone slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:47.636 * +failover-state-wait-promotion slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:48.453 # +promoted-slave slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:48.453 # +failover-state-reconf-slaves master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:48.541 * +slave-reconf-sent slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:49.494 * +slave-reconf-inprog slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:49.495 * +slave-reconf-done slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:49.570 # -odown master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:49.571 # +failover-end master mymaster 172.17.0.5 6379
1:X 14 Apr 15:36:49.571 # +switch-master mymaster 172.17.0.5 6379 172.17.0.8 6379
1:X 14 Apr 15:36:49.571 * +slave slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.8 6379
1:X 14 Apr 15:36:49.572 * +slave slave 172.17.0.5:6379 172.17.0.5 6379 @ mymaster 172.17.0.8 6379
1:X 14 Apr 15:36:54.593 # +sdown slave 172.17.0.5:6379 172.17.0.5 6379 @ mymaster 172.17.0.8 6379
1:X 14 Apr 15:37:12.186 # -sdown slave 172.17.0.5:6379 172.17.0.5 6379 @ mymaster 172.17.0.8 6379
如同前面講的過程,首先sentinel更新了epoch,并且開始選舉leader,然后開始選擇最佳的slave,然后promote為master,將之前的master降為slave,更新配置信息,再將之前的slave掛在新的master下面。
附:log的解析格式:<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>,detail含義:
+reset-master <instance details> -- 當(dāng)master被重置時(shí).
+slave <instance details> -- 當(dāng)檢測(cè)到一個(gè)slave并添加進(jìn)slave列表時(shí).
+failover-state-reconf-slaves <instance details> -- Failover狀態(tài)變?yōu)閞econf-slaves狀態(tài)時(shí)
+failover-detected <instance details> -- 當(dāng)failover發(fā)生時(shí)
+slave-reconf-sent <instance details> -- sentinel發(fā)送SLAVEOF命令把它重新配置時(shí)
+slave-reconf-inprog <instance details> -- slave被重新配置為另外一個(gè)master的slave,但數(shù)據(jù)復(fù)制還未發(fā)生時(shí)。
+slave-reconf-done <instance details> -- slave被重新配置為另外一個(gè)master的slave并且數(shù)據(jù)復(fù)制已經(jīng)與master同步時(shí)。
-dup-sentinel <instance details> -- 刪除指定master上的冗余sentinel時(shí) (當(dāng)一個(gè)sentinel重新啟動(dòng)時(shí),可能會(huì)發(fā)生這個(gè)事件).
+sentinel <instance details> -- 當(dāng)master增加了一個(gè)sentinel時(shí)。
+sdown <instance details> -- 進(jìn)入SDOWN狀態(tài)時(shí);
-sdown <instance details> -- 離開SDOWN狀態(tài)時(shí)。
+odown <instance details> -- 進(jìn)入ODOWN狀態(tài)時(shí)。
-odown <instance details> -- 離開ODOWN狀態(tài)時(shí)。
+new-epoch <instance details> -- 當(dāng)前配置版本被更新時(shí)。
+try-failover <instance details> -- 達(dá)到failover條件,正等待其他sentinel的選舉。
+elected-leader <instance details> -- 被選舉為去執(zhí)行failover的時(shí)候。
+failover-state-select-slave <instance details> -- 開始要選擇一個(gè)slave當(dāng)選新master時(shí)。
no-good-slave <instance details> -- 沒有合適的slave來擔(dān)當(dāng)新master
selected-slave <instance details> -- 找到了一個(gè)適合的slave來擔(dān)當(dāng)新master
failover-state-send-slaveof-noone <instance details> -- 當(dāng)把選擇為新master的slave的身份進(jìn)行切換的時(shí)候。
failover-end-for-timeout <instance details> -- failover由于超時(shí)而失敗時(shí)。
failover-end <instance details> -- failover成功完成時(shí)。
switch-master <master name> <oldip> <oldport> <newip> <newport> -- 當(dāng)master的地址發(fā)生變化時(shí)。通常這是客戶端最感興趣的消息了。
+tilt -- 進(jìn)入Tilt模式。
-tilt -- 退出Tilt模式。
3. Client設(shè)計(jì)Guide Line
由于Redis的集群會(huì)發(fā)生failover,因此對(duì)于client的行為,redis官方給出了下面的Guide Line(當(dāng)前基本上所有的Redis的客戶端都支持Sentinel了)。
服務(wù)發(fā)現(xiàn)步驟:
- 提供Sentinel的地址列表,Client會(huì)選擇從第一個(gè)Sentinel嘗試連接(對(duì)于Kubernetes,只用一個(gè)Headless Service就搞定);
- Client通過
SENTINEL get-master-addr-by-name master-name命令查詢到master節(jié)點(diǎn)的信息; - 連接到master節(jié)點(diǎn)調(diào)用
ROLE,驗(yàn)證當(dāng)前節(jié)點(diǎn)是否是真正的master,如果不是就從Sentinel中第二個(gè)節(jié)點(diǎn)重新開始服務(wù)發(fā)現(xiàn);
重連處理:
當(dāng)Client出現(xiàn)連接超時(shí),或者被user中斷之后,Redis建議Client重新進(jìn)行服務(wù)發(fā)現(xiàn)。
Sentinel Failover重連:
當(dāng)Redis集群發(fā)生Failover時(shí),Sentinel會(huì)發(fā)送CLIENT KILL type normal到Redis節(jié)點(diǎn),強(qiáng)制Client重連。
訪問Slaves:
Client可以通過SENTINEL slaves master-name查詢slaves的信息,并且通過ROLE命令驗(yàn)證當(dāng)前slave的狀態(tài)。