|
Scalable Open Financial Architecture Stack 是螞蟻金服自主研發(fā)的金融級(jí)分布式架構(gòu),包含了構(gòu)建金融級(jí)云原生架構(gòu)所需的各個(gè)組件,是在金融場(chǎng)景里錘煉出來(lái)的最佳實(shí)踐。
前言 SOFAJRaft 是一個(gè)基于 Raft 一致性算法的生產(chǎn)級(jí)高性能 Java 實(shí)現(xiàn),支持 MULTI-RAFT-GROUP,適用于高負(fù)載低延遲的場(chǎng)景。SOFAJRaft 是從百度的 braft 移植而來(lái),做了一些優(yōu)化和改進(jìn),感謝百度 braft 團(tuán)隊(duì)開(kāi)源了如此優(yōu)秀的 C++ Raft 實(shí)現(xiàn)。 GitHub 地址: https://github.com/alipay/sofa-jraft 之前,我們有一篇介紹 SOFAJRaft 的文章,可在文末獲得鏈接,延續(xù)這個(gè)內(nèi)容,今天的演講分為三部分,先簡(jiǎn)要介紹 Raft 算法,然后介紹 SOFAJRaft 的設(shè)計(jì),最后說(shuō)說(shuō)它的優(yōu)化。 分享嘉賓:力鯤 螞蟻金服 SOFAJRaft 核心成員 Raft 共識(shí)算法 Raft 是一種共識(shí)算法,其特點(diǎn)是讓多個(gè)參與者針對(duì)某一件事達(dá)成完全一致:一件事,一個(gè)結(jié)論。同時(shí)對(duì)已達(dá)成一致的結(jié)論,是不可推翻的??梢耘e一個(gè)銀行賬戶的例子來(lái)解釋共識(shí)算法:假如由一批服務(wù)器組成一個(gè)集群來(lái)維護(hù)銀行賬戶系統(tǒng),如果有一個(gè) Client 向集群發(fā)出“存 100 元”的指令,那么當(dāng)集群返回成功應(yīng)答之后,Client 再向集群發(fā)起查詢時(shí),一定能夠查到被存儲(chǔ)成功的這 100 元錢(qián),就算有機(jī)器出現(xiàn)不可用情況,這 100 元的賬也不可篡改。這就是共識(shí)算法要達(dá)到的效果。 Raft 算法和其他的共識(shí)算法相比,又有了如下幾個(gè)不同的特性:
共識(shí)算法有一個(gè)很典型的應(yīng)用場(chǎng)景就是復(fù)制狀態(tài)機(jī)。Client 向復(fù)制狀態(tài)機(jī)發(fā)送一系列能夠在狀態(tài)機(jī)上執(zhí)行的命令,共識(shí)算法負(fù)責(zé)將這些命令以 Log 的形式復(fù)制給其他的狀態(tài)機(jī),這樣不同的狀態(tài)機(jī)只要按照完全一樣的順序來(lái)執(zhí)行這些命令,就能得到一樣的輸出結(jié)果。所以這就需要利用共識(shí)算法保證被復(fù)制日志的內(nèi)容和順序一致。 Leader 選舉 復(fù)制狀態(tài)機(jī)集群在利用 Raft 算法保證一致性時(shí),要做的第一件事情就是 Leader 選舉。在講 Leader 選舉之前我們先要說(shuō)一個(gè)重要的概念:Term。Term 用來(lái)將一個(gè)連續(xù)的時(shí)間軸在邏輯上切割成一個(gè)個(gè)區(qū)間,它的含義類(lèi)似于“美國(guó)第 26 屆總統(tǒng)”這個(gè)表述中的“26”。 每一個(gè) Term 期間集群要做的第一件事情就是選舉 Leader。起初所有的 Server 都是 Follower 角色,如果 Follower 經(jīng)過(guò)一段時(shí)間( election timeout )的等待卻依然沒(méi)有收到其他 Server 發(fā)來(lái)的消息時(shí),F(xiàn)ollower 就可以認(rèn)為集群中沒(méi)有可用的 Leader,遂開(kāi)始準(zhǔn)備發(fā)起選舉。在發(fā)起選舉的時(shí)候 Server 會(huì)從 Follower 角色轉(zhuǎn)變成 Candidate,然后開(kāi)始嘗試競(jìng)選 Term + 1 屆的 Leader,此時(shí)他會(huì)向其他的 Server 發(fā)送投票請(qǐng)求,當(dāng)收到集群內(nèi)多數(shù)機(jī)器同意其當(dāng)選的應(yīng)答之后,Candidate 成功當(dāng)選 Leader。但是如下兩種情況會(huì)讓 Candidate 退回 (step down) 到 Follower,放棄競(jìng)選本屆 Leader:
當(dāng)然了,當(dāng)一個(gè) Leader 發(fā)現(xiàn)有 Term 更高的 Leader 時(shí)也會(huì)退回到 Follower 狀態(tài)。 當(dāng)選舉 Leader 成功之后,整個(gè)集群就可以向外提供正常讀寫(xiě)服務(wù)了,如圖所示,集群由一個(gè) Leader 兩個(gè) Follower 組成,Leader 負(fù)責(zé)處理 Client 發(fā)起的讀寫(xiě)請(qǐng)求,同時(shí)還要跟 Follower 保持心跳或者把 Log 復(fù)制給 Follower。 Log 復(fù)制 下面我們就詳細(xì)說(shuō)一下 Log 復(fù)制。我們之前已經(jīng)說(shuō)了 Log 就是 Client 發(fā)送給復(fù)制狀態(tài)機(jī)的一系列命令。這里我們?cè)倥e例解釋一下 Log,比如我們的復(fù)制狀態(tài)機(jī)要實(shí)現(xiàn)的是一個(gè)銀行賬戶系統(tǒng),那么這個(gè) Log 就可以是 Client 發(fā)給賬戶系統(tǒng)的一條存錢(qián)的命令,比如“存 100 元錢(qián)”。 Leader 與 Follower 之間的日志復(fù)制是共識(shí)算法運(yùn)用于復(fù)制狀態(tài)機(jī)的重要目的,在 Raft 算法中 Log 由 TermId、LogIndex、LogValue 這三要素構(gòu)成,在這張圖上每一個(gè)小格代表一個(gè) Log。當(dāng) Leader 在向 Follower 復(fù)制 Log 的時(shí)候,F(xiàn)ollower 還需要對(duì)收到的 Log 做檢查,以確保這些 Log 能和本地已有的 Log 保持連續(xù)。我們之前說(shuō)了,Raft 算法是要嚴(yán)格保證 Log 的連續(xù)性的,所以 Follower 會(huì)拒絕無(wú)法和本地已有 Log 保持連續(xù)的復(fù)制請(qǐng)求,那么這種情況下就需要走 Log 恢復(fù)的流程??傊?,Log 復(fù)制的目的就是要讓所有的 Server 上的 Log 無(wú)論在內(nèi)容上還是在順序上都要保持完全一致,這樣才能保證所有狀態(tài)機(jī)執(zhí)行結(jié)果一致。 目前已經(jīng)有一些很優(yōu)秀的對(duì) Raft 的實(shí)現(xiàn),比如 C++ 寫(xiě)的 braft,Go 寫(xiě)的 etcd,Rust 寫(xiě)的 TiKV。當(dāng)然了,SOFAJRaft 并不是 Raft 算法的第一個(gè) Java 實(shí)現(xiàn),在我們之前已經(jīng)有了很多項(xiàng)目。但是經(jīng)過(guò)我們的評(píng)估,覺(jué)得目前還是沒(méi)有一個(gè) Raft 的 Java 實(shí)現(xiàn)庫(kù)類(lèi)能夠滿足螞蟻生產(chǎn)環(huán)境的要求,這也是我們?nèi)?xiě) SOFAJRaft 的主要原因。 SOFAJRaft 介紹 接下來(lái)我們介紹 SOFAJRaft。 SOFAJRaft 是基于 Raft 算法的生產(chǎn)級(jí)高性能 Java 實(shí)現(xiàn),支持 MULTI-RAFT-GROUP。從去年 3 月開(kāi)發(fā)到今年 2 月完成,并在今年 3 月開(kāi)源。應(yīng)用場(chǎng)景有 Leader 選舉、分布式鎖服務(wù)、高可靠的元信息管理、分布式存儲(chǔ)系統(tǒng),目前使用案例有 RheaKV,這是 SOFAJRaft 中自帶的一個(gè)分布式 KV 存儲(chǔ),還有今天開(kāi)源的 SOFA 服務(wù)注冊(cè)中心中的元信息管理模塊也是用到了 SOFAJRaft,除此之外還有一些內(nèi)部的項(xiàng)目也有使用,但是因?yàn)闆](méi)有開(kāi)源,所以就不再詳述了。 這張圖就是 SOFAJRaft 的設(shè)計(jì)圖,Node 代表了一個(gè) SOFAJRaft Server 節(jié)點(diǎn),這些方框代表他內(nèi)部的各個(gè)模塊,我們依然用之前的銀行賬戶系統(tǒng)舉例來(lái)說(shuō)明 SOFAJRaft 的各模塊是如何工作的。 當(dāng) Client 向 SOFAJRaft 發(fā)來(lái)一個(gè)“存 100 元”的命令之后,Node 的 Log 存儲(chǔ)模塊首先將這個(gè)命令以 Log 的形式存儲(chǔ)到本地,同時(shí) Replicator 會(huì)把這個(gè) Log 復(fù)制給其他的 Node,Replicator 是有多個(gè)的,集群中有多少個(gè) Follower 就會(huì)有多少個(gè) Replicator,這樣就能實(shí)現(xiàn)并發(fā)的日志復(fù)制。當(dāng) Node 收到集群中半數(shù)以上的 Node 返回的“復(fù)制成功” 的響應(yīng)之后,就可以把這條 Log 以及之前的 Log 有序的送到狀態(tài)機(jī)里去執(zhí)行了。狀態(tài)機(jī)是由用戶來(lái)實(shí)現(xiàn)的,比如我們現(xiàn)在舉的例子是銀行賬戶系統(tǒng),所以狀態(tài)機(jī)執(zhí)行的就是賬戶金額的借貸操作。如果 SOFAJRaft 在別的場(chǎng)景中使用,狀態(tài)機(jī)就會(huì)有其他的執(zhí)行方式。 Meta Storage 是用來(lái)存儲(chǔ)記錄 Raft 實(shí)現(xiàn)的內(nèi)部狀態(tài),比如當(dāng)前 Term、 投票給哪個(gè)節(jié)點(diǎn)等信息。 Snapshot 是快照,所謂快照就是對(duì)數(shù)據(jù)當(dāng)前值的一個(gè)記錄,Leader 生成快照有這么幾個(gè)作用:
剛才我們說(shuō)的是一個(gè)節(jié)點(diǎn)內(nèi)部的情況,那在 Raft Group 中至少需要 3 個(gè)節(jié)點(diǎn),所以這是一個(gè)三副本的架構(gòu)圖。 我們會(huì)因?yàn)楦鞣N各樣的需求而去構(gòu)建一個(gè) Raft 集群,如果你的目標(biāo)是實(shí)現(xiàn)一個(gè)存儲(chǔ)系統(tǒng)的話,那單個(gè) Raft 集群可能沒(méi)有辦法承載你所有的存儲(chǔ)需求;如果你的目標(biāo)是實(shí)現(xiàn)一個(gè)為用戶請(qǐng)求提供 Service 的系統(tǒng)的話,因?yàn)?Raft 集群內(nèi)只有 Leader 提供讀寫(xiě)服務(wù),所以讀寫(xiě)也會(huì)形成單點(diǎn)的瓶頸。因此為了支持水平擴(kuò)展,SOFAJRaft 提供了 Multi-Group 部署模式。如圖所示,我們可以按某種 Key 進(jìn)行分片部署,比如用戶 ID,我們讓 Group 1 對(duì) [0, 10000) 的 ID 提供服務(wù),讓 Group 2 對(duì) [10000, 20000) 的 ID 提供服務(wù),以此類(lèi)推。 SOFAJRaft 特性 這是我們所支持的 Raft 特性,其中:
SOFAJRaft 定位是生產(chǎn)級(jí)的 Raft 算法實(shí)現(xiàn),所以除了幾百個(gè)單元測(cè)試以及部分 Chaos 測(cè)試之外, SOFAJRaft 還使用 jepsen 這個(gè)分布式驗(yàn)證和故障注入測(cè)試框架模擬了很多種情況,都已驗(yàn)證通過(guò):
網(wǎng)絡(luò)分區(qū)包括兩種,一種是非對(duì)稱(chēng)網(wǎng)絡(luò)分區(qū),一種是對(duì)稱(chēng)網(wǎng)絡(luò)分區(qū)。 在對(duì)稱(chēng)網(wǎng)絡(luò)分區(qū)中,S2 和其他節(jié)點(diǎn)通信中斷,由于無(wú)法和 Leader 通信,導(dǎo)致它不斷嘗試競(jìng)選 Leader,這樣等到網(wǎng)絡(luò)恢復(fù)的時(shí)候,S2 由于之前的不斷嘗試,其 Term 已經(jīng)高于 Leader 了。這會(huì)迫使 S1 退回到 Follower 狀態(tài),集群重新進(jìn)行選舉。為避免這種由于對(duì)稱(chēng)網(wǎng)絡(luò)分區(qū)造成的不必要選舉,SOFAJRaft 增加了預(yù)投票(pre-vote),一個(gè) Follower 在發(fā)起投票前會(huì)先嘗試預(yù)投票,只有超過(guò)半數(shù)的機(jī)器認(rèn)可它的預(yù)投票,它才能繼續(xù)發(fā)起正式投票。在上面的情況中,S2 在每次發(fā)起選舉的時(shí)候會(huì)先嘗試預(yù)選舉,由于在預(yù)選舉中它依然得不到集群內(nèi)多數(shù)派的認(rèn)可,所以預(yù)投票無(wú)法成功,S2 也就不會(huì)發(fā)起正式投票了,因此他的 Term 也就不會(huì)在網(wǎng)絡(luò)分區(qū)的時(shí)候持續(xù)增加了。 在非對(duì)稱(chēng)網(wǎng)絡(luò)分區(qū)中,S2 和 Leader S1 無(wú)法通信,但是它和另一個(gè) Follower S3 依然能夠通信。在這種情況下,S2 發(fā)起預(yù)投票得到了 S3 的響應(yīng),S2 可以發(fā)起投票請(qǐng)求。接下來(lái) S2 的投票請(qǐng)求會(huì)使得 S3 的 Term 也增加以至于超過(guò) Leader S1(S3 收到 S2 的投票請(qǐng)求后,會(huì)相應(yīng)把自己的 Term 提升到跟 S2 一致),因此 S3 接下來(lái)會(huì)拒絕 Leader S1 的日志復(fù)制。為解決這種情況,SOFAJRaft 在 Follower 本地維護(hù)了一個(gè)時(shí)間戳來(lái)記錄收到 Leader 上一次數(shù)據(jù)更新的時(shí)間,F(xiàn)ollower S3 只有超過(guò) election timeout 之后才允許接收預(yù)投票請(qǐng)求,這樣也就避免了 S2 發(fā)起投票請(qǐng)求。 SOFAJRaft 優(yōu)化 接下來(lái)我們說(shuō)一下 SOFAJRaft 的優(yōu)化。
為了提供支持生產(chǎn)環(huán)境運(yùn)行的高性能,SOFAJRaft 主要做了如下幾部分的性能優(yōu)化,其中:
下面我們?cè)僬f(shuō)說(shuō)另外三項(xiàng):批量化、復(fù)制流水線以及線性一致讀。 批量化是性能優(yōu)化最常用的手段之一。SOFAJRaft 通過(guò)批量化的手段合并 IO 請(qǐng)求、減少方法調(diào)用和上下文切換,具體包括批量提交 Task、批量網(wǎng)絡(luò)發(fā)送、本地 IO 批量寫(xiě)入以及狀態(tài)機(jī)批量應(yīng)用。值得一提的是 SOFAJRaft 主要是通過(guò) Disruptor 來(lái)實(shí)現(xiàn)批量的消費(fèi)模型,通過(guò)這種 Ring Buffer 的方式既可以實(shí)現(xiàn)批量消費(fèi),又不需要為了攢批而等待。 復(fù)制流水線主要是利用 Pipeline 的通信方式來(lái)提高日志復(fù)制的效率,如果 Leader 跟 Followers 節(jié)點(diǎn)的 Log 同步是串行 Batch 的方式,那么每個(gè) Batch 發(fā)送之后需要等待 Batch 同步完成之后才能繼續(xù)發(fā)送下一批(ping-pong), 這樣會(huì)導(dǎo)致較長(zhǎng)的延遲。通過(guò) Leader 跟 Followers 節(jié)點(diǎn)之間的 Pipeline 復(fù)制可以有效降低更新的延遲, 提高吞吐。
什么是線性一致讀呢?簡(jiǎn)單來(lái)說(shuō)就是要在分布式環(huán)境中實(shí)現(xiàn) Java volatile 語(yǔ)義的效果,也就是說(shuō)當(dāng)一個(gè) Client 向集群發(fā)起寫(xiě)操作的請(qǐng)求并且得到成功響應(yīng)之后,該寫(xiě)操作的結(jié)果要對(duì)所有后來(lái)的讀請(qǐng)求可見(jiàn)。和 volatile 的區(qū)別是 volatile 是實(shí)現(xiàn)線程之間的可見(jiàn),而 SOFAJRaft 需要實(shí)現(xiàn) Server 之間的可見(jiàn)。實(shí)現(xiàn)這個(gè)目的最常規(guī)的辦法是走 Raft 協(xié)議,將讀請(qǐng)求同樣按照 Log 處理,通過(guò) Log 復(fù)制和狀態(tài)機(jī)執(zhí)行來(lái)得到讀結(jié)果,然后再把結(jié)果返回給 Client。這種辦法的缺點(diǎn)是需要 Log 存儲(chǔ)、復(fù)制,這樣會(huì)帶來(lái)刷盤(pán)開(kāi)銷(xiāo)、存儲(chǔ)開(kāi)銷(xiāo)、網(wǎng)絡(luò)開(kāi)銷(xiāo),因此在讀操作很多的場(chǎng)景下對(duì)性能影響很大。所以 SOFAJRaft 采用 ReadIndex 來(lái)替代走 Raft 狀態(tài)機(jī)的方案,簡(jiǎn)單來(lái)說(shuō)就是依靠這樣的原則直接從 Leader 讀取結(jié)果:所有已經(jīng)復(fù)制到多數(shù)派上的 Log(可視為寫(xiě)操作)就可以被視為安全的 Log,Leader 狀態(tài)機(jī)只要按序執(zhí)行到這條 Log 之后,該 Log 所體現(xiàn)的數(shù)據(jù)就能對(duì) Client 可見(jiàn)了。具體可以分解為以下四個(gè)步驟:
通過(guò) ReadIndex 的優(yōu)化,SOFAJRaft 已經(jīng)能夠達(dá)到 RPC 上限的 80%了。但是我們其實(shí)還可以再往前走一步,上面的步驟中可以看到第 3 步還是需要 Leader 通過(guò)向 Followers 發(fā)心跳來(lái)確認(rèn)自己的 Leader 身份,因?yàn)?Raft 集群中的 Leader 身份隨時(shí)可能發(fā)生改變。所以我們可以采用 LeaseRead 的方式把這一步 RPC 省略掉。租約可以理解為集群會(huì)給 Leader 一段租期(lease)的身份保證,在此期間 Leader 的身份不會(huì)被剝奪,這樣當(dāng) Leader 收到讀請(qǐng)求之后,如果發(fā)現(xiàn)租期尚未到期,就無(wú)需再通過(guò)和 Followers 通信來(lái)確認(rèn)自己的 Leader 身份,這樣就可以跳過(guò)第 3 步的網(wǎng)絡(luò)通信開(kāi)銷(xiāo)。通過(guò) LeaseRead 優(yōu)化,SOFAJRaft 幾乎已經(jīng)能夠達(dá)到 RPC 的上限。但是通過(guò)時(shí)鐘維護(hù)租期本身并不是絕對(duì)的安全(時(shí)鐘漂移問(wèn)題),所以 SOFAJRaft 中默認(rèn)配置是線性一致讀,因?yàn)橥ǔG闆r下線性一致讀性能已足夠好。 性能 ![]() 這是我們性能測(cè)試的情況,測(cè)試條件如下:
可以看到在開(kāi)啟復(fù)制流水線之后,性能可以提升大約 30%。而當(dāng)復(fù)制流水線和 Client-Batching 都開(kāi)啟之后,8 臺(tái) Client 能夠達(dá)到 40w+ ops。 目前 SOFARaft 最新的版本是 v1.2.4,由于 Raft 算法本身也比較復(fù)雜,而且 SOFAJRaft 在實(shí)現(xiàn)中還做了很多優(yōu)化,所以如果對(duì)今天的講演有什么不清楚的地方,歡迎通過(guò) SOFAJRaft wiki 繼續(xù)了解更多細(xì)節(jié),另外我們還有一個(gè)如何使用 SOFAJRaft 的示例,在 wiki 上也有詳細(xì)的說(shuō)明。除此之外,家純同學(xué)寫(xiě)過(guò)一篇很詳細(xì)的介紹文章《螞蟻金服開(kāi)源 SOFAJRaft:生產(chǎn)級(jí) Java Raft 算法庫(kù)》,大家也可以看一看。 歡迎 Star SOFAJRaft 幫助我們改進(jìn)。 文中涉及到的相關(guān)鏈接
歡迎加入,參與 SOFAJRaft 源碼解析
本文為 SOFAJRaft 的初步介紹,主要還是希望大家對(duì) SOFAJRaft 有一個(gè)初步的認(rèn)識(shí)和了解。同時(shí),我們開(kāi)啟了《剖析 | SOFAJRaft 實(shí)現(xiàn)原理》系列,會(huì)逐步詳細(xì)介紹各個(gè)部分的代碼設(shè)計(jì)和實(shí)現(xiàn),預(yù)計(jì)按照如下的目錄進(jìn)行:
|
|
|
來(lái)自: airen89 > 《sofajraft》