Redis叢集slot遷移改造實踐

vivo互联网技术發表於2024-09-12

作者:來自 vivo 網際網路儲存團隊- Xu Xingbao

Redis 叢集經常需要進行線上水平擴縮容,實際操作過程中發現遷移期間服務時延劇烈抖動,業務側感知明顯,為了應對以上問題對原生 Redis 叢集 slot 遷移功能進行最佳化改造。

一、背景介紹

Redis 叢集服務在網際網路公司被廣泛使用,眾所周知服務叢集化可以突破單節點的能力瓶頸,帶來規模、可用性、擴充套件性等多方面的收益。在實際使用 Redis 叢集的過程中,發現在進行涉及叢集資料遷移的水平擴縮容操作時,業務側多次反饋 Redis 請求的時延升高問題,甚至發生過擴容操作導致叢集節點下線的可用性故障,並進一步引發遷移流程中斷、節點間資料腦裂等一系列嚴重影響,給運維同事帶來極大困擾,嚴重影響線上服務的穩定。

二、問題分析

2.1 原生遷移介紹

Redis 叢集功能採用無中心架構設計,叢集中各個節點都維護各自視角的叢集拓撲並儲存自有的分片資料,叢集節點間透過 gossip 協議進行資訊協調和變更通知。具體來說 Redis 叢集資料管理上採用虛擬雜湊槽分割槽機制,將資料的鍵透過雜湊函式對映到 0~16383 整數槽內,此處的槽位在 Redis 叢集設計中被稱為 slot。這樣實際上每一個節點只需要負責維護一部分 slot 所對映的鍵值資料,slot 就成為 Redis 叢集管理資料的基本單位,叢集擴縮容本質上就是 slot 資訊和 slot 對應資料資料在節點之間的轉移。Redis 叢集水平擴充套件的能力就是基於 slot 維度進行實現,具體流程如下圖所示。

上圖所示的遷移步驟中,步驟 1-2 是對待遷移 slot 進行狀態標記,方便滿足遷移過程中資料訪問,步驟 3-4 是遷移的核心步驟,這兩個步驟操作會在步驟 5 排程下持續不斷進行,直到待遷移 slot 的鍵值資料完全遷移到了目標節點,步驟 6 會在資料轉移完成後進行,主要是發起叢集廣播訊息更新叢集內節點 slot 拓撲。

由於正常的遷移時一個持續的處理過程,不可避免地會出現正在遷移 slot 資料分佈於遷移兩端地“分裂”狀態,這種狀態會隨著 slot 遷移的流程進行而持續存在。為了保證遷移期間正在遷移的 slot 資料能夠正常讀寫,Redis 叢集實現了下圖所示的一種 ask-move 機制,如果請求訪問正在遷移的 slot 資料,請求首先會按照叢集拓撲正常訪問到遷移的源節點,如果在源節點查詢到資料則正常處理響應請求;如果在源節點沒有找到請求所需資料,則會給客戶端回覆 ASK {ip}:{port} 訊息回包。

Redis 叢集智慧客戶端收到該回包後會按照包內節點資訊找到新節點重試命令,但是由於此時目標節點還沒有遷移中 slot 的所屬權,所以在重試具體命令之前智慧客戶端會首先向目的節點傳送一個 asking 命令,以此保證接下來訪問遷移中 slot 資料的請求能被接受處理。由於原生遷移時按照 key 粒度進行的,一個 key 的資料要不存在源節點,要不存在目的節點,所以 Redis 叢集可以透過實現上述 ask-move 機制,保證遷移期間資料訪問的一致性和完整性。

2.2 遷移問題分析

(1)時延分析
根據上述原生 Redis 叢集遷移操作步驟的瞭解,可以總結出原生遷移功能按照 key 粒度進行的,即不斷掃描源節點上正在遷移的 slot 資料併傳送資料給目的節點,這是叢集資料遷移的核心邏輯。微觀來說遷移單個 key 資料對於服務端來說包含以下操作:

  • 序列化待遷移鍵值對資料;
  • 透過網路連線傳送序列化的資料包;
  • 等待回覆(目標端接收完包並載入成功才會返回);
  • 刪除本地殘留的副本,釋放記憶體。

上述操作中涉及多個耗費執行緒處理時長的操作,首先序列化資料是非常耗費 CPU 時間的操作,如果遇到待遷移 key 比較大執行緒佔用時長也會隨之惡化,這對於單工作執行緒的 Redis 服務來說是不可接受的,進一步地網路傳送資料到目標節點時會同步等待結果返回,而遷移目的端又會在進行資料反序列化和入庫操作後才會向源節點進行結果返回。需要注意的是在遷移期間會不斷迴圈進行以上步驟的操作,而且這些步驟是在工作執行緒上連續處理的,期間無法對正常請求進行處理,所以此處就會導致服務響應時延持續突刺,這一點可以透過 slowlog 的監控資料得到驗證,遷移期間會在 slowlog 抓取到大量的 migrate 和 restore 命令。

