建設(shè)項(xiàng)目立項(xiàng)網(wǎng)站廣州百度seo排名
我們在生產(chǎn)中使用 Redis,如果只部署一個(gè) Redis 實(shí)例,當(dāng)該實(shí)例宕機(jī),到恢復(fù)之前都不可用;雖說 Redis 一般都用來做緩存,但不可用給業(yè)務(wù)系統(tǒng)帶來的影響也是不小的,流量大時(shí)甚至?xí)?dǎo)致整個(gè)服務(wù)宕機(jī)。所以 Redis 的高可用也非常重要,Redis 的高可用簡單來說就是增加冗余副本,將一份數(shù)據(jù)保存在多個(gè)實(shí)例上;即使有一個(gè)實(shí)例宕機(jī),其他服務(wù)仍然可以對(duì)外提供服務(wù),不影響業(yè)務(wù)使用。
一. Redis 主從同步
Redis 提供了主從模式(一主多從)來提高 Redis 的可用性,主從庫之間采用的是讀寫分離:

讀操作:主從庫都能接收
寫操作:主庫能接收,執(zhí)行完后同步給從庫
主從同步原理
首次全量同步
主從第一次同步會(huì)經(jīng)歷三個(gè)步驟:
(1)主從庫建立連接,二者連接完成后開始同步。
(2)首次同步需要全量數(shù)據(jù),主庫會(huì) fork 出一個(gè)子進(jìn)程來生成 RDB 快照,接著將 RDB 文件發(fā)送給從庫(不會(huì)阻塞主線程),從庫收到后清空舊數(shù)據(jù),最后加載 RDB 文件完成全量數(shù)據(jù)同步。
(3)在主庫生成 RDB 后接收的命令會(huì)暫存到一塊內(nèi)存區(qū)域:replication buffer,當(dāng)從庫加載完 RDB 快照后,再將這塊暫存的數(shù)據(jù)發(fā)送給從庫執(zhí)行,最終完成首次主從同步。
為什么要單獨(dú)維護(hù)全量同步階段的增量數(shù)據(jù)呢?
單獨(dú)維護(hù)是為了保證命令執(zhí)行的順序性,這批增量數(shù)據(jù)需要等到 RDB 文件加載完后再發(fā)送給從庫,否則會(huì)因?yàn)橄群箜樞虿煌瑢?dǎo)致主從不一致。
當(dāng)完成首次同步后,主從之間維護(hù)一個(gè)長連接,后續(xù)寫命令通過這個(gè)長連接進(jìn)行同步。
長連接因?yàn)榫W(wǎng)絡(luò)問題斷開了期間的寫命令會(huì)丟嗎?
當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)導(dǎo)致長連接斷開,主庫也會(huì)將寫命令暫存到一塊環(huán)形的內(nèi)存區(qū)域,等待連接恢復(fù)后將暫存的寫命令發(fā)送給從庫,保證主從一致。
做主從復(fù)制的作用是?
數(shù)據(jù)冗余:主從復(fù)制實(shí)現(xiàn)了數(shù)據(jù)的熱備份;
高可用:當(dāng)主節(jié)點(diǎn)出現(xiàn)問題時(shí),可以由從節(jié)點(diǎn)提供服務(wù),實(shí)現(xiàn)快速的故障恢復(fù);
負(fù)載均衡:在主從的模式下,配合讀寫分離,可以大大提供 Redis 整體的吞吐量。
二. Redis 故障轉(zhuǎn)移
主從模式能做到數(shù)據(jù)備份,也能支持讀寫分離,但一旦主節(jié)點(diǎn)宕機(jī),需要人工介入切換主節(jié)點(diǎn)。
Redis 提供了哨兵機(jī)制保證 Master 出現(xiàn)故障時(shí)自動(dòng)進(jìn)行主從切換,也就是故障轉(zhuǎn)移。
哨兵機(jī)制
哨兵節(jié)點(diǎn)的作用分為三點(diǎn):監(jiān)控,選主,通知;一般哨兵會(huì)集群部署,原因是為了保證哨兵的高可用和防止下線誤判(下線誤判在下面分析)。
哨兵實(shí)現(xiàn)故障轉(zhuǎn)移原理
1. 哨兵監(jiān)控
Sentinel 節(jié)點(diǎn)會(huì)監(jiān)控 matser、slave 及其他 Sentinel 節(jié)點(diǎn)的狀態(tài)。這個(gè)是通過 Redis 自身的 pub/sub 機(jī)制實(shí)現(xiàn)的。Redis 的哨兵一共有三個(gè)定時(shí)監(jiān)控任務(wù),來完成節(jié)點(diǎn)的發(fā)現(xiàn)與監(jiān)控。
監(jiān)控主從拓?fù)湫畔?/span>:每隔 10 s,每個(gè) Sentinel 節(jié)點(diǎn)會(huì)向主從庫發(fā)送 info 命令,來獲取最新的拓?fù)浣Y(jié)構(gòu);
Sentinel 集群節(jié)點(diǎn)之間交換信息:每隔 2 s,每個(gè) Sentinel 節(jié)點(diǎn)會(huì)向 _sentinel_:hello 頻道上發(fā)送自身的信息,以及對(duì)主節(jié)點(diǎn)的判斷信息。這樣,Sentinel 節(jié)點(diǎn)之間就可以交換信息。
節(jié)點(diǎn)狀態(tài)監(jiān)控:每隔 1 s,每個(gè) Sentinel 節(jié)點(diǎn)會(huì)向 master、slave 及其他 Sentinel 節(jié)點(diǎn)發(fā)送 ping 命令做心跳檢測(服務(wù)端回復(fù) pong 代表節(jié)點(diǎn)正常),來判斷這些節(jié)點(diǎn)是否可達(dá)。

