這篇文章主要介紹“SOFAJRaft-RheaKV MULTI-RAFT-GROUP實(shí)現(xiàn)分析SOFAJRaft的實(shí)現(xiàn)原理”,在日常操作中,相信很多人在SOFAJRaft-RheaKV MULTI-RAFT-GROUP實(shí)現(xiàn)分析SOFAJRaft的實(shí)現(xiàn)原理問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”SOFAJRaft-RheaKV MULTI-RAFT-GROUP實(shí)現(xiàn)分析SOFAJRaft的實(shí)現(xiàn)原理”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)公司是一家專業(yè)提供尉犁企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站制作、成都網(wǎng)站建設(shè)、H5網(wǎng)站設(shè)計(jì)、小程序制作等業(yè)務(wù)。10年已為尉犁眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。
SOFAStackScalable Open Financial Architecture Stack 是螞蟻金服自主研發(fā)的金融級(jí)分布式架構(gòu),包含了構(gòu)建金融級(jí)云原生架構(gòu)所需的各個(gè)組件,是在金融場景里錘煉出來的最佳實(shí)踐。
RheaKV 是首個(gè)以 JRaft 為基礎(chǔ)實(shí)現(xiàn)的一個(gè)原生支持分布式的嵌入式鍵值(key、value)數(shù)據(jù)庫,現(xiàn)在本文將從 RheaKV 是如何利用 MULTI-RAFT-GROUP 的方式實(shí)現(xiàn) RheaKV 的高性能及容量的可擴(kuò)展性的,從而進(jìn)行全面的源碼、實(shí)例剖析。
通過對(duì) Raft 協(xié)議的描述我們知道:用戶在對(duì)一組 Raft 系統(tǒng)進(jìn)行更新操作時(shí)必須先經(jīng)過 Leader,再由 Leader 同步給大多數(shù) Follower。而在實(shí)際運(yùn)用中,一組 Raft 的 Leader 往往存在單點(diǎn)的流量瓶頸,流量高便無法承載,同時(shí)每個(gè)節(jié)點(diǎn)都是全量數(shù)據(jù),所以會(huì)受到節(jié)點(diǎn)的存儲(chǔ)限制而導(dǎo)致容量瓶頸,無法擴(kuò)展。
MULTI-RAFT-GROUP 正是通過把整個(gè)數(shù)據(jù)從橫向做切分,分為多個(gè) Region 來解決磁盤瓶頸,然后每個(gè) Region 都對(duì)應(yīng)有獨(dú)立的 Leader 和一個(gè)或多個(gè) Follower 的 Raft 組進(jìn)行橫向擴(kuò)展,此時(shí)系統(tǒng)便有多個(gè)寫入的節(jié)點(diǎn),從而分擔(dān)寫入壓力,圖如下:
cdn.nlark.com/yuque/0/2019/jpeg/325890/1557569369003-7d4762a0-2590-48bc-afc9-b4e53b520054.jpeg">
此時(shí)磁盤及 I/O 瓶頸解決了,那多個(gè) Raft Group 是如何協(xié)作的呢,我們接著往下看。
RheaKV 主要由 3 個(gè)角色組成:PlacementDriver(以下成為 PD) 、Store、Region。由于 RheaKV 支持多組 Raft,所以比單組場景多出一個(gè) PD 角色,用來調(diào)度以及收集每個(gè) Store 及 Region 的基礎(chǔ)信息。

