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

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

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