日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Redis腦裂導致數據丟失的解決_Redis

作者:JavaEdge. ? 更新時間: 2023-03-20 編程語言

1 案例

主從集群有1個主庫、5個從庫和3個哨兵實例,突然發現客戶端發送的一些數據丟了,直接影響業務層數據可靠性。

最終排查發現是主從集群中的腦裂問題導致:主從集群中,同時有兩個主節點都能接收寫請求。

影響

客戶端不知道應往哪個主節點寫數據,導致不同客戶端往不同主節點寫數據。嚴重的,腦裂會進一步導致數據丟失。

2 腦裂原因

最初問題:在主從集群中,客戶端發送的數據丟失了。

2.1 為什么數據會丟失?

① 確認數據同步是否異常

在主從集群中發生數據丟失,最常見原因:主庫數據還沒同步到從庫,結果主庫故障,等從庫升級為主庫后,未同步數據丟了。

新寫入主庫的數據a=1、b=3,因為在主庫故障前未同步到從庫,失了。

這種數據丟失case,可直接對比主從庫的復制進度差值:

master_repl_offset - slave_repl_offset

若從庫的slave_repl_offset < 原主庫的master_repl_offset,則可認定數據丟失是由數據同步未完成導致。

部署主從集群時,也監測了:

  • 主庫的master_repl_offset
  • 從庫上的slave_repl_offset

但發現數據丟失后,檢查了新主庫升級前的slave_repl_offset,以及原主庫的master_repl_offset,一致,說明該升級為新主庫的從庫,在升級時已和原主庫的數據一致。

那為啥還會出現客戶端發的數據丟失?

所有數據操作都是從客戶端發給Redis實例,是否可從客戶端操作日志發現問題?

② 排查客戶端的操作日志,發現腦裂現象

發現主從切換后的一段時間,有個客戶端仍在和原主庫通信,并沒有和升級的新主庫交互。

相當于主從集群中同時有兩個主庫。據此,想到主從集群故障的腦裂。但不同客戶端給兩個主庫發送數據寫操作,應只會導致新數據會分布在不同主庫,而不會造成數據丟失。

思路又斷了。“從原理出發是追本溯源的好方法”。腦裂是發生在主從切換過程,猜測是漏掉了主從集群切換過程中的某環節,所以,聚焦主從切換的執行過程。

③ 發現是原主庫假故障導致的腦裂

我們采用哨兵機制進行主從切換的,主從切換發生時,一定有超過預設數量(quorum配置項)的哨兵實例和主庫的心跳都超時,才會把主庫判斷為客觀下線,然后,哨兵開始執行切換操作。

哨兵切換完成后,客戶端會和新主庫通信,發送請求操作。

但切換過程中,既然客戶端仍和原主庫通信,說明原主庫并未真故障(如主庫進程掛掉)。懷疑主庫某些原因無法處理請求,也沒響應哨兵的心跳,被哨兵錯判客觀下線。
被判下線后,原主庫又重新開始處理請求了,而此時,哨兵還沒完成主從切換,客戶端仍可和原主庫通信,客戶端發送的寫操作就會在原主庫寫數據。

為驗證原主庫只是“假故障”,查看原主庫服務器的資源使用監控。原主庫所在機器有段時間CPU利用率飆升,因某程序把機器CPU用滿,導致Redis主庫無法響應心跳,這期間,哨兵就把主庫判為客觀下線,開始主從切換。這程序很快恢復正常,CPU使用率也下來了。原主庫又繼續正常服務請求。

正因原主庫未真故障,在客戶端操作日志中就看到和原主庫通信記錄。從庫被升級為新主庫后,主從集群里就有兩個主庫,這就是案例腦裂原因。

3 為何腦裂會導致數據丟失?

主從切換后,從庫一旦升級為新主,哨兵就會讓原主庫執行slave of命令,和新主重新進行全量同步。

在全量同步執行最后階段,原主需清空本地數據,加載新主發送的RDB文件,原主在主從切換期間保存的新寫數據就丟了。

主從切換過程中,若原主只是“假故障”,會觸發哨兵啟動主從切換,一旦等它從假故障恢復,又開始處理請求,這就和新主共存,導致腦裂。

等哨兵讓原主和新主做全量同步后,原主在切換期間保存的數據就丟了。

4 腦裂應急方案

主從集群中的數據丟失是因為發生腦裂,必須有應對腦裂方案。

問題出在原主假故障后,仍能接收請求,因此,可在主從集群機制的配置項中查找是否有限制主庫接收請求的設置。Redis提供如下配置項限制主庫的請求處理:

min-replicas-to-write
主庫能進行數據同步的最少從庫數量

min-replicas-max-lag
主從庫間進行數據復制時,從庫給主庫發送ACK消息的最大延遲(單位s)

分別設置閾值N和T,倆配置項組合后的要求是:

  • 主庫連接的從庫中至少有N個從庫
  • 和主庫進行數據復制時的ACK消息延遲不能超過T秒

