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

Active_Sentinel發表於2022-03-23

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 訊息延遲超過十幾秒,我們就禁止主庫接收客戶端寫請求。

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

相關文章