因為Redis擁有諸多優(yōu)秀的特性,使用范圍越來越廣彪见,系統(tǒng)對其可用性的依賴也越來越重乾忱,當前絕大部分系統(tǒng)使用的Redis都實現(xiàn)了高可用屉更。這里主要介紹Redis官方推薦的兩種高可用方案Sentinel和Redis Cluster彤守。
CAP選擇
CAP理論:一個分布式系統(tǒng)最多只能同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance)這三項中的兩項哭靖。
Redis選擇了AP具垫,犧牲了C。
復制/同步的實現(xiàn)
任何分布式系統(tǒng)试幽,要滿足分區(qū)容錯性筝蚕,必須實現(xiàn)數(shù)據(jù)的復制/同步功能。
SYNC
從服務器對主服務器的同步操作需要通過向主服務器發(fā)送SYNC命令來完成铺坞,以下是SYNC命令的執(zhí)行步驟:
- 從服務器向主服務器發(fā)送SYNC命令饰及。
- 收到SYNC命令的主服務器執(zhí)行BGSAVE命令,在后臺生成一個RDB文件康震,并使用一個緩沖區(qū)記錄從現(xiàn)在開始執(zhí)行的所有寫命令燎含。
- 當主服務器的BGSAVE命令執(zhí)行完畢時,主服務器會將BGSAVE命令生成的RDB文件發(fā)送給從服務器腿短,從服務器接收并載入這個RDB文件屏箍,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務器執(zhí)行BGSAVE命令時的數(shù)據(jù)庫狀態(tài)。
-
主服務器將記錄在緩沖區(qū)里面的所有寫命令發(fā)送給從服務器橘忱,從服務器執(zhí)行這些寫命令赴魁,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務器數(shù)據(jù)庫當前所處的狀態(tài)。
從服務器斷開重連后钝诚,即使只有很小部分數(shù)據(jù)沒有同步颖御,也需要重新整個同步過來,而SYNC命令是一個非常耗費資源的操作凝颇,所以Redis有必要保證在真正有需要時才執(zhí)行SYNC命令潘拱。 所以從Redis2.8開始,使用PSYNC代替SYNC執(zhí)行主從同步功能拧略。
PSYNC
PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)兩種模式:
- 其中完整重同步用于處理初次復制情況:完整重同步的執(zhí)行步驟和SYNC命令的執(zhí)行步驟基本一樣芦岂,它們都是通過讓主服務器創(chuàng)建并發(fā)送RDB文件,以及向從服務器發(fā)送保存在緩沖區(qū)里面的寫命令來進行同步垫蛆。
-
而部分重同步則用于處理斷線后重復制情況:當從服務器在斷線后重新連接主服務器時禽最,如果條件允許,主服務器可以將主從服務器連接斷開期間執(zhí)行的寫命令發(fā)送給從服務器袱饭,從服務器只要接收并執(zhí)行這些寫命令川无,就可以將數(shù)據(jù)庫更新至主服務器當前所處的狀態(tài)。
部分重同步的功能由一下三個部分構(gòu)成:
- 主服務器的復制偏移量(replication offset)和從服務器的復制偏移量
- 主服務器的復制積壓緩沖區(qū)(replication backlog)
- 服務器的運行ID(run ID)
從這三個部分構(gòu)成應該很容易就能夠判斷出來部分重同步功能的實現(xiàn)邏輯虑乖。(如有不明白可以參考《Redis設計與實現(xiàn)》)
高可用
Redis實現(xiàn)高可用主要有兩種方式懦趋,一種是Sentinel(3.0之前),一種是3.0正式支持的Redis Cluster(推薦)决左。
Sentinel(哨兵)
在Redis Server之外愕够,運行數(shù)個Sentinel(一般是3個)走贪,負責監(jiān)控Redis Server健康狀況以及實現(xiàn)故障轉(zhuǎn)移。在Sentinel模式下惑芭,Redis Server一般具有1個主節(jié)點及多個從節(jié)點坠狡,Sentinel會與所有的Redis節(jié)點以及其他Sentinel保持連接。 具體工作模式如下:
檢測主觀下線狀態(tài)
在默認情況下遂跟,Sentinel會以每秒一次的頻率向所有與它創(chuàng)建了命令連接的實例(包括主服務器逃沿、從服務器、其他Sentinel在內(nèi))發(fā)送PING命令幻锁,并通過實例返回的PING命令回復來判斷實例是否在線凯亮。如果連續(xù)返回無效回復,則表示這個實例已經(jīng)進入主觀下線狀態(tài)哄尔。
可以修改down-after-milliseconds來控制判斷主觀下線的所需要的時間長度
檢查客觀下線狀態(tài)
當Sentinel將一個主服務器判斷為主觀下線之后假消,為了確認這個主服務器是否真的下線了,它會向同樣監(jiān)視這一主服務器的其他Sentinel進行詢問岭接,看它們是否也認為主服務器已經(jīng)進入了下線狀態(tài)(可以是主觀下線或者客觀下線)富拗。當Sentinel從其他Sentinel那里接收到足夠數(shù)量的已下線判斷之后,Sentinel就會將從服務器判定為客觀下線鸣戴,并對主服務器執(zhí)行故障轉(zhuǎn)移操作啃沪。
選舉領頭Sentinel
以下是Redis選舉領頭Sentinel的規(guī)則和方法:
- 所有在線的Sentinel都有被選為領頭Sentinel的資格,換句話說窄锅,監(jiān)視同一個主服務器的多個在線Sentinel中的任意一個都有可能成為領頭Sentinel创千。
- 每次進行領頭Sentinel選舉之后,不論選舉是否成功入偷,所有Sentinel的配置紀元(configuration epoch)的值都會自增一次追驴。配置紀元實際上就是一個計數(shù)器,并沒有什么特別的盯串。
- 在一個配置紀元里面氯檐,所有Sentinel都有一次將某個Sentinel設置為局部領頭Sentinel的機會,并且局部領頭一旦設置体捏,在這個配置紀元里面就不能再更改。
- 每個發(fā)現(xiàn)主服務器進入客觀下線的Sentinel都會要求其他Sentinel將自己設置為局部領頭Sentinel糯崎。
- 當一個Sentinel(源Sentinel)向另一個Sentinel(目標Sentinel)發(fā)送SENTINEL is-master-down-by-addr命令几缭,并且命令中的runid參數(shù)不是*符號而是源Sentinel的運行ID時,這表示源Sentinel要求目標Sentinel將前者設置為后者的局部領頭Sentinel沃呢。
- Sentinel設置局部領頭Sentinel的規(guī)則是先到先得:最先向目標Sentinel發(fā)送設置要求的源Sentinel將成為目標Sen-tinel的局部領頭Sentinel年栓,而之后接收到的所有設置要求都會被目標Sentinel拒絕。
- 目標Sentinel在接收到SENTINEL is-master-down-by-addr命令之后薄霜,將向源Sentinel返回一條命令回復某抓,回復中的leader_runid參數(shù)和leader_epoch參數(shù)分別記錄了目標Sentinel的局部領頭Sentinel的運行ID和配置紀元纸兔。
- 源Sentinel在接收到目標Sentinel返回的命令回復之后,會檢查回復中l(wèi)eader_epoch參數(shù)的值和自己的配置紀元是否相同否副,如果相同的話汉矿,那么源Sentinel繼續(xù)取出回復中的leader_runid參數(shù),如果leader_runid參數(shù)的值和源Sen-tinel的運行ID一致备禀,那么表示目標Sentinel將源Sentinel設置成了局部領頭Sentinel洲拇。
- 如果有某個Sentinel被半數(shù)以上的Sentinel設置成了局部領頭Sentinel,那么這個Sentinel成為領頭Sentinel曲尸。舉個例子赋续,在一個由10個Sentinel組成的Sentinel系統(tǒng)里面,只要有大于等于10/2+1=6個Sentinel將某個Sentinel設置為局部領頭Sentinel另患,那么被設置的那個Sentinel就會成為領頭Sentinel纽乱。
- 因為領頭Sentinel的產(chǎn)生需要半數(shù)以上Sentinel的支持,并且每個Sentinel在每個配置紀元里面只能設置一次局部領頭Sentinel昆箕,所以在一個配置紀元里面鸦列,只會出現(xiàn)一個領頭Sentinel。
- 如果在給定時限內(nèi)为严,沒有一個Sentinel被選舉為領頭Sen-tinel敛熬,那么各個Sentinel將在一段時間之后再次進行選舉,直到選出領頭Sentinel為止第股。
Sentinel系統(tǒng)選舉領頭Sentinel的方法是對Raft算法的領頭選舉方法的實現(xiàn)应民。
故障轉(zhuǎn)移
在選舉產(chǎn)生出領頭Sentinel之后,領頭Sentinel將對已下線的主服務器執(zhí)行故障轉(zhuǎn)移操作夕吻,該操作包含以下三個步驟:
- 在已下線主服務器屬下的所有從服務器里面诲锹,挑選出一個從服務器,并將其轉(zhuǎn)換為主服務器涉馅。
- 讓已下線主服務器屬下的所有從服務器改為復制新的主服務器归园。
- 將已下線主服務器設置為新的主服務器的從服務器,當這個舊的主服務器重新上線時稚矿,它就會成為新的主服務器的從服務器庸诱。
Redis Cluster
當一個從節(jié)點發(fā)現(xiàn)自己正在復制的主節(jié)點進入了已下線狀態(tài)時,從節(jié)點將開始對下線主節(jié)點進行故障轉(zhuǎn)移晤揣,以下是故障轉(zhuǎn)移的執(zhí)行步驟:
- 復制下線主節(jié)點的所有從節(jié)點里面桥爽,會有一個從節(jié)點被選中。
- 被選中的從節(jié)點會執(zhí)行SLAVEOF no one命令昧识,成為新的主節(jié)點钠四。
- 新的主節(jié)點會撤銷所有對已下線主節(jié)點的槽指派,并將這些槽全部指派給自己跪楞。
- 新的主節(jié)點向集群廣播一條PONG消息缀去,這條PONG消息可以讓集群中的其他節(jié)點立即知道這個節(jié)點已經(jīng)由從節(jié)點變成了主節(jié)點侣灶,并且這個主節(jié)點已經(jīng)接管了原本由已下線節(jié)點負責處理的槽。
- 新的主節(jié)點開始接收和自己負責處理的槽有關的命令請求缕碎,故障轉(zhuǎn)移完成褥影。
選舉新的主節(jié)點
新的主節(jié)點是通過選舉產(chǎn)生的。以下是集群選舉新的主節(jié)點的方法:
- 集群的配置紀元是一個自增計數(shù)器阎曹,它的初始值為0伪阶。
- 當集群里的某個節(jié)點開始一次故障轉(zhuǎn)移操作時,集群配置紀元的值會被增一处嫌。
- 對于每個配置紀元栅贴,集群里每個負責處理槽的主節(jié)點都有一次投票的機會,而第一個向主節(jié)點要求投票的從節(jié)點將獲得主節(jié)點的投票熏迹。
- 當從節(jié)點發(fā)現(xiàn)自己正在復制的主節(jié)點進入已下線狀態(tài)時檐薯,從節(jié)點會向集群廣播一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息注暗、并且具有投票權的主節(jié)點向這個從節(jié)點投票坛缕。
- 如果一個主節(jié)點具有投票權(它正在負責處理槽),并且這個主節(jié)點尚未投票給其他從節(jié)點捆昏,那么主節(jié)點將向要求投票的從節(jié)點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息赚楚,表示這個主節(jié)點支持從節(jié)點成為新的主節(jié)點。
- 每個參與選舉的從節(jié)點都會接收CLUS-TERMSG_TYPE_FAILOVER_AUTH_ACK消息骗卜,并根據(jù)自己收到了多少條這種消息來統(tǒng)計自己獲得了多少主節(jié)點的支持宠页。
- 如果集群里有N個具有投票權的主節(jié)點,那么當一個從節(jié)點收集到大于等于N/2+1張支持票時寇仓,這個從節(jié)點就會當選為新的主節(jié)點举户。
- 因為在每一個配置紀元里面,每個具有投票權的主節(jié)點只能投一次票遍烦,所以如果有N個主節(jié)點進行投票俭嘁,那么具有大于等于N/2+1張支持票的從節(jié)點只會有一個,這確保了新的主節(jié)點只會有一個服猪。
- 如果在一個配置紀元里面沒有從節(jié)點能收集到足夠多的支持票供填,那么集群進入一個新的配置紀元,并再次進行選舉罢猪,直到選出新的主節(jié)點為止捕虽。
這個選舉新主節(jié)點的方法和選舉領頭Sentinel的方法非常相似,因為兩者都是基于Raft算法的領頭選舉(leader election)方法來實現(xiàn)的坡脐。
注意事項
- 因為Sentinel與Redis Cluster都沒有實現(xiàn)強一致性(也沒有實現(xiàn)最終一致性),所以在使用時房揭,要牢記這一點备闲,不能用在一致性要求特別高的場景晌端,比如全局唯一ID,交易數(shù)據(jù)等恬砂。
- 如果master沒有設置持久化咧纠,存在風險,如果不小心重啟泻骤,則會丟失所有數(shù)據(jù)漆羔,而且從機也會因為同步,丟失所有數(shù)據(jù)(所以一定要高可用)狱掂。就算使用Sentinel也可能存在問題演痒,因為master可能會在很短時間的恢復,從而Sentinel根本沒有來得及檢測到并切換主從趋惨。 如果真的為了極致性能鸟顺,關閉了master的持久化,就一定要關閉自動重啟功能器虾。