Raft 算法在分佈式存儲系統 Curve 中的實踐

做爲網易數帆開源的高性能、高可用、高可靠的新一代分佈式存儲系統,Curve對於多副本數據同步、負載均衡、容災恢復方面都有較高的要求。網易數帆存儲團隊選用Raft算法做爲Curve底層一致性協議,並基於Raft的特性,實現了異常狀況下的數據遷移和自動恢復。本文首先簡要介紹一下Raft算法的一些基本概念和術語,再詳細介紹其在Curve中的實踐。node

Raft一致性算法介紹

Raft算法中,有Leader、Follower、Candidate三種角色,它們之間的轉換關係以下圖:git

在同一時刻,只能有一個Leader,當選Leader後到發起下一次選舉稱爲一個任期(term),Leader負責經過心跳向follower保持統治,並同步數據給Follower。Follower發起選舉的前提是超時未收到Leader的心跳。github

Leader競選:RAFT是一種Majority的協議,即贏得選舉的條件是得到包括本身之內的大多數節點的投票。Follower超時未收到Leader的心跳,則會成爲Candidate,發起新一輪的選舉。每一個節點在每一個Term內只能投一次票,且服從先到先服務原則。爲了不多個Follower同時超時,raft中的選舉超時時間是一個固定時間加一個隨機時間。算法

日誌複製:在任期內,Leader接收來自client的請求,並將其封裝成一個日誌條目(Raft Log Entry)把它append到本身的raft log中,而後並行地向其它服務器發起AppendEntries RPC,當肯定該entry被大多數節點成功複製後(這個過程叫commit),就能夠執行命令(這一步叫apply),並返回給client結果。log entry由三個部分組成,分別是:一、log index,二、log所屬的term,三、要執行的命令。安全

配置變動:在Raft中,稱複製組有哪些成員稱爲配置,配置並非固定的,會根據需求增刪節點或異常狀況下須要替換掉有問題的節點。從一個配置直接切換到另外一個配置是不安全的,由於不一樣的服務器會在不一樣的時間點進行切換。所以Raft配置變動時,會先建立一個特殊的log entry Cold,new,這條entry被commit後,會進入共同一致階段,即新舊配置一塊兒作決定。這時候,再生成一個Cnew的log entry,等這條entry被commit後,就能夠由新配置獨立作決定了。服務器

安裝快照:Raft快照指的是某個時刻保存下來的系統狀態的集合。快照有兩方面的做用:一個是日誌壓縮,打了快照以後,在此時刻以前的log entry就能夠刪除了。另外一個是啓動加速,系統起來的時候不須要從新回放全部日誌。當Leader同步日誌給Follower的時候,發現所需的log entry已經被快照刪掉了,便可經過發送InstallSnapshot RPC給Follower進行同步。app

Raft算法在Curve中的應用

Curve系統是一個分佈式存儲系統,在Curve系統中,數據分片的最小單位稱之爲Chunk,默認的Chunk大小是16MB。在大規模的存儲容量下,會產生大量的Chunk,如此衆多的Chunk,會對元數據的存儲、管理產生必定壓力。所以引入CopySet的概念。CopySet能夠理解爲一組ChunkServer的集合,一個Copyset管理多個Chunk,多副本間的同步和管理已Copyset爲單位組織。ChunkServer,Copyset和Chunk三者之間的關係以下圖:負載均衡

Curve copyset選用了braft做爲一致性協議的組件,使用方式爲multi-raft,即同一個機器,能夠屬於多個複製組,反過來講,一個機器上,能夠存在多個raft實例。基於braft,咱們實現了副本間數據同步,系統調度,輕量級raft快照等功能,下面一一詳細介紹。分佈式

副本間數據同步

CopysetNode在ChunkServer端封裝了braft node,具體關係以下圖所示:ide

Curve client發送一個寫請求時,三副本狀況下的具體的步驟以下:

  1. Client發送寫請求給Leader ChunkServer。

  2. ChunkServer收到請求,將請求封裝成一個log entry,提交給raft。

  3. braft模塊在本地持久化entry的同時發送entry給其餘副本(ChunkServer)。

  4. 本地持久化entry成功,且另外一個副本也落盤成功則commit。

  5. commit後執行apply,apply即執行咱們的寫盤操做。

在此過程當中,用戶的數據和操做,經過braft中的log entry的方式在副本間傳遞,進行數據同步。在三副本場景下,向上層返回的時間取決於兩個較快的副本的速度,所以能夠減小慢盤的影響。對於較慢的那個副本,leader也會經過無限重試的方式同步數據,所以在系統正常工做的前提下,最終三個副本的數據是一致的。

基於Raft的系統調度

在Curve中,ChunkServer按期向元數據節點MDS上報心跳,心跳中除了ChunkServer自身的一些統計信息,還包含ChunkServer上面的CopySet的統計信息,包含它的leader,複製組成員,是否有配置變動執行中,配置的epoch等。MDS基於這些統計信息,會生成一系列的Raft配置變動請求並下發給Copyset的leader所在的ChunkServer。

