切換語言為:簡體

Redis縮容導致線上大規模故障的慘痛經歷

  • 爱糖宝
  • 2024-09-02
  • 2045
  • 0
  • 0

簡述

唉,小趴菜我最近又犯事了,怎麼會是捏?
事情是這樣的,618後業務量回落,業務應用大量縮容,Redis叢集呢也要跟著縮容,但是在晚上22:00進行縮容運維操作的時候,突然導致業務訪問Redis大規模超時,此後經過重啟短暫恢復,但又在凌晨00:00到01:30再次觸發大規模Redis訪問故障,當我收到業務的電話轟炸和告警通知時,人已經麻了。。
痛定思痛,總結下來我覺得這次故障還是比較有價值的,至少是自己接手Redis以來的第一次大規模事故(痛心ing,績效沒了),下面就開始講述這一次我遭遇的血的教訓!

故障時間線

伏筆

在正式開始之前,我先埋一個伏筆。😀
在618開始之前,爲了應對業務高峰而擴容了Redis叢集,最多的Redis叢集機器數量超過100多個,這時由於叢集規模過大,因而發生了cluster nodes慢查詢的問題,爲了排查這個問題,我在一個叢集裡手動加入了兩個新節點用於測試Redis6對cluster nodes命令的最佳化,我成功完成了驗證Redis6確實有效,但今天的主題不是這個。。
一般來說加入新節點都是透過我們開發的web端Redis管理平臺加入的,但誰能想到這次手動加入卻成爲了十幾天後這次故障的伏筆...

第一次故障

時間 現象描述
22:31:00 縮容時需要將資料從待下線節點遷移到叢集內其他節點上,這時資料遷移完成
22:35:00 停止從節點的Redis程序
22:39:00 開始下線第一個主節點,這時業務開始報錯

第一次故障的現象是下線第一個主節點後業務開始報錯,具體報錯是超時報錯,由於Redis縮容導致短暫的快取訪問超時是預期內的,決定先觀察再決定後續操作。
但觀察1min後異常仍然沒有停止的跡象,爲了及時止損,先手動重啟(第二個伏筆)了這臺下線的主節點Redis程序,此時業務異常恢復。
鬆了一口氣的我正要排查原因時,突然業務反饋Redis查詢
99線變高了
!我一開始還不信,親自確認後發現確實999線達到了100多ms,平時這個時間最多2ms

所以原因是什麼呢?

我一時半會沒想到😢,聯絡了組內其他同事一起看,也沒有找到原因,因此決定將叢集完全恢復到縮容之前的狀態。。。

第二次故障

沒錯,只要恢復到縮容之前的狀態一切都會好起來的!👌

於是我們先決定將資料遷移回那些準備下線的Redis主節點上,我們在內部的Redis管理端操作遷移。但是一遷移問題就出現了,**又是大量業務報錯,甚至比第一次更多!**看到CAT上大量的紅盤我的心拔涼拔涼的。。。

我們緊急停止了遷移,但是報錯竟然沒有停止,還在瘋狂報錯,分析具體報錯發現不是超時報錯,而是MOVED異常,和之前的 MOVED異常不同,這次報錯的量很多,而且停止遷移後仍然在報錯。

進一步分析發現MOVED異常是有規律的,具體來說基本上集中在兩個ip,slot是10234,假設這兩個ip分別為ip1和ip2,那麼報錯就類似於:

moved ip1 10234 moved ip2 10234

我們立即檢視了到這兩個ip上檢視叢集拓撲(透過cluster nodes命令),果然發現了問題,在ip1上檢視10234的歸屬確實是ip2,然而在ip2上檢視10234的歸屬竟然是:

86107b968a281aedbcddd9267b53dfb4bcaadee4 ip2:6379@16379 myself,master - 0 1716880618000 80 connected ... [10234-<-9a1be71bd5699ecb62b4308212ba1363ea72020f]
9a1be71bd5699ecb62b4308212ba1363ea72020f ip1:6379@16379 myself,master - 0 1716880619167 74 connected ...

原來如此!在ip2上檢視10234的狀態竟然是遷移中
而且源節點就是ip1,那麼怪不得有報錯了:只要查詢10234上的key的請求,大部分都會路由到ip2,但是ip2又會返回MOVED ip1,然後客戶端再訪問ip1,ip1又重定向到ip2,如此迴圈,超過5次(Lettuce的預設配置)就會報錯。😵

那麼問題來了,為什麼slot的狀態不對呢?

這時候我們沒有時間詳細排查,業務全炸了((⊙﹏⊙)還好是半夜)
所以我們透過在ip2上執行cluster setslot命令先將10234的歸屬設定為自己,然後再觀察業務報錯,果然在執行命令之後全部恢復了。 終於鬆了一口氣,但是當我們確認業務訪問Redis的999線是否正常時,居然又給我當頭一棒:業務的999線仍然沒有恢復,還是超過100ms!

這時我終於傻了,為什麼呀?😂

999線仍然很高的原因是什麼?

在檢視cluster nodes命令輸出時發現一個盲點:我們發現了已經下線的從節點仍然在cluster nodes的輸出中,而且狀態是handshake! 為什麼已經下線的節點還會出現在叢集拓撲中呢?

來不及細想,我們懷疑這很可能就是999線升高的原因,懷著瞎貓碰著死耗子的心態,我重啟了已經下線的從節點程序,然後奇蹟發生了,999線居然真的恢復了!!!

歷時3個小時,從22:30開始的故障持續到1:20,心力交瘁的我仍然無法入睡,比起明天來自領導的質問,此時縈繞在我心頭的是兩個問題:

  1. 為什麼999線會升高?handshake狀態的節點會和這有關係嗎?

  2. 為什麼遷移slot的時候會出現slot分佈不一致的情況?

