天天看點

記一次redis主從切換導緻的資料丢失與陷入隻讀狀态故障

作者:王路飛學Java

背景

最近一組業務redis資料不斷增長需要擴容記憶體,而擴容記憶體則需要重新開機雲主機,在按計劃擴容更新執行主從切換時意外發生了資料丢失與master進入隻讀狀态的故障,這裡記錄分享一下。

業務redis高可用架構

該組業務redis使用的是一主一從,通過sentinel叢集實作故障時的自動主從切換,這套架構已經平穩運作數年,經曆住了多次實戰的考驗。

高可用架構大體如下圖所示:

記一次redis主從切換導緻的資料丢失與陷入隻讀狀态故障

簡單說一下sentinel實作高可用的原理:

叢集的多個(2n+1,N>1)哨兵會定期輪詢redis的所有master/slave節點,如果sentinel叢集中超過一半的哨兵判定redis某個節點已主觀下線,就會将其判定為客觀下線進行相應處理:

  1. 如果下線節點是master,標明一個正常work的slave将其標明為新的master節點。
  2. 如果下線節點是slave,将其從slave節點中移除。

如果已經被客觀下線的節點恢複了正常,sentinel中超過一半哨兵确認後則将其加回可用的slave節點。

所有需要讀寫redis的server并不需要直接寫死redis 主從配置,而是通過通路sentinel擷取目前redis的主從可用狀态,具體實作方式可以定時查詢sentinel詢問更新,也可以通過訂閱機制讓sentinel在主從變動時主動通知訂閱方更新。

sentinel實作高可用的詳細原理這裡不做過多贅述,有興趣的小夥伴可以移步參考文獻中的相關資料。

具體記憶體擴容流程

sentinel可以在檢測到故障時自動切換redis主從,也可以主動執行sentinel failover mastername 指令實作手動切換主從,是以這次的記憶體擴容重新開機流程設計如下(A代表初始master所在雲主機,B代表初始slave所在雲主機):

  1. 更新主機B記憶體配置,重新開機主機B
  2. 檢查B重新開機後其上的redis slave是否重新同步master資料完成,包括:

    2.1 檢視slave redis log是否異常,無異常pass

    2.2 使用info keyspace指令check master、slave 各db key數量是否一緻,無異常pass

    2.3 在master寫入一個測試key,在slave上check是否同步成功

    2.4 觀察依賴server log是否有異常

  3. 使用sentinel failover mastername指令手動主從切換,主機A變成新slave,主機B變成新master,根據以前手動切換的經驗走到這一步基本上就穩了--因為這裡本質上和一次普通主從切換已經沒有差別了。
  4. 更新主機A記憶體配置,重新開機主機A,執行以下check:

    4.1 檢視新slave redis log是否異常

    4.2 使用info keyspace指令check 新master、新slave 各db key數量是否一緻,無異常pass

    4.3 在新master寫入測試key,在新slave上check是否同步成功

    4.4 觀察依賴server log是否有異常

至此,若以上步驟都正常通過,一個完美的redis記憶體更新工作就完成了。

主從切換後資料丢失

結果正是沒有想過可能會出問題的步驟3反而出現了問題,直接導緻了主從切換後丢掉了部分資料,并且新master進入隻讀狀态将近十分鐘。

當時的情況是這樣的:

在執行完步驟3後,check 新slave redis log無異常,正在考慮觀察一會兒後執行主機A的更新重新開機操作,api的分鐘級别異常監控觸發了一小波redis相關報警。第一反應在新master與新slave上執行了info keyspace檢視key數量是否已經不一緻,結果發現master/slave的key數量是一緻的--但是再仔細一看:和切換前的key總數百萬級相比切換後key總數降到了十萬級--大部分key資料被丢失了。

檢視新master、新slave log都沒有發現明顯log可以解釋為什麼主從切換後會丢失一大半資料這一現象,這時小夥伴第一次提到了是不是記憶體不夠了,當時自己略一思考馬上回複到:新master剛更新了記憶體,不可能内容擴大後反而記憶體不足的,是以應該不是這個問題。

n分鐘後...

小夥伴再一次提出了是不是maxmemory問題,這一下子點中了關鍵點,馬上想到主機B更新了記憶體是不會有系統層面記憶體不足的問題,但是redis的記憶體使用實際上還會受到maxmemory參數限制,馬上在新master上執行config get maxmemory, 隻有3GB,而更新前資料實際使用記憶體超過了6GB!

立刻調大了新master的maxmemory參數,redis很快恢複了可讀寫正常狀态,一大波redis隻讀引發的告警通知開始快速下降。