(2)ask-move 開銷
正常情況下每個正在遷移的 slot 資料都會一段時間記憶體在資料分佈在遷移的兩端的情況,遷移期間該 slot 資料訪問請求可以透過 ask-move 機制來保證資料一致性,但是不難看出這樣的機制會導致單個請求網路訪問次數出現成倍的增加,對客戶端也存在一定的開銷壓力。另外,對於可能存在的使用者採用 Lua 或者 Pipline 這種需要對單個 slot 內多 key 連續訪問的場景,目前大部分叢集智慧客戶端支援有限,可能會遇到遷移期間相關請求不能正常執行的報錯。另外需要說明的是,由於 ask-move 機制的只在遷移兩端的主節點上能觸發,所以遷移期間從節點是不能保證資料請求結果一致性的,這對於採用讀寫分離方式訪問叢集資料的使用者也非常不友好。

(3)拓撲變更開銷
為了降低遷移期間資料 ask-move 的機制對請求的影響,正常情況下原生遷移每次只會操作一個 slot 遷移,這就導致對每一個遷移完成的 slot 都會觸發叢集內節點進行一次拓撲更新,而每次叢集拓撲的更新都會觸發正在執行指令的業務客戶端幾乎同時傳送請求尋求更新叢集拓撲,拓撲重新整理請求結果計算開銷高、結果集大,大大增加了節點的處理開銷,也會造成正常服務請求時延的突刺,尤其對於連線數較大、叢集節點多的叢集,集中的拓撲重新整理請求很容易造成節點計算資源緊張和網路擁塞,容易觸發出各種服務異常告警。

(4)遷移無高可用
原生的遷移的 slot 標記狀態只存在於遷移雙端的主節點,其對應的從節點並不知道遷移狀態,這也就導致一旦在遷移期間發生節點的 failover,遷移流程將會中斷和出現 slot 狀態殘留,也將進一步導致遷移 slot 資料的訪問請求無法正常觸發 ask-move 機制而發生異常。例如遷移源節點異常,那麼其 slave 節點 failover 上線,由於新主節點並不能同步到遷移狀態資訊,那麼對於遷移中 slot 的請求就不能觸發 ask 回覆,如果是一個對已經遷移至目標節點的資料的寫請求,新主節點會直接在本節點新增 key,導致資料出現腦裂,類似地如果處理的是已經遷移資料的讀取請求也無法保證返回正確結果。

三、最佳化方案

3.1 最佳化方向思考

透過原生資料遷移機制分析,可以發現由於遷移操作涉及大量的同步阻塞操作會長時間佔用工作執行緒,以及頻繁的拓撲重新整理操作,會導致請求時延不斷出現上升。那麼是否可以考慮將阻塞工作執行緒的同步操作改造成為非同步執行緒處理呢?這樣改造有非常大的風險,因為原生遷移之所以能夠保證遷移期間資料訪問的正確性,正是這些同步介面進行了一致性保證,如果改為非同步操作將需要引入併發控制,還要考慮遷移資料請求與 slave 節點的同步協調問題,此方案也無法解決拓撲變動開銷問題。所以 vivo 自研 Redis 放棄了原生按照 key 粒度進行遷移的邏輯,結合線上真實擴容需求,採用了類似主從同步的資料遷移邏輯,將遷移目標節點偽裝成遷移源節點的從節點,透過主從協議來轉移資料。

3.2 功能實現原理

Redis 主從同步機制是指在 Redis 主節點(Master)和從節點(Slave)之間進行資料同步和複製的過程,主從同步機制可以提高 Redis 叢集的可用性,避免單點故障和資料丟失等問題。Redis 目前主從同步有全量同步和部分同步兩種方式,從節點傳送同步位點給主節點,如果是首次同步則需要走全量同步邏輯,主節點透過傳送 RDB 基礎資料檔案和傳播增量命令方式將資料同步給從節點;如果不是首次同步,主節點則會透過從節點同步請求中的位點等資訊判斷是否滿足增量同步條件,優先進行增量同步以控制同步開銷。由於主節點在同步期間也在持續處理新的命令請求,所以從節點對主節點的資料同步是一個動態追齊的過程,正常情況下,主節點會持續傳送寫命令給從節點。

基於同步機制,我們設計實現了一套如下圖所示的 Redis 叢集資料遷移的功能。遷移資料邏輯主要走的全量同步邏輯,遷移資料和同步資料最大的區別在於,正常情況下需要遷移的是源節點部分 slot 資料,目標節點並不需要複製源節點的全量資料,完全複用同步機制會產生不必要的開銷,需要對主從同步邏輯進行修改適配。為了解決該問題,我們對相關邏輯做了一些針對性的改造。首先在同步命令互動上,針對遷移場景增加了遷移節點間 slot 資訊互動,從而讓遷移源節點獲知需要遷移哪些 slot 到哪個節點。另外,我們還對 RDB 檔案檔案結構按照 slot 順序進行了調整改造,並且將各個 slot 資料的檔案起始偏移量資料作為後設資料記錄到 RDB 檔案尾部固定位置,這樣在進行遷移操作的 RDB 傳輸步驟時就可以方便地索引到 RDB 檔案中目標 slot 資料片段。

