­

Redis同步遷移數據

概述

        Redis集群版採用hash slot的方式來決定key存在哪個slot中。它總共有16384個slot,通過CLUSTER SLOTS或者CLUSTER NODES可獲取slot分佈情況。每個key只能存儲於一個slot裏面,具體一個key存儲於哪個slot是通過crc16( key)%16384計算得來。也可通過命令CLUSTER KEYSLOT獲取某個key位於哪個slot,一般情況下,集群中每個分片平均分配slot。比如3個分片。那每個分片的slot分別為0-5460,5461-10922,10923-16383。但這些slot並不是固定的,可以動態調整。下面則主要介紹它的調整方式及存在的問題及風險。

slot遷移流程

遷移一個slot大體可簡化為3步,流程如下圖所示:

Redis遷移流程

Redis遷移流程

  1. 標記遷移狀態

標記遷移目標分片的待遷移slot為IMPORTING狀態,然後再標記源分片待遷移slot為MIGRATING狀態。這裡標記狀態一定不能置換,否則會導致在該slot上的源分片及目標分片都無法寫入新數據。

  1. 遷移slot中的所有數據

依次獲取slot中的key,然後將這些key遷移到目標分片,直到所有的key遷移完成。

  1. 標記slot歸屬權

標記遷移完的slot歸屬權為遷移的目標分片,然後再標記源分片遷移完的slot歸屬於目標分片。這裡標記狀態一定不能置換,否則同 樣會導致在該slot上的源分片及目標分片都無法寫入新數據。

模擬搬遷過程

首先創建一個3主3從的集群,其初始化的集群分片信息如下,其中紅色標記了分片slot信息,其第一列為分片ID。

32fe95bc56611ced95553ba90bc7add1e3bef3ad 9.134.240.17:2777@12777 master – 0 1581226004825 4 connected 0-5460

ea63f5b7a787f2f12c74669814db487b1b18a887 9.134.240.102:3307@13307 slave c6e02ef185bd9d641b8a50fd82781f0aeb5eb618 0 1581226005827 5 connected

952c500a85976e818ce0da15e036f96f0076382c 9.134.240.214:3074@13074 myself,slave 32fe95bc56611ced95553ba90bc7add1e3bef3ad 0 1581225986000 3 connected

c6e02ef185bd9d641b8a50fd82781f0aeb5eb618 9.134.240.85:1637@11637 master – 0 1581226003824 5 connected 10923-16383

5a7c0f4a005ba70c8aa5097424d85dc07eb19c6e 9.134.241.18:3447@13447 master – 0 1581226002822 2 connected 5461-10922

a2574a5a2f54db80e4e401f8a1cc900b2da035aa 9.134.240.75:4219@14219 slave 5a7c0f4a005ba70c8aa5097424d85dc07eb19c6e 0 1581226006828 2 connected

首先寫入一個testmigrate到集群中,通過cluster keyslot testmigrate可算出它位於4470號slot,那麼待遷移的源分片為32fe95bc56611ced95553ba90bc7add1e3bef3ad,在遷移前,向源分片寫入set testmigrate 1。我們將這個分片遷移到目標分片c6e02ef185bd9d641b8a50fd82781f0aeb5eb618。

  1. 連接目標分片,標記遷移目標分片的待遷移slot為IMPORTING狀態。(cluster setslot 4470 importing 32fe95bc56611ced95553ba90bc7add1e3bef3ad),在標誌後,目標分片信息為:c6e02ef185bd9d641b8a50fd82781f0aeb5eb618 9.134.240.85:1637@11637 myself,master – 0 1581228243000 5 connected 10923-16383 [4470-<-32fe95bc56611ced95553ba90bc7add1e3bef3ad]
  2. 連接源分片,標記源分片待遷移slot為MIGRATING狀態。(cluster setslot 4470 migrating c6e02ef185bd9d641b8a50fd82781f0aeb5eb618)標誌後目標分片信息為:32fe95bc56611ced95553ba90bc7add1e3bef3ad 9.134.240.17:2777@12777 myself,master – 0 1581228385000 4 connected 0-5460 [4470->-c6e02ef185bd9d641b8a50fd82781f0aeb5eb618]
  3. 模擬數據寫入(操作流程可跳過這個步驟,這步用於解釋方案存在的問題)
    1. 寫入或者修改未遷移的數據testmigrate,此時返回成功,並無影響。
    2. 寫入已經遷移完的key或者新的key,此時會返回(error) ASK 4470 9.134.240.85:1637錯誤,這個時候我們需要連接到遷移的目標分片,然後先執行asking,再寫入已經遷移完的key或者新的key,這個也是同步擴容的關鍵點,它能保證數據一定可以遷移成功,與業務的寫入速度無直接關係,這個屬於同步方案的最大優點。但這點上缺陷也比較明顯,如果用戶通過mset或者mget這類命令寫入或者查詢多個key的時候,如果這些key在源分片不能完全找到,它會返回ASK錯誤,此時需要轉向目標分片執行寫入和查詢,但執行時並不能保證所有的key已經遷移到目標分片,這個時候會返回(error) TRYAGAIN Multiple keys request during rehashing of slot錯誤,這樣就導致key無法正常寫入。這種情況可以通過client或者proxy將mset/mget在寫入redis之前進行拆分,這樣相當於set/get命令,此時便不會出現無法寫入的情況,但性能會有影響。
  4. 批量從源分片獲取KEY ,(CLUSTER GETKEYSINSLOT 4470 10),命令一次讀取10個key,但由於遷移前只寫入了一個key,這裡只會返回一個key。testmigrate
  5. 遷移上步獲取這些KEY (migrate 9.134.240.85 1637 "" 0 10000 auth password keys testmigrate),這個命令會遷移上步獲取 key。
  6. 重複前面兩步至到遷移完後
  7. 連接目標分片,標記遷移完的slot歸屬權為遷移的目標分片(cluster setslot 4470 node c6e02ef185bd9d641b8a50fd82781f0aeb5eb618)。
  8. 連接源分片,然後再標記源分片遷移完的slot為目標分片。(cluster setslot 4470 node c6e02ef185bd9d641b8a50fd82781f0aeb5eb618)