否則,主庫就不會再接收客戶端請求。

即使原主假故障,假故障期間也無法響應哨兵心跳,也不能和從庫進行同步,自然就無法和從庫進行ACK確認。這倆配置項組合要求就無法得到滿足,原主庫就會被限制接收客戶端請求,客戶端也就不能在原主庫中寫新數據。

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

假設

  • min-replicas-to-write=1
  • min-replicas-max-lag設為12s
  • 哨兵的down-after-milliseconds設為10s

主庫因某原因卡住15s,導致哨兵判斷主庫客觀下線,開始進行主從切換。
同時,因原主庫卡住15s,沒有一個從庫能和原主庫在12s內進行數據復制,原主庫也無法接收客戶端請求。
主從切換完成后,也只有新主庫能接收請求,不會發生腦裂,也就不會發生數據丟失。

5 總結

腦裂,主從集群中,同時有兩個主能接收寫請求。Redis主從切換過程中,若發生腦裂,客戶端數據就會寫入原主,若原主被降為從庫,這些新寫入數據就丟了。

腦裂主要是因為原主庫發生了假故障,假故障的原因:

  • 和主庫部署在同一臺服務器上的其他程序臨時占用了大量資源(例如CPU資源),導致主庫資源使用受限,短時間內無法響應心跳。其它程序不再使用資源時,主庫又恢復正常
  • 主庫自身遇到阻塞,如處理bigkey或是發生內存swap(你可以復習下第19講中總結的導致實例阻塞的原因),短時間內無法響應心跳,等主庫阻塞解除后,又恢復正常的請求處理了。

應對腦裂,你可以在主從集群部署時,通過合理地配置參數min-slaves-to-write和min-slaves-max-lag,來預防腦裂。

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

6 最佳實踐

假設從庫有K個,可將:

  • min-slaves-to-write設置為K/2+1(如果K等于1,就設為1)
  • min-slaves-max-lag設置為十幾秒(例如10~20s)

這個配置下,如果有一半以上的從庫和主庫進行的ACK消息延遲超過十幾s,我們就禁止主庫接收客戶端寫請求。

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

假設:

  • min-slaves-to-write 置 1
  • min-slaves-max-lag 設置為 15s,哨兵的
  • down-after-milliseconds 設置為 10s
    哨兵主從切換需要 5s,主庫因為某些原因卡住12s,此時,還會發生腦裂嗎?主從切換完成后,數據會丟失嗎?

主庫卡住 12s,達到哨兵設定的切換閾值,所以哨兵會觸發主從切換。但哨兵切換時間5s,即哨兵還未切換完成,主庫就會從阻塞狀態中恢復回來,且沒有觸發 min-slaves-max-lag 閾值,所以主庫在哨兵切換剩下的 3s 內,依舊可以接收客戶端的寫操作,如果這些寫操作還未同步到從庫,哨兵就把從庫提升為主庫了,那么此時也會出現腦裂的情況,之后舊主庫降級為從庫,重新同步新主庫的數據,新主庫也會發生數據丟失。

即使 Redis 配置了 min-slaves-to-write 和 min-slaves-max-lag,當腦裂發生時,還是無法嚴格保證數據不丟失,只是盡量減少數據的丟失。

這種情況下,新主庫之所以會發生數據丟失,是因為舊主庫從阻塞中恢復過來后,收到的寫請求還沒同步到從庫,從庫就被哨兵提升為主庫了。如果哨兵在提升從庫為新主庫前,主庫及時把數據同步到從庫了,那么從庫提升為主庫后,也不會發生數據丟失。但這種臨界點的情況還是有發生的可能性,因為 Redis 本身不保證主從同步的強一致。

還有一種腦裂情況,就是網絡分區:主庫和客戶端、哨兵和從庫被分割成了 2 個網絡,主庫和客戶端處在一個網絡中,從庫和哨兵在另一個網絡中,此時哨兵也會發起主從切換,出現 2 個主庫的情況,而且客戶端依舊可以向舊主庫寫入數據。等網絡恢復后,主庫降級為從庫,新主庫丟失了這期間寫操作的數據。

腦裂本質是,Redis 主從集群內部沒有通過共識算法,來維護多個節點數據的強一致性。不像 Zookeeper,每次寫請求必須大多數節點寫成功后才認為成功。當腦裂發生時,Zookeeper 主節點被孤立,此時無法寫入大多數節點,寫請求會直接返回失敗,因此它可以保證集群數據的一致性。

對于min-slaves-to-write,如果只有 1 個從庫,當把 min-slaves-to-write 設置為 1 時,在運維時需要小心一些,當日常對從庫做維護時,例如更換從庫的實例,需要先添加新的從庫,再移除舊的從庫才可以,或者使用 config set 修改 min-slaves-to-write 為 0 再做操作,否則會導致主庫拒絕寫,影響到業務。

原文鏈接:https://blog.csdn.net/qq_33589510/article/details/121120876

欄目分類
最近更新