11 | 脑裂: 一次奇怪的数据丢失
1. 前言
腦裂就是指在主從集群中,同時有兩個主節點,它們都能接收寫請求。而腦裂最直接的影響,就是客戶端不知道應該往哪個主節點寫入數據,結果就是不同的客戶端會往不同的主節點上寫入數據。而且,嚴重的話,腦裂會進一步導致數據丟失。
2. 為什么會發生腦裂?
第一步:確認是不是數據同步出現了問題
??在主從集群中發生數據丟失,最常見的原因就主庫的數據還沒有同步到從庫,結果主庫發生了故障,等從庫升級為主庫后,未同步的數據就丟失了。
??如果是這種情況的數據丟失,我們可以通過比對主從庫上的復制進度差值來進行判斷,也就是計算 master_repl_offset 和 slave_repl_offset 的差值。如果從庫上的slave_repl_offset 小于原主庫的 master_repl_offset,那么,我們就可以認定數據丟失是由數據同步未完成導致的。
第二步:排查客戶端的操作日志,發現腦裂現象
??在排查客戶端的操作日志時,我們發現,在主從切換后的一段時間內,有一個客戶端仍然在和原主庫通信,并沒有和升級的新主庫進行交互。這就相當于主從集群中同時有了兩個主庫。根據這個跡象,我們就想到了在分布式主從集群發生故障時會出現的一個問題:腦裂。
??但是,不同客戶端給兩個主庫發送數據寫操作,按道理來說,只會導致新數據會分布在不同的主庫上,并不會造成數據丟失。那么,為什么我們的數據仍然丟失了呢?到這里,我們的排查思路又一次中斷了。
??不過,在分析問題時,我們一直認為“從原理出發是追本溯源的好方法”。腦裂是發生在主從切換的過程中,我們猜測,肯定是漏掉了主從集群切換過程中的某個環節,所以,我們把研究的焦點投向了主從切換的執行過程。
第三步:發現是原主庫假故障導致的腦裂
??我們是采用哨兵機制進行主從切換的,當主從切換發生時,一定是有超過預設數量(quorum 配置項)的哨兵實例和主庫的心跳都超時了,才會把主庫判斷為客觀下線,然后,哨兵開始執行切換操作。哨兵切換完成后,客戶端會和新主庫進行通信,發送請求操作。
??但是,在切換過程中,既然客戶端仍然和原主庫通信,這就表明,原主庫并沒有真的發生故障(例如主庫進程掛掉)。我們猜測,主庫是由于某些原因無法處理請求,也沒有響應哨兵的心跳,才被哨兵錯誤地判斷為客觀下線的。結果,在被判斷下線之后,原主庫又重新開始處理請求了,而此時,哨兵還沒有完成主從切換,客戶端仍然可以和原主庫通信,客戶端發送的寫操作就會在原主庫上寫入數據了。
3. 為什么腦裂會導致數據丟失
??主從切換后,從庫一旦升級為新主庫,哨兵就會讓原主庫執行 slave of 命令,和新主庫重新進行全量同步。而在全量同步執行的最后階段,原主庫需要清空本地的數據,加載新主庫發送的 RDB 文件,這樣一來,原主庫在主從切換期間保存的新寫數據就丟失了。
??在主從切換的過程中,如果原主庫只是“假故障”,它會觸發哨兵啟動主從切換,一旦等它從假故障中恢復后,又開始處理請求,這樣一來,就會和新主庫同時存在,形成腦裂。等到哨兵讓原主庫和新主庫做全量同步后,原主庫在切換期間保存的數據就丟失了。
4. 如何應對腦裂問題
??既然問題是出在原主庫發生假故障后仍然能接收請求上,我們就開始在主從集群機制的配置項中查找是否有限制主庫接收請求的設置。Redis 已經提供了兩個配置項來限制主庫的請求處理,分別是 min-slaves-to-write 和 min-slaves-max-lag。
- min-slaves-to-write:這個配置項設置了主庫能進行數據同步的最少從庫數量;
- min-slaves-max-lag:這個配置項設置了主從庫間進行數據復制時,從庫給主庫發送ACK 消息的最大延遲(以秒為單位)。
??我們可以把 min-slaves-to-write 和 min-slaves-max-lag 這兩個配置項搭配起來使用,分別給它們設置一定的閾值,假設為 N 和 T。這兩個配置項組合后的要求是,主庫連接的從庫中至少有 N 個從庫,和主庫進行數據復制時的 ACK 消息延遲不能超過 T 秒,否則,主庫就不會再接收客戶端的請求了。
??等到新主庫上線時,就只有新主庫能接收和處理客戶端請求,此時,新寫的數據會被直接寫到新主庫中。而原主庫會被哨兵降為從庫,即使它的數據被清空了,也不會有新數據丟失。
??假設我們將 min-slaves-to-write 設置為 1,把 min-slaves-max-lag 設置為 12s,把哨兵的 down-after-milliseconds 設置為 10s,主庫因為某些原因卡住了 15s,導致哨兵判斷主庫客觀下線,開始進行主從切換。同時,因為原主庫卡住了 15s,沒有一個從庫能和原主庫在 12s 內進行數據復制,原主庫也無法接收客戶端請求了。這樣一來,主從切換完成后,也只有新主庫能接收請求,不會發生腦裂,也就不會發生數據丟失的問題了。
小結
??這節課,我們學習了主從切換時可能遇到的腦裂問題。腦裂是指在主從集群中,同時有兩個主庫都能接收寫請求。在 Redis 的主從切換過程中,如果發生了腦裂,客戶端數據就會寫入到原主庫,如果原主庫被降為從庫,這些新寫入的數據就丟失了。
??為了應對腦裂,你可以在主從集群部署時,
通過合理地配置參數 min-slaves-to-write 和min-slaves-max-lag,來預防腦裂的發生。
??在實際應用中,可能會因為網絡暫時擁塞導致從庫暫時和主庫的 ACK 消息超時。在這種情況下,并不是主庫假故障,我們也不用禁止主庫接收請求。
??所以,我給你的建議是,假設從庫有 K 個,可以將 min-slaves-to-write 設置為K/2+1(如果 K 等于 1,就設為 1),將 min-slaves-max-lag 設置為十幾秒(例如 10~20s),在這個配置下,如果有一半以上的從庫和主庫進行的 ACK 消息延遲超過十幾秒,我們就禁止主庫接收客戶端寫請求。
總結
以上是生活随笔為你收集整理的11 | 脑裂: 一次奇怪的数据丢失的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 04 | 内存快照:宕机后, Redis
- 下一篇: 01 | 基础架构:一条 SQL 查询语