在實際的項目中,服務高可用非常重要,如,當Redis作為緩存服務使用時, 緩解數據庫的壓力,提高數據的訪問速度,提高網站的性能 ,但如果使用Redis是單機模式運行 ,只要一個服務器宕機就不可以提供服務,這樣會可能造成服務效率低下,甚至出現其相對應的服務應用不可用。
因此為了實現高可用,Redis提供了哪些高可用方案?
Redis主從復制
Redis持久化
哨兵集群
...
Redis基于一個Master主節點多Slave從節點的模式和Redis持久化機制,將一份數據保持在多個實例中實現增加副本冗余量,又使用哨兵機制實現主備切換, 在master故障時,自動檢測,將某個slave切換為master,最終實現Redis高可用 。
Redis主從復制
Redis主從復制,主從庫模式一個Master主節點多Slave從節點的模式,將一份數據保存在多Slave個實例中,增加副本冗余量,當某些出現宕機后,Redis服務還可以使用。
但是這會存在數據不一致問題,那redis的副本集是如何數據一致性?
Redis為了保證數據副本的一致,主從庫之間采用讀寫分離的方式:
讀操作:主庫、從庫都可以執行處理;
寫操作:先在主庫執行,再由主庫將寫操作同步給從庫。
使用讀寫分離方式的好處,可以避免當主從庫都可以處理寫操作時,主從庫處理寫操作加鎖等一系列巨額的開銷。
采用讀寫分離方式,寫操作只會在主庫中進行后同步到從庫中,那主從庫是如何同步數據的呢?
主從庫是同步數據方式有兩種:
全量同步:通常是主從服務器剛剛連接的時候,會先進行全量同步
增量同步:一般在全同步結束后,進行增量同步,比如主從庫間網絡斷,再進行數據同步。
全量同步
主從庫間第一次全量同步,具體分成三個階段:
當一個從庫啟動時,從庫給主庫發送psync命令進行數據同步(psync命令包含:主庫的runID和復制進度offset兩個參數),
當主庫接收到psync 命令后將會保存RDB 文件并發送給從庫,發送期間會使用緩存區(replication buffer)記錄后續的所有寫操作 ,從庫收到數據后,會先清空當前數據庫,然后加載從主庫獲取的RDB 文件,
當主庫完成 RDB 文件發送后,也會把將保存發送RDB文件期間寫操作的replication buffer發給從庫,從庫再重新執行這些操作。這樣一來,主從庫就實現同步了。
另外,為了分擔主庫生成 RDB 文件和傳輸 RDB 文件壓力,提高效率,可以使用“主 - 從 - 從”模式將主庫生成 RDB 和傳輸 RDB 的壓力,以級聯的方式分散到從庫上。
增量同步
增量同步,基于環形緩沖區repl_backlog_buffer緩存區實現。
在環形緩沖區,主庫會記錄自己寫到的位置master_repl_offset,從庫則會記錄自己已經讀到的位置slave_repl_offset, 主庫并通過master_repl_offset和slave_repl_offset的差值的數據同步到從庫。
主從庫間網絡斷了, 主從庫會采用增量復制的方式繼續同步,主庫會把斷連期間收到的寫操作命令,寫入replication buffer,同時也會把這些操作命令也寫入repl_backlog_buffer這個緩沖區,然后主庫并通過master_repl_offset和slave_repl_offset的差值數據同步到從庫。
因為repl_backlog_buffer是一個環形緩沖區,當在緩沖區寫滿后,主庫會繼續寫入,此時,會出現什么情況呢?
覆蓋掉之前寫入的操作。如果從庫的讀取速度比較慢,就有可能導致從庫還未讀取的操作被主庫新寫的操作覆蓋了,這會導致主從庫間的數據不一致。因此需要關注repl_backlog_size參數,調整合適的緩沖空間大小,避免數據覆蓋,主從數據不一致。
主從復制,除了會出現數據不一致外,甚至可能出現主庫宕機的情況,Redis會有主從自主切換機制,那如何實現的呢?
Redis哨兵機制
當主庫掛了,redis寫操作和數據同步無法進行,為了避免這樣情況,可以在主庫掛了后重新在從庫中選舉出一個新主庫,并通知到客戶端,redis提供了哨兵機制,哨兵為運行在特殊模式下的 Redis 進程。
Redis會有主從自主切換機制,那如何實現的呢?
哨兵機制是實現主從庫自動切換的關鍵機制,其主要分為三個階段:
監控:哨兵進程會周期性地給所有的主從庫發送 PING 命令,檢測它們是否仍然在線運行。
選主(選擇主庫):主庫掛了以后,哨兵基于一定規則評分選選舉出一個從庫實例新的主庫 。
通知 : 哨兵會將新主庫的信息發送給其他從庫,讓它們和新主庫建立連接,并進行數據復制。同時,哨兵會把新主庫的信息廣播通知給客戶端,讓它們把請求操作發到新主庫上。
其中,在監控中如何判斷主庫是否處于下線狀態?
哨兵對主庫的下線判斷分為:
主觀下線:哨兵進程會使用 PING 命令檢測它自己和主、從庫的網絡連接情況,用來判斷實例的狀態,如果單哨兵發現主庫或從庫對 PING 命令的響應超時了,那么,哨兵就會先把它標記為“主觀下線”
客觀下線:在哨兵集群中,基于少數服從多數,多數實例都判定主庫已“主觀下線”,則認為主庫“客觀下線”。
為什么會有這兩種"主觀下線"和“客觀下線”的下線狀態呢?
由于單機哨兵很容易產生誤判,誤判后主從切換會產生一系列的額外開銷,為了減少誤判,避免這些不必要的開銷,采用哨兵集群,引入多個哨兵實例一起來判斷,就可以避免單個哨兵因為自身網絡狀況不好,而誤判主庫下線的情況,
基于少數服從多數原則, 當有 N 個哨兵實例時,最好要有 N/2 + 1 個實例判斷主庫為“主觀下線”,才能最終判定主庫為“客觀下線” (可以自定義設置闕值)。
那么哨兵之間是如何互相通信的呢?
哨兵集群中哨兵實例之間可以相互發現,基于Redis提供的發布 / 訂閱機制(pub/sub機制),
哨兵可以在主庫中發布/訂閱消息,在主庫上有一個名為“\__sentinel__:hello”的頻道,不同哨兵就是通過它來相互發現,實現互相通信的,而且只有訂閱了同一個頻道的應用,才能通過發布的消息進行信息交換。
哨兵 1連接相關信息(IP端口)發布到“\__sentinel__:hello”頻道上,哨兵 2 和 3 訂閱了該頻道。
哨兵 2 和 3 就可以從這個頻道直接獲取哨兵 1連接信息,以這樣的方式哨兵集群就形成了,實現各個哨兵互相通信。
哨兵集群中各個實現通信后,就可以判定主庫是否已客觀下線。
在已判定主庫已下線后,又如何選舉出新的主庫?
新主庫選舉按照一定條件篩選出的符合條件的從庫,并按照一定規則對其進行打分,最高分者為新主庫。
通常一定條件包括:
從庫的當前在線狀態,
判斷它之前的網絡連接狀態,通過down-after-milliseconds * num(斷開連接次數),當斷開連接次數超過閾值,不適合為新主庫。
一定規則包括:
從庫優先級 , 通過slave-priority配置項,給不同的從庫設置不同優先級,優先級最高的從庫得分高
從庫復制進度,和舊主庫同步程度最接近的從庫得分高,通過repl_backlog_buffer緩沖區記錄主庫master_repl_offset和從庫slave_repl_offset相差最小高分
從庫 ID 號 , ID 號小的從庫得分高。
全都都基于在只有在一定規則中的某一輪評出最高分從庫就選舉結束,哨兵發起主從切換。
leader哨兵
選舉完新的主庫后,不能每個哨兵都發起主從切換,需要選舉成leader哨兵,那如何選舉leader哨兵執行主從切換?
選舉leader哨兵,也是基于少數服從多數原則"投票仲裁"選舉出來,
當任何一個從庫判定主庫“主觀下線”后,發送命令s-master-down-by-addr命令發送想要成為Leader的信號,
其他哨兵根據與主機連接情況作出相對的響應,贊成票Y,反對票N,而且如果有多個哨兵發起請求,每個哨兵的贊成票只能投給其中一個,其他只能為反對票。
想要成為Leader 的哨兵,要滿足兩個條件:
第一,獲得半數以上的贊成票;
第二,獲得的票數同時還需要大于等于哨兵配置文件中的quorum值。
選舉完leader哨兵并新主庫切換完畢之后,那么leader哨兵怎么通知客戶端?
還是基于哨兵自身的 pub/sub 功能,實現了客戶端和哨兵之間的事件通知,客戶端訂閱哨兵自身消息頻道 ,而且哨兵提供的消息訂閱頻道有很多,不同頻道包含了:
事件相關頻道
主庫下線事件+sdown(實例進入“主觀下線”狀態)
-sdown(實例退出“主觀下線”狀態)
+odown(實例進入“客觀下線”狀態)
-odown(實例退出“客觀下線”狀態)
新主庫切換+ switch-master(主庫地址發生變化)
其中,當客戶端從哨兵訂閱消息主從庫切換,當主庫切換后,端戶端就會接收到新主庫的連接信息:
switch-master 復制代碼
在這樣的方式哨兵就可以通知客戶端切換了新庫。
基于上述的機制和原理Redis實現了高可用,但也會帶了一些潛在的風險,比如數據缺失。
數據問題
Redis實現高可用,但實現期間可能產出一些風險:
主備切換的過程, 異步復制導致的數據丟失
腦裂導致的數據丟失
主備切換的過程,異步復制導致數據不一致
數據丟失-主從異步復制
因為master將數據復制給slave是異步實現的,在復制過程中,這可能存在master有部分數據還沒復制到slave,master就宕機了,此時這些部分數據就丟失了。
總結:主庫的數據還沒有同步到從庫,結果主庫發生了故障,未同步的數據就丟失了。
數據丟失-腦裂
何為腦裂?當一個集群中的 master 恰好網絡故障,導致與 sentinal 通信不上了,sentinal會認為master下線,且sentinal選舉出一個slave 作為新的 master,此時就存在兩個 master了。
此時,可能存在client還沒來得及切換到新的master,還繼續寫向舊master的數據,當master再次恢復的時候,會被作為一個slave掛到新的master 上去,自己的數據將會清空,重新從新的master 復制數據,這樣就會導致數據缺失。
總結:主庫的數據還沒有同步到從庫,結果主庫發生了故障,等從庫升級為主庫后,未同步的數據就丟失了。
數據丟失解決方案
數據丟失可以通過合理地配置參數 min-slaves-to-write 和 min-slaves-max-lag 解決,比如
min-slaves-to-write1
min-slaves-max-lag10
如上兩個配置:要求至少有 1 個 slave,數據復制和同步的延遲不能超過 10 秒,如果超過 1 個 slave,數據復制和同步的延遲都超過了 10 秒鐘,那么這個時候,master 就不會再接收任何請求了。
數據不一致
在主從異步復制過程,當從庫因為網絡延遲或執行復雜度高命令阻塞導致滯后執行同步命令,這樣就會導致數據不一致
解決方案: 可以開發一個外部程序來監控主從庫間的復制進度(master_repl_offset和slave_repl_offset),通過監控master_repl_offset與slave_repl_offset差值得知復制進度,當復制進度不符合預期設置的Client不再從該從庫讀取數據。
總結
Redis使用主從復制、持久化、哨兵機制等實現高可用,需要理解其實現過程,也要明白其帶了風險以及解決方案,才能在實際項目更好優化,提升系統的可靠性、穩定性。