同步遷移存在問題及解決辦法

  1. 遷移時長限制

Redis請求處理為單線程,所有命令都只能串形執行,如果在我們遷移過程中有一個大key,那麼在遷移過程用戶及集群gossip請求處理都會阻塞,在我們的遷移測試中,遷移600MB的list,4千萬個整形key,整個遷移時間為7.42s。那在這個7.42s的過程中,用戶是無法有請求響應。由於我們分片cluster-node-timeout為默認的15s,所以在整個遷移過程中如果超過15s分片會被判死,從而導致主從切換。為了減少對業務的影響,同時避免主從切換,那就需要控制遷移時間。為了控制每次Key的搬遷時長,我們引入了遷移評估流程,就是在遷移前,檢查是否滿足遷移條件,總共需要檢查的有兩個點,一是每個key的搬移時長不能超過指定時長,我們目前定的是3s,那轉換為key的大小,大體為200MB,所以我們在遷移過程中是限制單Key不能超過200MB,

  1. 遷移容量評估

除遷移時長外,還一個就是遷移容量,遷移過程中一定要保證目標分片一定有足夠的容量容下帶遷移的key。Redis獲取key的容量使用的是MEMORY USAGE key samples count方法,但這個方法需要藉助管控去查詢所有待遷移的key,並且分slot統計需要遷移的容量,這種方式最主要的問題就是速度太慢。為了減少評估時間,我們在Redis中新增了評估命令,該命令返回slot的容量及其中最大Key的容量來解決遷移評估。如果在一個縮容流程中,它的數據遷移流程如下:

擴縮容流程

擴縮容流程

  1. 在兩個分片中同一個slot都存在部分key時的訪問問題。

在上節3b步驟中提過其存在的問題,這裡不在重複。除了一些批量執行命令外,在lua執行中也可能出現執行報錯。

  1. 遷移一個slot到全新分片時lua無法遷移的問題。

由於在新分片無lua相關腳本 ,如果通過EVALSHA執行則會報錯。 但在實際中,一般比較少的情況下只使用evalsha命令,因為lua腳本一旦變了,那麼腳本的sha也會改變。

  1. 遷移過程中主從切換導致集群無法寫入及多份數據的問題

源分片發生主從切換,此時集群在目標分片不認可源分片對遷移分片的歸屬權,從而導致該分片認為集群fail,此時該分片則無法寫入數據。但由於源分片可以寫入,此時可能存在兩個不同分片上面存在二份不同版本的數據,同時讀取的時候由於部分數據已經遷移,也會導致部分數據無法讀取。

  1. 對業務存在很小的性能影響

在遷移過程中會在源分片dump數據,然後在目標分片restore數據,會一定程度增加一定的寫入量,但這個可以根據並發遷移key個數及加入一定遷移間隔來減少對業務影響。

總結

Redis同步遷移有着簡單,遷移不受寫入速度的限制,但也存在一些無法規避的問題,特別是遷移大key影響業務及集群、lua無法遷移到新分片的問題,同步遷移都無法很好的支持,並且遷移過程中存在狀態,也增加了一定遷移風險。在redis5.0中redis-cli直接集成了cluster相關的工具,比如slot均衡,slot擴容狀態修復等,也簡化了常用運維操作,但本身並沒有解決其存在的問題,我們在實際的生產環境中改動redis源碼來加強遷移穩定性,但還是無法消除同步遷移方法的不足。而最終解決只能通過異步遷移來解決同步遷移的問題,目前公有雲最新的遷移已經切換到異步方案。可參考https://cloud.tencent.com/developer/article/1598700。