Redis存儲遷移方案
9 人贊了文章
摘要
現在互聯網公司的體系架構中,redis存儲是必不可少的。有很多公司在創業初期的時候,由於技術積累或研發成本等原因,選擇使用雲上的Redis存儲作為NOSQL。通常雲上的Redis都是分散式的Redis存儲,具有高吞吐量、高可用、可水平伸縮等特性,這為研發人員提供了很大便利。隨著公司規模的發展、技術的壁壘等因素,希望把在雲上的Redis存儲數據遷移到內部的機房等其他地方。市面上已經有了一些Redis遷移工具,但是這些工具的使用場景有限,有時並不滿足遷移時的使用需求。這時就需要一個較為完善的Redis遷移工具來完成這件事情。
在這裡,我們會介紹已經在生產環境驗證多次的Redis遷移工具的設計,然後描述應用級透明、低開銷、高可用、數據一致性這四個設計目標是如何實現的。之後會描述這個遷移工具的一些限制和弊端,還會介紹一些其他的遷移方法。最後會討論一些可以優化的地方來提高整體效率和穩定性。
1 介紹
我們開發了一個Redis遷移工具來把在雲上的Redis存儲遷移到自建的Redis存儲或Codis集群,我們把自建Redis存儲或Codis集群這類存儲稱為目標存儲。在很多情況下,使用雲上的Redis存儲的業務服務是不可以停機的。另外,可能由於網路異常、服務故障等不可預測的因素導致數據遷移過程會隨時中斷,所以數據遷移被中斷不應該影響業務服務的正常使用。從這些條件中推倒出來了幾個具體的設計目標:
- 應用級透明:當進行遷移時, 正在運行的服務不應該意識到遷移正在進行,這意味著開發人員不需要對代碼做任何改動,業務服務也不需要停機。
- 低開銷:這個遷移工具不應該明顯地影響進行遷移的服務的性能。在遷移的時候,執行Redis命令請求所花費的時間不應該影響業務的可用性。
- 高可用:如果在數據遷移過程中發生了異常,那麼不應該影響業務服務的可用性,只需要重新啟動遷移過程就可以繼續遷移。
- 遷移後的數據一致:在遷移的過程中, 雲上的Redis存儲的狀態在隨時發生著變化。當遷移的過程結束時, 我們需要保證目標存儲的最終狀態要與在雲上的Redis存儲的狀態保持一致。
我們的Redis遷移工具的一個額外的設計目標是高吞吐量,因為不同的服務使用Redis存儲的讀寫模式不同,某些服務大多數Redis請求是讀請求,寫請求只佔很小的一部分。而有些服務的請求大部分是寫請求。為了實現上述四個設計目標,寫請求的吞吐量成為了這個遷移工具的性能瓶頸。由於寫請求吞吐量的限制,所以我們無法在保證業務服務高可用的前提下進行遷移超出寫請求吞吐量限制的業務服務。
當業務服務進行遷移時,雖然應用級透明這個設計目標保證了開發人員不需改動代碼,但是開發或運維人員需要多次對服務進行重新配置來更改Redis存儲的伺服器的地址。
2 實現
有很多公司在創業初期的時候,由於技術積累或研發成本等原因,選擇使用雲上的Redis存儲作為NOSQL。通常雲上的Redis都是分散式的Redis存儲,具有高吞吐量、高可用、可水平伸縮等特性,這為研發人員提供了很大便利。隨著公司規模的發展、技術的壁壘等因素,希望把在雲上的Redis存儲數據遷移到內部的機房等其他地方。市面上已經有了一些Redis遷移工具,如後面會介紹的redis-port[1],但是這些工具的使用場景有限,有時並不滿足遷移時的使用需求。這時就需要一個較為完善的Redis遷移工具來完成這件事情。下面將會介紹如何實現低開銷、高可用、遷移後的數據一致、應用級透明這四個設計目標的。
2.1 分散式集群
首先,業務服務是時刻運行的,為了給用戶提供高可用的服務,在遷移的過程中不能使業務服務停機。另外,又要保證業務服務在遷移過程中的高可用,不能因為遷移過程出現未知異常而導致業務服務不可用或最終的遷移數據不一致的情況。經過上面兩點的考量,我們決定使用分散式集群作為遷移工具的整體架構。如圖2-1所示,整個集群由多個Proxy組成,Proxy實現了Raft一致性協議[2],Proxy集群使用Raft協議用來保證遷移工具的高可用性,在遷移過程中,業務服務的可用性不會受到部分Proxy出現宕機、網路中斷等異常的影響。另外Proxy還實現了Redis的RESP[3]協議,所以對於Redis客戶端來說連接Proxy與連接一個Redis伺服器並沒有什麼區別。正如圖2-1所示的那樣,在Proxy集群中存在著不同的角色,分別是Leader和Follower,不同的角色會處理不同的事情。
2.1.1 Follower
在整個Proxy集群中存在著多個Follower,每一個Follower的職責都是相同的:
- 處理Redis讀請求:當接收到來自Redis-Client的讀請求時,Proxy-Follower相當於一個Redis代理,它僅僅簡單地把這個Redis讀請求轉發到雲上的Redis存儲中,然後同步地把結果返回給Redis-Client。
- 轉發Redis寫請求:當接收到來自Redis-Client的寫請求時,Proxy-Follower會把這個請求轉發給Proxy集群中的Leader,由Leader統一處理集群中的Redis寫請求。然後把Leader返回的執行結果返回給Redis-Client。
2.1.2 Leader
在整個Proxy集群中,只有一個Proxy扮演著Leader的角色。Leader除了會做與Follower相同的事情之外,還會處理整個集群中所有的Redis寫請求。Leader把Redis寫請求轉發到雲上的Redis存儲的同時,會根據執行成功的寫請求的轉發的順序對這個命令記錄AOF日誌。AOF日誌是保證遷移狀態結束之後目標存儲與雲上存儲的數據保持一致的關鍵記錄,2.2節會詳細介紹AOF日誌。除此之外,在重放階段,Leader會把AOF日誌中所有的命令同步到目標存儲,3.1節會詳細介紹重放階段。
2.2 AOF日誌
AOF全稱為Append Only File,意為僅在文件後面追加數據。AOF日誌僅在重放階段被使用,它作為遷移後的數據一致性的保證,主要用於在重放階段把Redis寫命令同步到目標存儲。AOF日誌只由Leader負責記錄在本地磁碟,每一條AOF日誌由兩部分構成:
- Index:Index是一個遞增的序列,唯一標識一條AOF日誌。當Leader成功地處理了一條Redis寫請求的時候,Index會進行遞增,同時會把當前執行的Redis寫命令與Index記錄到AOF日誌,之後會把最新的Index同步到Raft中。
- CMD:CMD表示當前執行的Redis寫命令,它包含了一條命令中所有的關鍵信息,例如命令名稱、操作命令的key等。
記錄AOF日誌最重要的一點就是需要按照Leader處理Redis寫命令的順序進行記錄AOF日誌。為什麼需要按照Leader處理命令的順序記錄AOF日誌以及如果違反了這一點會導致怎樣的後果將會在第3節詳細介紹。當把一條Redis寫記錄寫進AOF日誌後,Leader才會向Redis-Client返回這個寫請求的結果。在重放階段,Leader會從AOF日誌的起始位置讀取Redis命令的同時還會把到達的Redis寫命令追加到這個日誌中。因此,當Leader追加Redis寫命令到AOF日誌的速度大於重放Redis命令的速度的時候無法達到同步階段的,同步階段會在3.1節中介紹。
3 數據一致性的保證
我們需要實現的一個目標是需要保證目標存儲的最終狀態要與在雲上的Redis存儲的狀態保持一致。數據的一致性對於業務服務來講至關重要,在某些情況下,業務不希望Redis存儲遷移完之後某個Key的數據發生了變化。下面將從遷移狀態、命令冪等、連接模型、轉發與AOF日誌四個方面來說明我們是如何保證數據一致的。
3.1 遷移狀態
使用遷移工具遷移Redis存儲到目標存儲需要經歷三個階段:準備階段、重放階段、同步階段。這三個階段之間息息相關,每一個階段都是為了數據一致的保證做鋪墊。
3.1.1 準備階段
在準備階段的時候,業務服務直接與Proxy進行通訊,如圖2-1所示。這時需要業務服務更改Redis服務的配置然後重啟業務。Proxy作為一個代理把Redis讀請求轉發到雲上的Redis,同時會記錄Redis寫請求的AOF日誌。在業務服務連接上Proxy上之後,選擇一個時間點生成Cloud-Redis的RDB文件,然後把RDB文件FLUSH到目標存儲。此時目標存儲已經有了RDB文件的中的數據,需要注意的是,這部分數據中包含了一部分新寫入的Redis數據,而這部分新寫入的數據還會存在AOF日誌中。當RDB文件全部FLUSH到目標存儲的時候,就可以進入重放階段了。
3.1.2 重放階段
在重放階段的時候,Proxy中的Leader會收集記錄在本地磁碟上的AOF日誌,然後按照AOF日誌中記錄的Index來重放命令。在重放階段的時候也會記錄Redis寫命令的AOF日誌,這意味著Leader在進行重放的時候AOF日誌也在增長。當某一刻AOF日誌重放完成時就進入到了同步階段。
3.1.3 同步階段
從重放階段切換到同步階段的時候,為了保證數據的一致,使用二階段提策略進行狀態的變更。首先Leader通知所有其他Proxy要準備進入同步階段了,這時所有Proxy的請求都處於Pending狀態。之後進入第二階段,同步階段。在同步階段時,Proxy就完全相當於一個代理了,Proxy會同步地轉發Redis請求到目標存儲,然後同步地返回結果給Redis客戶端。當遷移階段進入同步階段的時候,目標存儲與雲上的存儲中的數據完全一致,之後所有新的請求全部轉發到目標存儲上,這時業務服務就可以切換到目標存儲上去了。
3.2 命令的冪等性
在重放階段會把AOF中的日誌重放到目標存儲,重放這個操作可能會對某些Redis Key重複執行一些命令。我們需要保證重複執行的命令具有冪等性,也就是重複執行了這個命令不會改變Key的值。比如當我們在記錄AOF日誌的時候遇到了INCR命令,我們會把INCR轉換成SET命令。
3.3 連接模型
Proxy的一個職責就是轉發Redis請求,為了保證Redis客戶端視角與Redis存儲中數據的保持一致,Proxy在同一條Redis客戶端的連接上進行Redis請求轉發的時候使用一條同步的後端連接。當使用非同步連接模型的時候,可能會出現圖3-3的情形。在圖3-3中,當Client向Proxy同步地請求兩個SET命令時,對於客戶端的視角來說key的最終值應該為1。經過Proxy層轉發請求之後,Redis存儲中的值可能是2。Proxy在轉發請求和重放的時候都會使用同步連接。
3.4 轉發與AOF日誌
在準備階段和重放階段中,Leader會轉發Redis寫請求同時會記錄AOF日誌。為了保證到達同步狀態的時雲上的Redis存儲與目標存儲中的數據一致,Leader需要保證轉發Redis寫請求與記錄AOF日誌的順序一致。否則會導致圖3-4中的情況,Redis客戶端A向Proxy發送了一個SET key 1命令,與此同時另一個Redis客戶端B發送了一個SET key 2命令。經過Leader轉發,雲上的Redis的key的最終狀態是2, 而在AOF日誌中記錄請求的順序與轉發的順序相反,這樣在重放之後就導致目標存儲與雲上存儲的數據不一致的情況發生。
4 約束
在重放階段,AOF日誌會繼續增長。當重放的速度跟不上AOF日誌增長的速度時,永遠也達不到同步階段。Leader重放的速度就成為了整個遷移工具的性能瓶頸。
在準備階段,需要雲上Redis存儲的RDB文件。如果運行商無法提供RDB文件,那麼就無法進行遷移
5 其他遷移方案
6 優化
當Leader進行記錄AOF時,每一條Redis寫請求都會通過Raft協議來申請Index,這就導致了Redis寫請求的吞吐量受限於Raft協議。在記錄AOF日誌的時候,可以不通過Raft來申請Index而使用一個外部的數據,例如NOSQL等資料庫,這樣可以提高Redis寫請求的吞吐量。甚至根本不需要Index,而根據AOF日誌中記錄的順序來進行重放。
推薦閱讀:
※Flink失敗容忍之快照(checkpoint)機制
※Zookeeper 服務註冊發現方案
※Snowflake演算法
※FastDFS分散式文件系統安裝與使用