PD 負(fù)責(zé)整個(gè)集群的管理調(diào)度、Region ID 生成等。此組件非必須的,如果不使用 PD,設(shè)置 PlacementDriverOptions 的 fake 屬性為 true 即可。PD 一般通過 Region 的心跳返回信息進(jìn)行對(duì) Region 調(diào)度,Region 處理完后,PD 則會(huì)在下一個(gè)心跳返回中收到 Region 的變更信息來更新路由及狀態(tài)表。
通常一個(gè) Node 負(fù)責(zé)一個(gè) Store,Store 可以被看作是 Region 的容器,里面存儲(chǔ)著多個(gè)分片數(shù)據(jù)。Store 會(huì)向 PD 主動(dòng)上報(bào) StoreHeartbeatRequest 心跳,心跳交由 PD 的 handleStoreHeartbeat 處理,里面包含該 Store 的基本信息,比如,包含多少 Region,有哪些 Region 的 Leader 在該 Store 等。
Region 是數(shù)據(jù)存儲(chǔ)、搬遷的最小單元,對(duì)應(yīng)的是 Store 里某個(gè)實(shí)際的數(shù)據(jù)區(qū)間。每個(gè) Region 會(huì)有多個(gè)副本,每個(gè)副本存儲(chǔ)在不同的 Store,一起組成一個(gè)Raft Group。Region 中的 Leader 會(huì)向 PD 主動(dòng)上報(bào) RegionHeartbeatRequest 心跳,交由 PD 的 handleRegionHeartbeat 處理,而 PD 是通過 Region 的 Epoch 感知 Region 是否有變化。
Muti-Raft-Group 的多 Region 是通過 RegionRouteTable 路由表組件進(jìn)行管理的,可通過 addOrUpdateRegion、removeRegion 進(jìn)行添加、更新、移除 Region,也包括 Region 的拆分。目前暫時(shí)還未實(shí)現(xiàn) Region 的聚合,后面會(huì)考慮實(shí)現(xiàn)。

“讓每組 Raft 負(fù)責(zé)一部分?jǐn)?shù)據(jù)。”
數(shù)據(jù)分區(qū)或者分片算法通常就是 Range 和 Hash,RheaKV 是通過 Range 進(jìn)行數(shù)據(jù)分片的,分成一個(gè)個(gè) Raft Group,也稱為 Region。這里為何要設(shè)計(jì)成 Range 呢?原因是 Range 切分是按照對(duì) Key 進(jìn)行字節(jié)排序后再做每段每段切分,像類似 scan 等操作對(duì)相近 key 的查詢會(huì)盡可能集中在某個(gè) Region,這個(gè)是 Hash 無法支持的,就算遇到單個(gè) Region 的拆分也會(huì)更好處理一些,只用修改部分元數(shù)據(jù),不會(huì)涉及到大范圍的數(shù)據(jù)挪動(dòng)。
當(dāng)然 Range 也會(huì)有一個(gè)問題那就是,可能會(huì)存在某個(gè) Region 被頻繁操作成為熱點(diǎn) Region。不過也有一些優(yōu)化方案,比如 PD 調(diào)度熱點(diǎn) Region 到更空閑的機(jī)器上,或者提供 Follower 分擔(dān)讀的壓力等。
Region 和 RegionEpoch 結(jié)構(gòu)如下:
class Region {
long id; // region id
// Region key range [startKey, endKey)
byte[] startKey; // inclusive
byte[] endKey; // exclusive
RegionEpoch regionEpoch; // region term
List<Peer> peers; // all peers in the region
}
class RegionEpoch {
// Conf change version, auto increment when add or remove peer
long confVer;
// Region version, auto increment when split or merge
long version;
}
class Peer {
long id;
long storeId;
Endpoint endpoint;
}Region.id:為 Region 的唯一標(biāo)識(shí),通過 PD 全局唯一分配。
Region.startKey、Region.endKey:這個(gè)表示的是 Region 的 key 的區(qū)間范圍 [startKey, endKey),特別值得注意的是針對(duì)最開始 Region 的 startKey,和最后 Region 的 endKey 都為空。
Region.regionEpoch:當(dāng) Region 添加和刪除 Peer,或者 split 等,此時(shí) regionEpoch 就會(huì)發(fā)生變化,其中 confVer 會(huì)在配置修改后遞增,version 則是每次有 split 、merge(還未實(shí)現(xiàn))等操作時(shí)遞增。
Region.peers:peers 則指的是當(dāng)前 Region 所包含的節(jié)點(diǎn)信息,Peer.id 也是由 PD 全局分配的,Peer.storeId 代表的是 Peer 當(dāng)前所處的 Store。
由于數(shù)據(jù)被拆分到不同 Region 上,所以在進(jìn)行多 key 的讀、寫、更新操作時(shí)需要操作多個(gè) Region,這時(shí)操作前我們需要得到具體的 Region,然后再單獨(dú)對(duì)不同 Region 進(jìn)行操作。我們以在多 Region上 scan 操作為例, 目標(biāo)是返回某個(gè) key 區(qū)間的所有數(shù)據(jù):
我們首先看 scan 方法的核心調(diào)用方法 internalScan 的異步實(shí)現(xiàn):
例如:com.alipay.sofa.jraft.rhea.client.DefaultRheaKVStore#scan(byte[], byte[], boolean, boolean)