3.3 改造效果分析

(1)時延影響小
對於 slot 遷移操作而言,主要涉及遷移源和目的兩端的開銷,對於基於主從同步機制實現的新 slot 遷移,其源節點主要開銷在於生成 RDB 和傳送網路包,正常對於請求時延影響不大。但是因為目的節點需要對較大的 RDB 檔案片段資料進行接收、載入,由於目的節點遷移時也需要對正常服務請求響應,此時不再能採用類似 slave 節點將所有資料收取完以後儲存本地檔案,然後進行阻塞式資料載入的方案,所以新 slot 遷移功能對遷移目的節點的資料載入流程進行了針對性改造,目的節點會按照接收到的網路包粒度將資料按照下圖所示進行遞進式載入,即 slot 遷移目標節點每接收完一個 RDB 資料網路包就會嘗試載入,每次只載入本次網路包內包含的完整元素,這樣複合型別資料就可以按照 field 粒度載入,從而降低多元素大 key 資料遷移對訪問時延的劇烈影響。透過這樣的設計保持原來單執行緒簡潔架構的同時,有效地控制了時延影響,所有資料變更操作都保持在工作執行緒進行,不需要進行併發控制。透過以上改造,基本消除了遷移大 key 對遷移目的節點時延影響。

(2)資料訪問穩定
新 slot 遷移操作期間,正在遷移的資料還是儲存在源節點上沒有變,請求繼續在源節點上正常處理,使用者側的請求不會觸發 ask-move 轉發機制。這樣使用者就不需要擔心讀寫分離會出現資料不一致現象,在進行事務、pipeline 等方式封裝執行命令時也不會出現大量請求報錯的問題。遷移動作一旦完成,殘留在源端的已遷移 slot 資料將成為節點的殘留資料,這部分資料不會再被訪問,對上述殘留資料的清理被設計在 serverCron 中逐步進行,這樣每一次清理多少資料可以引數化控制,可以根據需要進行個性化設定,保證資料清理對正常服務請求影響完全可控。

(3)拓撲變更少
原生的遷移功能為了降低 ask-move 機制對正常服務請求的影響,每次僅會對一個 slot 進行資料遷移,遷移完了會立即發起拓撲變更通知來叢集節點轉換 slot 的屬主,這就導致拓撲變化的次數隨著遷移 slot 的數量增加而變多,客戶端也會在每一次感知到拓撲變化後傳送命令請求進行拓撲更新。更新拓撲資訊的命令計算開銷較大,如果多條查詢拓撲的命令集中處理,就會導致節點資源的緊張。新的 slot 遷移按照節點進行資料同步,可以支援同時遷移源節點的多個 slot 甚至全部資料,最後可以透過一次拓撲變更轉換多個 slot 的屬主,大大降低了拓撲重新整理的影響。

(4)支援高可用
叢集的資料遷移是一個持續的過程,這個過程可能長達幾個小時,期間服務可能發生各種異常情況。正常情況下的 Redis 叢集具有 failover 機制,從節點可感知節點異常以代替舊主節點進行服務。新 slot 遷移功能為了應對這樣的可用性問題,將 slot 遷移狀態同步給從節點,這樣遷移期間如果叢集遷移節點發生 failover,其從節點就可以代替舊主節點繼續推進資料遷移流程,保證了遷移流程的高可用能力,避免人工干預,大大簡化運維操作複雜度。

四、功能測試對比

為了驗證改造後遷移功能的效果,對比自研遷移和原生遷移對請求響應的影響,在三臺同樣配置物理機上部署了原生和自研兩套相同拓撲的叢集,選擇後對 hash 資料型別的 100k 和 1MB 兩種大小資料分別進行了遷移測試,每輪在節點間遷移記憶體用量 5G 左右的資料。測試主要目的是對比改造前後數轉移對節點服務時延影響,所以在實際測試時沒有對叢集節點進行背景流量操作,節點的時延資料採用每秒鐘 ping 10 次節點的方式進行採集,遷移期間源節點和目的節點的時延監控資料入下表所示(縱軸數值單位:ms)。

透過對比以上原生和自研叢集 slot 遷移期間的時延監控資料,可以看出自研 slot 遷移功能遷移資料期間遷移兩端節點的請求響應時延表現非常平穩,也可以表現出經過主從複製原理改造的 Redis 叢集 slot 遷移功能具備的優勢和價值。

五、總結和展望

原生 Redis 叢集的擴縮容功能按照 key 粒度進行資料轉移,較大的 key 會造成工作執行緒的長時間佔用,進而引起正常服務請求時延飆高問題,甚至導致節點長時間無法回覆心跳包而被判定下線的情況,存在穩定性風險。透過同步機制改造實現的新 slot 遷移功能,能顯著降低資料遷移對使用者訪問時延的影響,提升線上 Redis 叢集穩定性和運維效率,同時新的 slot 遷移功能還存在一些問題,例如新的遷移造成節點頻繁的 bgsave 壓力,遷移期間節點記憶體佔用增加等問題,未來我們將圍繞這些具體問題,繼續不斷最佳化總結。

相關文章