Redis運維實戰之集群中的腦裂

1.對於分佈式Redis主從集群來說,什麼是腦裂?

所謂的腦裂,就是指在主從集群中,同時有兩個主節點,它們都能接收寫請求。而腦裂最直接的影響,就是客戶端不知道應該往哪個主節點寫入數據,結果就是不同的客戶端會往不同的主節點上寫入數據。而且,嚴重的話,腦裂會進一步導致數據丟失。

2.為什麼會發生腦裂?

2.1 網絡問題:導致Redis Master節點跟Redis Slave節點和Sentinel集群處於不同的網絡分區,此時因為Sentinel集群無法感知到master的存在,所以將Slave節點提升為Master節點。此時存在兩個不同的Master節點,就像一個大腦分裂成了兩個。

2.2 主機資源問題:redis Master節點所在的服務器上的其他程序臨時佔用了大量資源(例如 CPU 資源),導致主庫資源使用受限,短時間內無法響應心跳,於是Sentinel集群重新選舉了新的Master,當其它程序不再使用資源時,舊Master節點又恢復正常,同一集群下出現兩個Master;

2.3 Redis 主節點阻塞:主庫自身遇到了阻塞的情況,例如,處理 bigkey 或是發生內存 swap,短時間內無法響應心跳,還是會觸發Sentinel機制,等主庫阻塞解除後,又恢復正常的請求處理了。

3.腦裂的影響?

當原主庫並沒有真的發生故障(例如主庫進程掛掉),而是由於某些原因無法處理請求,也沒有響應哨兵的心跳,才被哨兵錯誤地判斷為客觀下線的。結果,在被判斷下線之後,原主庫又重新開始處理請求了,而此時,哨兵還沒有完成主從切換,客戶端仍然可以和原主庫通信;

如果客戶端還在基於原來的主庫繼續寫入數據,那麼新的主庫將無法同步這些數據,當網絡問題解決之後,哨兵就會讓原主庫執行 slave of 命令,和新主庫重新進行全量同步。而在全量同步執行的最後階段,原主庫需要清空本地的數據,加載新主庫發送的 RDB 文件,這樣一來,原主庫在主從切換期間保存的新寫數據就丟失了。

4.如何避免腦裂現象?

主從集群中的數據丟失事件,歸根結底是因為發生了腦裂。所以,我們必須要找到應對腦裂問題的策略。

既然問題是出在原主庫發生假故障後仍然能接收請求上,我們就開始在主從集群機制的配置項中查找是否有限制主庫接收請求的設置。

通過查找,可以發現,Redis 已經提供了兩個配置項來限制主庫的請求處理,分別是 min-slaves-to-write 和 min-slaves-max-lag。

  • min-slaves-to-write:這個配置項設置了主庫能進行數據同步的最少從庫數量,即至少要保證N個從庫能進行數據同步;
  • min-slaves-max-lag:這個配置項設置了主從庫間進行數據複製時,從庫給主庫發送 ACK 消息的最大延遲(以秒為單位)。

有了這兩個配置項後,我們就可以輕鬆地應對腦裂問題了。具體咋做呢?

我們可以把 min-slaves-to-write 和 min-slaves-max-lag 這兩個配置項搭配起來使用,分別給它們設置一定的閾值,假設為 N 和 T。這兩個配置項組合後的要求是,主庫連接的從庫中至少有 N 個從庫,和主庫進行數據複製時的 ACK 消息延遲不能超過 T 秒,否則,主庫就不會再接收客戶端的請求了。

即使原主庫是假故障,它在假故障期間也無法響應哨兵心跳,也不能和從庫進行同步,自然也就無法和從庫進行 ACK 確認了。這樣一來,min-slaves-to-write 和 min-slaves-max-lag 的組合要求就無法得到滿足,原主庫就會被限制接收客戶端請求,客戶端也就不能在原主庫中寫入新數據了。

等到新主庫上線時,就只有新主庫能接收和處理客戶端請求,此時,新寫的數據會被直接寫到新主庫中。而原主庫會被哨兵降為從庫,即使它的數據被清空了,也不會有新數據丟失。

配置示例:

假設我們將 min-slaves-to-write 設置為 1,把 min-slaves-max-lag 設置為 12s,把哨兵的 down-after-milliseconds 設置為 10s,主庫因為某些原因卡住了 15s,導致哨兵判斷主庫客觀下線,開始進行主從切換。同時,因為原主庫卡住了 15s,沒有一個從庫能和原主庫在 12s 內進行數據複製,原主庫也無法接收客戶端請求了。這樣一來,主從切換完成後,也只有新主庫能接收請求,不會發生腦裂,也就不會發生數據丟失的問題了。

運維建議:

在實際應用中,可能會因為網絡暫時阻塞導致從庫暫時和主庫的 ACK 消息超時。在這種情況下,並不是主庫假故障,我們也不用禁止主庫接收請求。

所以,我給你的建議是,假設從庫有  N 個,可以將 min-slaves-to-write 設置為 N/2+1(如果 N 等於 1,就設為 1),將 min-slaves-max-lag 設置為十幾秒(例如 10~20s),在這個配置下,如果有一半以上的從庫和主庫進行的 ACK 消息延遲超過十幾秒,我們就禁止主庫接收客戶端寫請求。

這樣一來,我們可以避免腦裂帶來數據丟失的情況,而且,也不會因為只有少數幾個從庫因為網絡阻塞連不上主庫,就禁止主庫接收請求,增加了系統的健壯性。