滴滴 Redis 異地多活的演進歷程

陶然陶然發表於2023-11-20

  為了更好的做好容災保障,使業務能夠應對機房級別的故障,滴滴的儲存服務都在多機房進行部署。本文簡要分析了 Redis 實現異地多活的幾種思路,以及滴滴 Redis 異地多活架構演進過程中遇到的主要問題和解決方法,拋磚引玉,給小夥伴們一些參考。

   Redis 異地多活的主要思路

  業界實現 Redis 異地多活通常三種思路:主從架構、Proxy雙寫架構、資料層雙向同步架構。

  主從架構  

  主從架構的思路:

  各機房的 Redis 透過 Proxy 對外提供讀寫服務,業務流量讀寫本機房的 Redis-proxy

  主機房裡的 Redis-master 例項承擔所有機房的寫流量

  從機房裡的 Redis-slave 例項只讀,承擔本機房裡的讀流量

  主從架構的優點:

  實現簡單,在 Proxy 層開發讀寫分流功能就可以實現

  Redis 層使用原生主從複製,可以保證資料一致性

  主從架構的缺點:

  從機房裡的 Redis-proxy 需要跨機房寫,受網路延時影響,業務在從機房裡的寫耗時高於主機房

  主機房故障時,從機房的寫流量也會失敗,需要把從機房切換為主機房,切換 Redis-master

  網路故障時,從機房的寫流量會全部失敗,為了保障資料一致性,這種場景比較難處理

  Proxy 雙寫架構  

  Proxy 雙寫架構的思路:

  各機房的 Redis 透過 Proxy 對外提供讀寫服務,業務流量讀寫本機房的 Redis-proxy

  不區分主從機房,每個機房都是獨立的 Redis 叢集

  各機房的讀寫流量都是訪問本機房的 Redis 叢集

  Proxy 層在寫本機房成功後,將寫請求非同步傳送到對端機房

  Proxy 雙寫架構的優點:

  實現簡單,在 Proxy 層開發雙寫功能就可以實現

  一個機房故障時,其他機房的流量不受影響

  網路故障時,各機房內部的流量也不受影響

  Proxy 雙寫架構的缺點:

  不能保證資料一致性,Proxy 非同步 write 請求可能會失敗,失敗丟棄請求後,導致雙機房資料不一致

  假設機房-A的叢集先上線,機房-B 後上線,Proxy 雙寫架構不能支援把機房-A的存量資料同步到機房-B

  網路故障時,非同步 write 會失敗後丟棄,網路恢復後,之前失敗的資料已經丟棄,導致雙機房資料不一致

  資料層雙向同步架構  

  資料層雙向同步架構的思路:

  Proxy 不關心底層 Redis 資料同步

  業務流量只訪問本機房裡的 Redis 叢集

  在 RedisServer 層面實現資料同步

  資料層雙向同步架構的優點:

  機房-A故障時,機房-B不受影響,反向如是

  網路故障時,本機房流量不受影響,網路恢復後,資料層面可以拉取增量資料繼續同步,資料不丟

  支援存量資料的同步

  業務訪問 Redis 延時低,訪問鏈路不受機房間網路延時影響

  業務單元化部署時,雙機房 Redis 會有較高的資料一致性

  資料層雙向同步架構的缺點:

  實現相對比較複雜,RedisServer 改動比較大

   滴滴 Redis 架構

  Codis 架構(早期架構,現已廢棄)  

  Kedis 架構(線上架構)  

   滴滴 Redis 異地多活架構的演進

  第一代多活架構  

  第一代 Redis 多活基於 Codis 架構在 proxy 層實現了雙寫,即本機房的 Proxy 將寫流量轉發到對端機房的 Proxy,這個方案的特點是快速實現,儘快滿足了業務多機房同步的需求。如前面 Proxy 雙向架構思路所講,本方案還存在著諸多缺點,最主要的是網路故障時,同步資料丟失的問題,為了解決這些問題,我們開發了第二代多活架構。

  第二代多活架構  

  

  第二代多活基於 Kedis 架構,對 Redis-server 進行改造,可以把增量資料從 Redis 直接寫入本機房的 MQ 中,由對端機房的 consumer 來消費 MQ,consumer 將資料寫入對端 Redis 中。網路故障時,資料會在 MQ 堆積,待網路恢復後,consumer 可以基於故障前的 offset 繼續進行消費,寫入對端 Redis,從而保證在網路故障時 Redis 多活不會丟資料。

  但這一代架構仍不夠完美,存在以下問題:

  ProducerThread 把資料寫入 MQ 時,如果觸發 MQ 限流,資料會被丟掉

  RedisServer 內部包含了 ProducerThread,當中間內部 queue 累積資料量超過10000條時,資料會被 MainThread 丟掉

  中間同步資料寫入 MQ,增加了跨部門依賴,同步鏈路長,不利於系統穩定性

  中間同步鏈路重試會造成非冪等命令執行多次,例如 incrby 重試可能造成命令執行多次造成資料不一致

  對於新建雙活鏈路,不支援同步存量資料,只能從當前增量資料開始同步

  Redis 增量資料寫入 MQ,導致成本增加

  為了解決以上問題,我們開發了第三代架構。

  第三代多活架構

  在第三代架構中,我們細化了設計目標,主要思路是保證同步鏈路中的資料不丟不重,同時去掉對 MQ 的依賴,降低多活成本。  

  第三代架構中,我們去掉了 MQ 和 consumer,新增了 syncer 元件。syncer 元件模擬 Redis-slave 從 Redis-master 中拉取增量資料,這樣把資料同步和 Redis 進行解耦,便於後續多機房擴充套件。

  在第三代架構中,Redis 遇到了迴環、重試、資料衝突、增量資料儲存和讀取等問題,接下來一一介紹我們應對這些問題的解決方案。

  1、迴環問題

  機房-A 寫入的資料同步到機房-B,防止資料再傳回機房-A。  

  為了解決迴環問題,我們開發了防迴環機制:

  Redis 增加 shardID 配置,標識唯一分片號

  Redis 請求中增加 opinfo,記錄元資訊,包含 shardID  

  機房-A 的 Proxy 寫入了 set k v 請求

  機房-A 的 Redis-master 向 syncer 同步 set k v opinfo[shardID-1] 請求

  syncer 向機房-B 寫入 set k v opinfo[shardID-1] 請求

  這樣機房-B 根據 shardID-1 識別出這條請求是機房-A 生產的資料,因此不會再向機房-A 同步本條請求

  2、重試問題

  機房-A 寫入的 incrby 請求同步到機房-B,由於中間鏈路的重試,導致機房-B 可能執行了多次。  

  為了解決重試問題,我們開發了防重放機制:

  Redis 增加 opid,標識唯一請求號

  Redis 請求中增加 opinfo,記錄元資訊[opid]  

  機房-A 的 Proxy 寫入了 incrby k 1 請求

  機房-A 的 Redis-master 向 syncer 同步了 incrby k 1 opinfo[opid=100] 請求, 之前同步的 opid=99 的請求已經成功

  syncer 向機房-B 寫入 incrby k 1 opinfo[opid=100] 請求

  機房-B 的 Redis 裡儲存了防重放資訊 shardID-1->opid[99]

  機房-B 的 Redis 發現新請求的 opid=100>本地的99,判斷為新請求

  機房-B的 Redis 執行這條請求,並把防重放資訊更新為shardID-1->opid[100]

  假設機房-A 的 syncer 將本條請求進行了重試,又執行了一遍 incrby k 1 opinfo[opid=100]

  機房-B 的 Redis 發現新請求 opid=100 等於本地的100,判斷為重複請求

  機房-B 的 Redis 忽略掉本地請求,不執行

  3、資料衝突問題

  雙機房同時修改同一個 key 導致資料不一致  

  對於資料衝突,不同資料型別的不同操作的資料合併,如果單從儲存層解決,是一個非常複雜的話題。如果業務層做了單元化部署,則不會出現這種問題。如果業務層沒有做單元化,我們開發了衝突檢測功能,來幫助業務及時發現資料衝突,最後資料以哪邊為準來修正,需要業務同學來決策。

  衝突檢測機制:

  Redis 記錄 key 的最後 write 時間

  Redis 請求中增加 opinfo,記錄元資訊 [timestamp]

  如果 opinfo.timestamp<=key_write_time,則記錄衝突 key  

  時間T1<T2<T3

  T1時間,使用者在機房-A 寫入請求 set k v1

  T2時間,使用者在機房-B 寫入請求 set k v2,並記錄k的最後修改時間為T2

  由於網路同步延時,T3時間,syncer 把T1時間寫入的 set k v1請求傳送到了機房-B

  機房-B 的 Redis 執行 set k v1 時發現 timestamp 為T1,但 k 的最後修改時間為T2

  由於T1<T2,機房-B 的 Redis 判斷這是一次衝突,並記錄下來,然後執行該條請求

  以上是衝突檢測的基本原理,這是一個旁路統計,幫助使用者發現一些潛在衝突資料。

  4、增量資料儲存和讀取問題

  因為 syncer 只是同步元件,不會儲存資料,所以需要考慮當網路故障時,增量資料的儲存和讀取問題。  

  為了解決這個問題,我們對 Redis 的 aof 機制進行了改造,可以在網路故障時,增量資料都堆積在 Redis 的磁碟上,在網路恢復後,syncer 從 Redis 里拉取增量 aof 資料傳送到對端機房,避免資料丟失。

  aof 機制改造有:aof 檔案切分、aof 增量複製、aof 非同步寫盤  

  將 aof 檔案切分為多個小檔案,儲存增量資料

  當增量資料超過配置的閾值時,Redis 自動刪除最舊的 aof 檔案

  當 Redis 重啟時,載入 rdb 檔案和 rdb 之後的 aof 檔案,可以恢復全部資料

  當網路故障恢復後,syncer 根據故障前的 opid 向 Redis 請求拉取增量資料,傳送到對端機房  

  開源 Redis 是在主執行緒中進行 aof 寫盤,當磁碟 IO 過高時,Redis 寫盤可能造成業務訪問 Redis 耗時抖動。因此我們開發了 aof 非同步寫盤機制:

  Redis 的主執行緒將 aof 資料寫入 queue 中

  bio 執行緒來消費 queue

  bio 執行緒將 aof 資料寫入磁碟

  這樣 Redis 的訪問耗時不受磁碟 IO 的影響,更好的保證穩定性。

來自 “ 滴滴技術 ”, 原文作者:魏猛;原文連結:https://server.it168.com/a2023/1115/6829/000006829577.shtml,如有侵權,請聯絡管理員刪除。

相關文章