原因定位

緊張又刺激的故障處理就這麼過去了,在優先處理完丢失key資料恢複工作之後,開始回顧整理故障的詳細原因,總共有如下幾個疑問:

  1. 明确記得上個月給主機A、B上的redis都通過config set maxmemory設定為了7GB,為什麼出現問題時查詢B上redis 的maxmemory配置卻變成了3GB?
  2. 如果主機B的maxmemory是3GB,其作為slave時為什麼從master同步超過6GB的資料時不會有問題?--在主從切換前無論是檢視info keyspace還是在master上寫入測試key同步check都是OK的。
  3. 為什麼主從切換後主機B上的key資料會丢失?這個是因為maxmemory設定過小,是故障的直接原因。
  4. 為什麼新master由于maxmemory參數超限進入隻讀狀态且删除部分資料後,新master中實際資料占用的大小依然超過>3GB?

如上四個疑問除了問題3已經明确了,剩下三個問題都讓人疑惑--事出詭異必有妖,經過一番探尋得出其答案:

  1. 上個月修改redis maxmemory時,隻通過config set指令修改了其運作時配置,而沒有修改對應配置redis.conf上maxmemory的值,主機B上redis在重新開機後就會從redis.conf上載入該maxmemory,該配置正是3GB,同時maxmemory參數是redis節點獨立的配置,slave并不會從master同步該值。
  2. 在redis5.0版本之後,redis引入了一個新的參數replica-ignore-maxmemory,其官方文檔定義如下:
Maxmemory on replicas
By default, a replica will ignore maxmemory (unless it is promoted to master after a failover or manually). It means that the eviction of keys will be handled by the master, sending the DEL commands to the replica as keys evict in the master side.
This behavior ensures that masters and replicas stay consistent, which is usually what you want. However, if your replica is writable, or you want the replica to have a different memory setting, and you are sure all the writes performed to the replica are idempotent, then you may change this default (but be sure to understand what you are doing).
Note that since the replica by default does not evict, it may end up using more memory than what is set via maxmemory (since there are certain buffers that may be larger on the replica, or data structures may sometimes take more memory and so forth). Make sure you monitor your replicas, and make sure they have enough memory to never hit a real out-of-memory condition before the master hits the configured maxmemory setting.
To change this behavior, you can allow a replica to not ignore the maxmemory. The configuration directives to use is:
replica-ignore-maxmemory no
           

大意是redis作為slave時預設會無視maxmemory參數,這樣可以保證主從的資料始終保持一緻。當master/slave實際資料大小均小于其maxmemory設定時,這個參數沒有任何影響,而這次丢失資料的原因正是因為主機B重新開機後作為slave時maxmemory(3GB)小于實際資料大小(6GB+),此時replica-ignore-maxmemory 預設開啟保證作為slave時直接無視maxmemory的限制,而當執行sentinel failover mastername将主機B切換為新master後,新master不會受replica-ignore-maxmemory影響,發現自身maxmemory<實際資料大小後直接開始主動淘汰key,進而導緻了資料丢失。

3. 至于主機B作為master執行淘汰key政策并最終進入隻讀狀态後,其實際資料大小依然>3GB的原因,則是由于線上redis配置的政策是volatile-lru政策,該政策隻會淘汰有過期時間的key,對于不過期的key是不淘汰的。

總結

總的來看這次故障的根本原因還是個人對于redis的配置、操作經驗不足,如果在調整運作時maxmemory時能做到以下二者之一,這次故障就不會出現了:

  1. 調整運作時maxmemory時同時調整配置檔案maxmemory保持一緻。
  2. 将配置檔案maxmemory設定為0--表示不限制記憶體使用。

正是因為對redis的認識和經驗不足,沒有想過到運作時配置與靜态配置不一緻可能導緻的問題,這次不可避免的踩坑了。

但是,作為一個本職RD,半路接手基本靠自學的兼職運維,要考慮到maxmemory的運作配置與靜态配置一緻性問題好像也确實不是那麼的理所當然。

處理完這次故障後,特意在網上搜尋了一番redis主從切換的注意事項、踩坑文章,想看看有沒有人提到類似的坑,但是并無所獲,難道這個坑真的沒其他人踩(分享)過?陷入思考...

最後

如果有經驗豐富的小夥伴看到這裡,也歡迎不吝賜教指導一下redis主從的切換的各類常識與常見大坑!

原文:http://www.cnblogs.com/AcAc-t/p/redis_master_switch_failure.html

如果感覺本文對你有幫助,點贊關注支援一下

繼續閱讀