下發配置變動

Curve ChunkServer會按期向MDS上報心跳。MDS調度下發配置變動是在心跳的response中完成的。上報心跳的過程以下圖:

心跳是定時任務觸發的,ChunkServer除了上報一些本身的容量信息等統計信息外,還會上報Copyset的一些信息,好比leader,成員,epoch,是否有進行中的配置變動等。

MDS在心跳response中下發配置變動的過程以下圖:

ChunkServer收到response後,解析其中的配置變動信息,並下發給每一個Copyset。

Curve epoch

epoch同步配置。MDS生成的調度信息,是由後臺定時任務觸發,並在ChunkServer下一次請求到來時在response中下發的,所以MDS下發的配置變動有多是過時的。爲了實現MDS和ChunkServer之間的配置同步,Curve引入了epoch機制,初始狀態,epoch初始爲0,每進行一次配置變動(包括leader變動),epoch都會加一。當MDS中的epoch等於ChunkServer端的epoch時,即將下發的配置變動才被認爲是有效的。epoch與term有何區別?term用於表示Leader的任期,即只跟選舉有關,而epoch是與配置變動相關的,也包括了Leader選舉這種狀況。

epoch的更新。epoch是在ChunkServer端更新的,braft在實現上提供了一個用戶狀態機,在braft內部發生變化,好比apply,出錯,關閉,打快照,加載快照,leader變動,配置變動等時會調用用戶狀態機中對應的函數。Curve copyset經過繼承的方式實現這個用戶狀態機來完成與braft的交互,epoch是在on_configuration_committed函數中加一的。在braft中,當Leader變動的時候會把當前的配置再提交一遍,所以在on_configuration_committed中增長epoch便可保證在配置發生變化或者Leader變動時epoch順序遞增。

epoch的持久化。在MDS端,epoch隨着CopySet的其餘信息一塊兒持久化在etcd中。ChunkServer也對epoch進行了持久化,可是ChunkServer中持久化epoch並非每次epoch發生變化都須要持久化的。這是利用了raft的日誌回放和快照功能。考慮如下兩種狀況:

  1. 假設raft沒有打快照,那麼就不須要持久化epoch,由於全部操做日誌,包括配置變動的entry都已持久化,當服務重啓的時候,回放這些日誌的時候會依次再調用一遍on_configuration_committed,最後epoch會恢復到重啓前的值。

  2. 當raft有快照時,打快照前的entry都會被刪除,就不能經過上面的方式回放,所以必需要持久化。但咱們只須要在打raft快照時持久化epoch的當前值便可。這樣當系統重啓的時候,會先安裝raft快照,安裝後epoch恢復到快照時的值,再經過執行後面的log entry,最終epoch恢復到重啓前的值。在打快照時更新epoch是在on_snapshot_save函數中完成的。

Raft輕量級快照

上面介紹Raft算法的時候介紹過,Raft須要定時打快照,以清理老的log entry,不然Raft日誌會無限增加下去。打快照的時候須要保存系統當前的狀態,對於Curve塊存儲場景來講,系統狀態就是Chunk當前的數據。直觀的方案是將打快照時刻的所有chunk拷貝一遍備份起來。可是這樣有兩個問題:

  1. 空間上要多出一倍,空間浪費很是嚴重。

  2. Curve默認打快照的間隔是30分鐘一次,這種方案下會有頻繁的數據拷貝,對磁盤形成很大的壓力,影響正常的IO。

所以,Curve中使用的Raft快照是輕量級的,即打快照的時候只保存當前的Chunk文件的列表,不對Chunk自己作備份。具體流程以下:

這樣操做,Follower下載到的chunk文件不是打快照時的狀態,而是最新的狀態,在回放日誌的時候,會把這些新數據再寫一遍。但這對於咱們的場景是能夠接受的,由於底層都是覆蓋寫是冪等的,即寫一次和寫屢次結果是一致的。

小結

本篇文章首先介紹了Raft一致性算法的一些基本概念。隨後介紹了Raft算法新一代高性能分佈式存儲系統Curve中的應用,分別從數據同步、系統調度和Raft快照三個角度介紹了Curve中使用Raft的細節。關於Curve的更多介紹詳見咱們的代碼倉庫:https://github.com/opencurve/curve。另外,Curve開源分享也在火熱進行中,分享的PPT能夠在https://github.com/opencurve/curve-meetup-slides倉庫中獲取。

做者簡介

查日蘇,網易數帆存儲團隊高級C++開發工程師,高性能分佈式存儲系統Curve核心開發。


相關視頻:

Curve核心組件之ChunkServer數據節點

相關文章:

Curve技術解析之MDS元數據管理

分佈式存儲開發:Curve中的內存管理

網易數帆存儲負責人親述:我眼中的Curve與Ceph

Curve的roadmap

分享預告:

Curve系列技術課程,每週五晚19:00 B站直播,本週五主題爲 Curve 快照克隆,敬請點擊收看:Curve開源技術直播(每週五19:00)

相關文章
相關標籤/搜索