2. 主觀下線
Sentinel 每隔 1 s 會(huì)對(duì)數(shù)據(jù)節(jié)點(diǎn)發(fā)送 ping 命令做心跳檢測,當(dāng)節(jié)點(diǎn)超過 down-after-milliseconds 沒有進(jìn)行回復(fù),Sentinel 會(huì)對(duì)該節(jié)點(diǎn)做失敗判定,這個(gè)行為被稱作主觀下線。
主觀下線,顧名思義是主觀的,可能會(huì)誤判,假設(shè)主觀下線后就進(jìn)行主從切換,實(shí)際主庫并沒有發(fā)生故障,后續(xù)的選主和通知操作會(huì)帶來額外的開銷。
發(fā)生誤判的場景:網(wǎng)絡(luò)擁塞、節(jié)點(diǎn)發(fā)生短暫網(wǎng)絡(luò)分區(qū),或是節(jié)點(diǎn)壓力較大響應(yīng)超時(shí)。
3. 客觀下線
為了防止下線誤判,只有當(dāng)大多數(shù)的哨兵節(jié)點(diǎn)認(rèn)為 master 下線才算真正下線,這個(gè)行為叫做客觀下線。
客觀下線過程:
(1) 當(dāng)某個(gè) Sentinel 節(jié)點(diǎn)發(fā)生判斷主庫“主觀下線”后,會(huì)給其他哨兵實(shí)例發(fā)送 is-master-down-by-addr 命令,其他哨兵節(jié)點(diǎn)會(huì)根據(jù)自己和主庫的連接情況,做出 Y(贊同)或 N(反對(duì))的響應(yīng)。
(2) 當(dāng)哨兵獲取到了“客觀下線”所需的贊成票數(shù)后,就可以標(biāo)記主庫為“客觀下線”,這個(gè)所需要的票數(shù)由 quorum 配置項(xiàng)決定(例如,現(xiàn)在有 5 個(gè)哨兵,quorum 為 2,當(dāng)兩個(gè)哨兵判斷主服務(wù)器下線后則觸發(fā)故障轉(zhuǎn)移)。
4.Sentinel Leader 選舉
當(dāng)發(fā)生了客觀下線后,哨兵節(jié)點(diǎn)集群就會(huì)選出一個(gè) Leader 來進(jìn)行實(shí)際的故障轉(zhuǎn)移操作。Redis 使用 Raft 算法來實(shí)現(xiàn)哨兵領(lǐng)導(dǎo)者的選舉,大致過程如下:
(1)哨兵節(jié)點(diǎn)設(shè)置主服務(wù)器為“客觀下線”后,向其他哨兵節(jié)點(diǎn)發(fā)送命令,表明希望自己來執(zhí)行主從切換,其他哨兵節(jié)點(diǎn)會(huì)進(jìn)行投票。
(2)當(dāng)哨兵節(jié)點(diǎn)拿到半數(shù)以上的贊成票且票數(shù)大于等于哨兵配置文件中的 quorum 值就會(huì)成為 Leader。
Leader 選舉的投票邏輯很簡單:在這一輪投票中,如果沒有投過票就回復(fù)同意,如果投過票就回復(fù)拒絕。
(3)如果此過程沒有選出 Leader 則會(huì)等待故障超時(shí)間的 2 倍時(shí)長,然后進(jìn)入下一輪選舉。
什么情況會(huì)選不出 Leader?
哨兵集群能夠成功投票,很大程度上取決于正常的網(wǎng)絡(luò)傳輸。如果網(wǎng)絡(luò)壓力大或短暫阻塞就可能導(dǎo)致沒有哨兵節(jié)點(diǎn)拿到半數(shù)以上的票。而網(wǎng)絡(luò)問題一般都會(huì)持續(xù)一小段時(shí)間,所以在沒有選出 Leader 后會(huì)等待一段時(shí)間再進(jìn)入下一輪。
5. 故障轉(zhuǎn)移
選出哨兵的 Leader 后就會(huì)進(jìn)行故障轉(zhuǎn)移,也就是從 slave 中選出一個(gè)新 master 替換故障 master,主要有以下判斷標(biāo)準(zhǔn):
(1)跟 master 斷開鏈接的時(shí)長:如果一個(gè) slave 和 master 的斷開鏈接時(shí)長已經(jīng)超過 down-after-milliseconds 的 10 倍,那哨兵就會(huì)認(rèn)為該 slave 不適合被選為 master。
(2)slave 的優(yōu)先級(jí)配置:slave priority 參數(shù)越小,優(yōu)先級(jí)越高。
(3)主從復(fù)制進(jìn)度:當(dāng) 優(yōu)先級(jí) 相同時(shí),哪個(gè) slave 和 master 的數(shù)據(jù)越接近,優(yōu)先級(jí)越高。
(4)run id:如果 優(yōu)先級(jí)配置 和 主從復(fù)制進(jìn)度 都相同,則哪個(gè) slave 的 run id 越小,優(yōu)先級(jí)越高。
選出 master 后,對(duì)它執(zhí)行 slaveof no one 命令讓其成為主節(jié)點(diǎn),并對(duì)剩余 slave 節(jié)點(diǎn)發(fā)送命令讓他們成為新 master 的從節(jié)點(diǎn),最后和其他哨兵節(jié)點(diǎn)交換信息完成故障轉(zhuǎn)移。
主從切換過程中,是否能對(duì)外正常提供讀寫服務(wù)?
如果采用讀寫分離,還是可以正常處理讀請求,但是對(duì)于寫請求,服務(wù)端就無法處理了。如果需要應(yīng)對(duì)寫請求,業(yè)務(wù)系統(tǒng)中可以將寫緩存的操作改成異步或放到隊(duì)列處理。
腦裂問題
如果碰巧客觀下線也誤判會(huì)發(fā)生什么?
會(huì)發(fā)生腦裂。
腦裂就是在主從集群中同時(shí)有兩個(gè)主節(jié)點(diǎn),他們都能接收寫請求。而不同的客戶端會(huì)往不同的主節(jié)點(diǎn)上寫數(shù)據(jù),甚至導(dǎo)致數(shù)據(jù)丟失。
Redis 的腦裂一般發(fā)生在主從切換時(shí)原主庫假故障的場景下:
當(dāng)主庫因?yàn)橐恍┰驘o法處理哨兵節(jié)點(diǎn)的心跳檢測時(shí),就會(huì)被判定為“客觀下線”,接著就會(huì)進(jìn)行主從切換,但在主從切換完成之前,原主庫又恢復(fù)服務(wù),就又會(huì)處理寫請求,當(dāng)主從切換完成后通知客戶端之前就會(huì)有兩個(gè)主節(jié)點(diǎn),即發(fā)生腦裂。
Redis 的腦裂可能會(huì)造成數(shù)據(jù)丟失,根本原因是 Redis 內(nèi)部沒有通過共識(shí)算法來維護(hù)多個(gè)數(shù)據(jù)節(jié)點(diǎn)的強(qiáng)一致性,因?yàn)閺?qiáng)一致性的成本太大,而 Redis 主打性能,所以 Redis 放棄 C(一致性) 而選擇 A(可用性)。