我們很容易看到,在調(diào)用 scan 首先讓 PD Client 通過 RegionRouteTable.findRegionsByKeyRange 檢索 startKey、endKey 所覆蓋的 Region,最后返回的可能為多個(gè) Region,具體 Region 覆蓋檢索方法如下:

檢索相關(guān)變量定義如下:

我們可以看到整個(gè) RheaKV 的 range 路由表是通過 TreeMap 的進(jìn)行存儲(chǔ)的,正呼應(yīng)我們前面講過所有的 key 是通過對(duì)應(yīng)字節(jié)進(jìn)行排序存儲(chǔ)。對(duì)應(yīng)的 Value 為該 Region 的 RegionId,隨后我們通過 Region 路由 regionTable 查出即可。
現(xiàn)在我們得到 scan 覆蓋到的所有 Region:List<Region> 在循環(huán)查詢中我們看到有一個(gè)“retryCause -> {}”的 Lambda 表達(dá)式很容易看出這里是加持異常重試處理,后面我們會(huì)講到,接下來會(huì)通過 internalRegionScan 查詢每個(gè) Region 的結(jié)果。具體源碼如下:

這里也同樣有一個(gè)重試處理,可以看到代碼中根據(jù)當(dāng)前是否為 Region 節(jié)點(diǎn)來決定是本機(jī)查詢還是通過RPC進(jìn)行查詢,如果是本機(jī)則調(diào)用 rawKVStore.scan() 進(jìn)行本地直接查詢,反之通過 rheaKVRpcService 進(jìn)行 RPC 遠(yuǎn)程節(jié)點(diǎn)查詢。最后每個(gè) Region 查詢都返回為一個(gè) future,通過 FutureHelper.joinList 工具類 CompletableFuture.allOf 異步并發(fā)返回結(jié)果 List<KVEntry>。
我們再看看寫入具體流程。相比 scan 讀,put 寫相對(duì)比較簡單,只需要針對(duì) key 計(jì)算出對(duì)應(yīng) Region 再進(jìn)行存儲(chǔ)即可,我們可以看一個(gè)異步 put 的例子。
例如:com.alipay.sofa.jraft.rhea.client.DefaultRheaKVStore#put(java.lang.String, byte[])

我們可以發(fā)現(xiàn) put 基礎(chǔ)方法是支持 batch 的,即可成批提交。如未使用 batch 即直接提交,具體邏輯如下:

通過 pdClinet 查詢對(duì)應(yīng)存儲(chǔ)的 Region,并且通過 regionId 拿到 RegionEngine,再通過對(duì)應(yīng)存儲(chǔ)引擎 KVStore 進(jìn)行 put,整個(gè)過程同樣支持重試機(jī)制。我們再回過去看看 batch 的實(shí)現(xiàn),很容易發(fā)現(xiàn)利用到了 Disruptor 的 RingBuffer 環(huán)形緩沖區(qū),無鎖隊(duì)列為性能提供了保障,代碼現(xiàn)場如下:

什么時(shí)候 Region 會(huì)拆分?
前面我們有講過,PD 會(huì)在 Region 的 heartBeat 里面對(duì) Region 進(jìn)行調(diào)度,當(dāng)某個(gè) Region 里的 keys 數(shù)量超過預(yù)設(shè)閥值,我們即可對(duì)該 Region 進(jìn)行拆分,Store 的狀態(tài)機(jī) KVStoreStateMachine 即收到拆分消息進(jìn)行拆分處理。具體拆分源碼如下:
KVStoreStateMachine.doSplit 源碼如下:

StoreEngine.doSplit 源碼如下:

我們可以輕易的看到從原始 parentRegion 切分成 region 和 pRegion,并重設(shè)了 startKey、endKey 和版本號(hào),并添加到 RegionEngineTable 注冊到 RegionKVService,同時(shí)調(diào)用 pdClient.getRegionRouteTable().splitRegion() 方法進(jìn)行更新存儲(chǔ)在 PD 的 Region 路由表。
什么時(shí)候需要對(duì) Region 進(jìn)行合并?
既然數(shù)據(jù)過多需要進(jìn)行拆分,那 Region 進(jìn)行合并那就肯定是 2 個(gè)或者多個(gè)連續(xù)的 Region 數(shù)據(jù)量明顯小于絕大多數(shù) Region 容量則我們可以對(duì)其進(jìn)行合并。這一塊后面會(huì)考慮實(shí)現(xiàn)。
通過上面我們知道,一個(gè) Store 即為一個(gè)節(jié)點(diǎn),里面包含著一個(gè)或者多個(gè) RegionEngine,一個(gè) StoreEngine 通常通過 PlacementDriverClient 對(duì) PD 進(jìn)行調(diào)用,同時(shí)擁有 StoreEngineOptions 配置項(xiàng),里面配置著存儲(chǔ)引擎和節(jié)點(diǎn)相關(guān)配置。
我們以默認(rèn)的 DefaultRheaKVStore 加載 StoreEngine 為例,DefaultRheaKVStore 實(shí)現(xiàn)了 RheaKVStore 接口的基礎(chǔ)功能,從最開始 init 方法,根據(jù) RheaKVStoreOptions 加載了 pdClinet 實(shí)例,隨后加載 storeEngine。
在 StoreEngine 啟動(dòng)的時(shí)候,首先會(huì)去加載對(duì)應(yīng)的 StoreEngineOptions 配置,構(gòu)建對(duì)應(yīng)的 Store 配置,并且生成一致性讀的線程池 readIndexExecutor、快照線程池 snapshotExecutor、RPC 的線程池 cliRpcExecutor、Raft 的 RPC 線程池 raftRpcExecutor,以及存儲(chǔ) RPC 線程池 kvRpcExecutor、心跳發(fā)送器 HeartbeatSender 等,如果打開代碼,我們還能看到 metricsReportPeriod,打開配置可以進(jìn)行性能指標(biāo)監(jiān)控。
在 DefaultRheaKVStore 加載完所有工序之后,便可使用 get、set、scan 等操作,還包含對(duì)應(yīng)同步、異步操作。
在這個(gè)過程中里面的 StoreEngine 會(huì)記錄著 regionKVServiceTable、regionEngineTable,它們分別掌握著具體每個(gè)不同的 Region 存儲(chǔ)的操作功能,對(duì)應(yīng)的 key 即為 RegionId。
每個(gè)在 Store 里的 Region 副本中,RegionEngine 則是一個(gè)執(zhí)行單元。它里面記錄著關(guān)聯(lián)著的 StoreEngine 信息以及對(duì)應(yīng)的 Region 信息。由于它也是一個(gè)選舉節(jié)點(diǎn),所以也包含著對(duì)應(yīng)狀態(tài)機(jī) KVStoreStateMachine,以及對(duì)應(yīng)的 RaftGroupService,并啟動(dòng)里面的 RpcServer 進(jìn)行選舉同步。
這個(gè)里面有個(gè)transferLeadershipTo方法,這個(gè)可被調(diào)用用于平衡當(dāng)前節(jié)點(diǎn)分區(qū)的Leader,避免壓力重疊。
DefaultRegionKVService 是 RegionKVService 的默認(rèn)實(shí)現(xiàn)類,主要處理對(duì) Region 的具體操作。
需要特別講到的是,在具體的 RheaKV 操作時(shí),F(xiàn)ailoverClosure 擔(dān)任著比較重要的角色,也給整個(gè)系統(tǒng)增加了一定的容錯(cuò)性。假如在一次 scan 操作中,如果跨 Store 需要多節(jié)點(diǎn) scan 數(shù)據(jù)的時(shí)候,任何網(wǎng)絡(luò)抖動(dòng)都會(huì)造成數(shù)據(jù)不完整或者失敗情況,所以允許一定次數(shù)的重試有利于提高系統(tǒng)的可用性,但是重試次數(shù)不宜過高,如果出現(xiàn)網(wǎng)絡(luò)堵塞,多次 timeout 級(jí)別失敗會(huì)給系統(tǒng)帶來額外的壓力。這里只需要在 DefaultRheaKVStore 中,進(jìn)行配置 failoverRetries 設(shè)置次數(shù)即可。
PlacementDriverClient 接口主要由 AbstractPlacementDriverClient 實(shí)現(xiàn),然后 FakePlacementDriverClient、RemotePlacementDriverClient 為主要功能。FakePlacementDriverClient 是當(dāng)系統(tǒng)不需要 PD 的時(shí)候進(jìn)行 PD 對(duì)象的模擬,這里主要講到 RemotePlacementDriverClient。
RemotePlacementDriverClient 通過PlacementDriverOptions 進(jìn)行加載,并根據(jù)基礎(chǔ)配置刷新路由表;
RemotePlacementDriverClient 承擔(dān)著對(duì)路由表RegionRouteTable 的管控,例如獲取Store、路由、Leader節(jié)點(diǎn)信息等;
RemotePlacementDriverClient 還包含著 CliService,通過 CliService 外部可對(duì)復(fù)制節(jié)點(diǎn)進(jìn)行操作運(yùn)維,如addReplica、removeReplica、transferLeader。
由于很多傳統(tǒng)存儲(chǔ)中間件并不原生支持分布式,所以一直少有體感,Raft 協(xié)議是一套比較比較好理解的共識(shí)協(xié)議,SOFAJRaft 通俗易懂是一個(gè)非常好的代碼和工程范例,同時(shí) RheaKV 也是一套非常輕量化支持多存儲(chǔ)結(jié)構(gòu)可分片的嵌入式數(shù)據(jù)庫。寫一篇代碼分析文章也是一個(gè)學(xué)習(xí)和進(jìn)步的過程,由此我們也可以窺探到了一些數(shù)據(jù)庫的基礎(chǔ)實(shí)現(xiàn),祝愿社區(qū)能在 SOFAJRaft / RheaKV 基礎(chǔ)上構(gòu)建更加靈活和自治理的系統(tǒng)和應(yīng)用。
到此,關(guān)于“SOFAJRaft-RheaKV MULTI-RAFT-GROUP實(shí)現(xiàn)分析SOFAJRaft的實(shí)現(xiàn)原理”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
本文名稱:SOFAJRaft-RheaKVMULTI-RAFT-GROUP實(shí)現(xiàn)分析SOFAJRaft的實(shí)現(xiàn)原理
網(wǎng)頁地址:http://chinadenli.net/article36/gdjhsg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、關(guān)鍵詞優(yōu)化、網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈、虛擬主機(jī)、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)