您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“如何利用 Raft 實現(xiàn)日志復(fù)制”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“如何利用 Raft 實現(xiàn)日志復(fù)制”吧!
在 Raft 算法中,需要實現(xiàn)分布式一致性的數(shù)據(jù)被稱作日志,我們 Java 后端絕大部分人談到日志,一般會聯(lián)想到項目通過 log4j 等日志框架輸出的信息,而 Raft 算法中的數(shù)據(jù)提交記錄,他們會按照時間順序進行追加,Raft 也是嚴(yán)格按照時間順序并已一定的格式寫入日志文件中:
如上圖所示,Raft 的日志以日志項(LogEntry)的形式來組織,每個日志項包含一條命令、任期信息、日志項在日志中的位置信息(索引值 LogIndex)。
指令:由客戶端請求發(fā)送的執(zhí)行指令,有點繞口,我覺得理解成客戶端需要存儲的日志數(shù)據(jù)即可。
索引值:日志項在日志中的位置,需要注意索引值是一個連續(xù)并且單調(diào)遞增的整數(shù)。
任期編號:創(chuàng)建這條日志項的領(lǐng)導(dǎo)者的任期編號。
Raft 的復(fù)制過程大致如下:
領(lǐng)導(dǎo)者接收到客戶端發(fā)來的請求,創(chuàng)建一個新的日志項,并將其追加到本地日志中,接著領(lǐng)導(dǎo)者通過追加條目 RPC 請求,將新的日志項復(fù)制到跟隨者的本地日志中,當(dāng)領(lǐng)導(dǎo)者收到大多數(shù)跟隨者的成功響應(yīng)之后,則將這條日志項應(yīng)用到狀態(tài)機中,可以理解成該條日志寫成功了,最后領(lǐng)導(dǎo)者返回日志寫成功的消息響應(yīng)客戶端,流程如下圖所示:
可以看出,Raft 的復(fù)制過程中,領(lǐng)導(dǎo)者接收到大多數(shù)跟隨者成功響應(yīng),并且將日志項應(yīng)用到狀態(tài)機之后,不需要將結(jié)果響應(yīng)給跟隨者,而是直接將成功消息響應(yīng)給客戶端,這是一種優(yōu)化方式,同時 Raft 會在下一次 RPC 追加日志請求中附加上本次的日志項信息。
以上僅僅只是一種沒有發(fā)生任何問題的復(fù)制過程,在這過程中難免會發(fā)生節(jié)點宕機等問題,在這種情況下,Raft 是如何處理的呢?
上面講到,在正常情況下,領(lǐng)導(dǎo)者的日志追加 RPC 請求響應(yīng)都成功的情況下,領(lǐng)導(dǎo)人和跟隨者的日志保持一致性。然而在領(lǐng)導(dǎo)者突然宕機的情況下有可能會造成領(lǐng)導(dǎo)者與跟隨者日志不一致的情況,這種情況會隨著后續(xù)領(lǐng)導(dǎo)者一些列宕機的情況下加劇問題的嚴(yán)重:
注:例子來源于 Raft 論文。
如上所示,當(dāng)一個領(lǐng)導(dǎo)者成功當(dāng)選時,跟隨者有可能是 a-f 的情況:
a-b 表示跟隨者的日志項落后于當(dāng)前領(lǐng)導(dǎo)者;
c-d 表示跟隨者有些日志項沒有被提交;
e-f 情況稍微有點復(fù)雜,以上兩種情況它們都存在。
下面我來還原上面圖所表示的情況是怎么發(fā)生的:
假設(shè)一開始 e 為領(lǐng)導(dǎo)者,在任期 2 時,f 被推選為領(lǐng)導(dǎo)者,寫入了若干日志項之后,在追加 RPC 請求中崩潰了,重啟后又被選舉為領(lǐng)導(dǎo)者(任期號 3),又在寫入了若干日志項之后奔潰了;e 此時又重新選舉為領(lǐng)導(dǎo)者(任期號為 4),成功復(fù)制了若干日志項,同時還有一部分沒有成功追加到大多數(shù)跟隨者又崩潰了,同時跟隨者 b 復(fù)制了一部分日志項之后崩潰了;假設(shè) a 在任期 5 時被選舉為領(lǐng)導(dǎo)者,c 在任期 6 時被選舉為領(lǐng)導(dǎo)者,還未全部將本地日志復(fù)制到其他跟隨者之前又崩潰了,在任期 7 時 d 被選擇為領(lǐng)導(dǎo)者,寫入了若干日志項之后,在追加 RPC 請求中崩潰了,最后形成了上圖的情況。
面對以上的情況,Raft 是如何解決日志的一致性呢?
在 Raft 的日志機制中,為了簡化日志一致性的行為,有以下兩點非常重要的特性:
如果在不同的日志中的兩個條目擁有相同的索引和任期號,那么他們存儲了相同的指令。
如果在不同的日志中的兩個條目擁有相同的索引和任期號,那么他們之前的所有日志條目也全部相同。
第一個特性是因為 Raft 日志項在日志中不會改變,因此只要日志項只要是索引值和任期號相同,就可以認(rèn)為他們是存儲了相同的指令數(shù)據(jù)信息。
第二個特性是因為領(lǐng)導(dǎo)者會通過強制覆蓋的方式讓跟隨者復(fù)制自己的日志來解決日志不一致的問題,領(lǐng)導(dǎo)者在追加 RPC 請求過程中會附帶需要復(fù)制的日志以及前一個日志項相關(guān)信息,如果跟隨者匹配不到包含相同索引位置和任期號的日志項,那么他就會拒絕接收新的日志條目,接著領(lǐng)導(dǎo)者會繼續(xù)遞減要復(fù)制的日志項索引值,直至找到相同索引和任期號的日志項,最后就直接覆蓋跟隨者之后的日志項??烧J(rèn)為兩個條目擁有相同的索引和任期號,那么他們之前的所有日志條目也全部相同。
因此,Raft 的日志追加大致可分為兩個步驟:
領(lǐng)導(dǎo)者找到跟隨者與自己相同的最大日志項,這意味著跟隨者之前的日志都與領(lǐng)導(dǎo)者的日志相同;
領(lǐng)導(dǎo)者強制覆蓋之后不一致的日志,實現(xiàn)日志的一致性。
下面我用一個例子充分表達 Raft 在日志復(fù)制過程中是如何進行日志強制覆蓋的。
假設(shè)有一個領(lǐng)導(dǎo)者和一個跟隨者,他們的日志項復(fù)制情況如下:
可以看出,跟隨者在任期號 3 時是領(lǐng)導(dǎo)者,在追加日志過程中崩潰了,重啟之后成為跟隨者,隨后新的領(lǐng)導(dǎo)者向其追加日志,此時他的任期號為 3 最后的一個日志項將被覆蓋。
先來看下 Raft 追加條目 RPC 的請求參數(shù):
參數(shù) | 描述 |
---|---|
term | 領(lǐng)導(dǎo)者的任期 |
leaderId | 領(lǐng)導(dǎo)者ID 因此跟隨者可以對客戶端進行重定向(譯者注:跟隨者根據(jù)領(lǐng)導(dǎo)者id把客戶端的請求重定向到領(lǐng)導(dǎo)者,比如有時客戶端把請求發(fā)給了跟隨者而不是領(lǐng)導(dǎo)者) |
prevLogIndex | 緊鄰新日志條目之前的那個日志條目的索引 |
prevLogTerm | 緊鄰新日志條目之前的那個日志條目的任期 |
entries[] | 需要被保存的日志條目(被當(dāng)做心跳使用是 則日志條目內(nèi)容為空;為了提高效率可能一次性發(fā)送多個) |
leaderCommit | 領(lǐng)導(dǎo)者的已知已提交的最高的日志條目的索引 |
領(lǐng)導(dǎo)者追加并覆蓋跟隨者過程如下:
領(lǐng)導(dǎo)者通過日志追加 RPC 請求,將當(dāng)前最新的要追加到跟隨者的日志項以及前一個它的 prevLogIndex=7、prevLogTerm=3 等信息發(fā)送跟跟隨者;
跟隨者判斷當(dāng)前最新的日志的任期號與 prevLogTerm 不一致,拒絕追加;
領(lǐng)導(dǎo)者繼續(xù)遞減需要復(fù)制的日志項的索引值,此時 prevLogIndex=6、prevLogTerm=3;
跟隨者找到了 LogIndex=6、LogTerm=3 的日志項,跟隨者接受追加請求;
領(lǐng)導(dǎo)者接著會將跟隨者 LogIndex=6、LogTerm=3 的日志項之后的日志項進行追加并覆蓋。
到此,相信大家對“如何利用 Raft 實現(xiàn)日志復(fù)制”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。