事後分析

slot分佈不一致的原因

冥思苦想得不出結果,我重新梳理了slot遷移的各個步驟:

  1. 首先要在源節點上設定slot的狀態為migrating,在目標節點上設定slot的狀態為importing

  2. 開始遷移,使用migrate命令

  3. 遷移完成,給所有叢集中的master節點發送cluster setslot {slot} node {nodeId},這裏的nodeId就是目標節點的slotId,這個命令的目的是確立slot的最終歸屬

好的回到我們的問題,為什麼會出現第二次故障時,目標節點的slot狀態不對?

這就到了伏筆二出場的時刻了,還記得當初是怎麼重啟下線的主節點的?手動! 為什麼手動會出現問題?

因為我們是在管理端操作遷移的,管理端會自動進行上述1~3步驟命令的傳送,但是卻沒有給手動重啟的節點發送!因為出手動重啟的節點已經在管理端下線,只是手動重啟導致管理端並不知道節點已經重啟了,傳送setslot命令並沒有傳送給出問題的節點!所以導致slot的狀態一直沒有更新。。

好的,第二個問題的解釋如此蒼白,但總算有了解釋!

現在來分析第一個問題:為什麼999線會升高? handshake狀態的節點會和這有關係嗎? 這其實是兩個問題,我們分開來說。

handshake狀態的節點從哪來的?

首先解釋為什麼會出現handshake狀態的節點?

handshake節點是剛加入到叢集的狀態,還沒有完成ping-pong的互動,而且進一步細看,這些節點全部都是已經下線的從節點!

我再次確認了從節點上已經沒有Redis程序了,可為什麼已經下線的從節點還會出現叢集中?

首先我們要了解Redis叢集節點下線需要做什麼:

  1. 如果是主節點,需要將其擁有的slot遷移到其他節點,然後對叢集內其他節點執行cluster forget {nodeId}

  2. 如果是從節點,則對叢集內其他節點直接執行cluster forget {nodeId}

注意“對叢集內其他節點直接執行cluster forget”這個動作需要在60s內傳送完成,其原因是:

當節點接收到CLUSTER FORGET node-id命令後,會把node-id指定的節點加入到禁用列表中,在禁用列表內的節點不再發送Gossip訊息。禁用列表有效期為60秒,超過60秒節點會再次參與訊息交換。也就是說第一次forget命令發出後,需60秒內在叢集各節點上執行forget命令

所以為什麼會出現handshake?

這個問題隨著slot遷移不一致問題的解決也得到了提示,這時是時候揭曉伏筆一了:之前我沒有透過管理端而是手動新增了兩個節點到叢集中!

那麼這兩個節點肯定沒有接收到cluster forget命令!導致其在叢集內部不斷傳播這兩個節點的gossip訊息。

完了嗎?還沒有!

999線升高的原因

好的handshake的問題解決了,可為什麼業務訪問999線會升高呢?

這裏我詳細觀察了業務訪問Redis耗時變高的時間點正好和叢集內部cluster nodes命令突然變高的時間點吻合!
而且cluster nodes命令呈現出週期性尖刺的形式!那也就是說很可能是業務客戶端在同一時間呼叫cluster nodes命令造成Redis短時間阻塞,但為什麼cluster nodes變得頻繁了?

cluster nodes是Lettuce客戶端用來重新整理叢集拓撲的命令,而觸發叢集拓撲重新整理有幾個條件:

拓撲重新整理機制 觸發時機 約束條件
onAskRedirection 執行 Redis 命令時候,Key 所在 SLOT 正在遷移資料 預設 30 秒內已經重新整理過,則跳過重新整理
onReconnectAttempt ConnectionWatchdog 監聽到 channelInactive,立即觸發延遲重連,預設最多重連 5 次延遲提交機制
onUncoveredSlot 客戶端建立 Redis 連線時,找不到 SLOT 或者 Host/Port 對應的 partition 資訊

對於第一個“執行 Redis 命令時候,Key 所在 SLOT 正在遷移資料”這個不太可能,因為即使不再遷移資料999線還是升高。
第三個onUncoveredSlot,當時並沒有出現找不到Slot或者Slot未找到對應Redis節點。

而注意第二個:“ConnectionWatchdog 監聽到 channelInactive,立即觸發延遲重連,預設最多重連 5 次延遲提交機制”,這個是最有可能的,因為在我下線從節點的時候其實999線就已經升高了,而在我們恢復從節點後999線就恢復了。

所以事情是這樣的。。。

  1. 從節點下線,業務機器和從節點之間的連線斷開,大部分叢集內部節點都forget了從節點,但是還有兩個節點沒有forget

  2. Lettuce觸發叢集拓撲重新整理(我們配置的是60s重新整理一次)

  3. 兩個沒有forget的節點仍然在叢集中傳播已經下線的節點資訊,體現在cluster nodes命令中就是那些handshake的節點

  4. Lettuce獲取的叢集拓撲仍然包含handshake節點,依然會不斷觸發拓撲重新整理 5.大量cluster nodes集中在某一個時間點,Redis短暫阻塞,導致業務查詢出現999升高

經驗總結

不要線上上做任何黑屏操作!

不要線上上做任何黑屏操作!

不要線上上做任何黑屏操作!

這次出現的故障都是由於手動操作導致的! 其他要注意的點有:

  1. 節點下線要保證在1min內傳送cluster forget命令到所有節點上

  2. cluster nodes導致慢查詢的最佳化

希望大家遇到此類問題時,不要慌,相信一定有解決的辦法,當然最好還是提前準備好預案,線上謹慎操作吧!祝大家工作愉快

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.