日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

raft算法mysql主从复制_Etcd raft算法实现原理分析

發(fā)布時間:2023/12/10 数据库 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 raft算法mysql主从复制_Etcd raft算法实现原理分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1.1 主要概念

要實現(xiàn)集群數(shù)據(jù)的一致性,節(jié)點在進(jìn)行通信的時候必定需要遵守特定規(guī)則進(jìn)行數(shù)據(jù)校驗,而這些規(guī)則具體都是通過某些具有特定含義的屬性來實現(xiàn)的。為了讓對Raft 算法比較陌生的讀者對算法的關(guān)鍵概念有一個初步認(rèn)識,作者整理了算法中涉及的概念如下所示:

1.2 節(jié)點狀態(tài)

Raft 算法比較簡單,其中一個原因是節(jié)點的狀態(tài)少,一共只有三種角色,大大降低了角色間轉(zhuǎn)換的復(fù)雜性。這三種角色分別是Leader 、Follower 和Candidate (其實大部分時間只有Leader 和Follower 角色,因為Candidate 只是選舉的一個過渡角色),每種角色都有自己的運行規(guī)則。角色之間可以在一定條件下進(jìn)行角色轉(zhuǎn)換,狀態(tài)轉(zhuǎn)換如下圖所示:

l Follower:集群啟動時,每個Raft 節(jié)點初始以Follower的角色運行。他們各自維護(hù)了一個超時字段(ElectionTick屬性),如果在超時時間段內(nèi)Follower都沒有收到來自Leader的消息,Follower就知道這個時候集群中沒有Leader節(jié)點,這時它會把自己的角色轉(zhuǎn)換為Candidate,并發(fā)起領(lǐng)導(dǎo)人選舉。

l Candidate:處于Candidate狀態(tài)的節(jié)點會發(fā)起選舉的操作的操作,如果選舉成功,那它將成為下一任的領(lǐng)導(dǎo)人,如果選舉失敗,就轉(zhuǎn)化為Follower節(jié)點,負(fù)責(zé)處理Leader節(jié)點發(fā)起的請求,比如添加日志,心跳檢測等操作。在選舉過程中可能會有多個節(jié)點競爭領(lǐng)導(dǎo)人角色,并且多個選舉人獲得同樣多的選票。這時候大家都不能當(dāng)選Leader,經(jīng)過一定時間,Follower會重新發(fā)起一輪選舉,保證一次選舉最多只有一個領(lǐng)導(dǎo)人。這里面有一些優(yōu)化機(jī)制,比如每個候選人重新發(fā)起選舉的間隔時間是不一樣的,可以降低多次選票相同,重新選舉的問題。

l Leader:Leader節(jié)點主要負(fù)責(zé)集群中數(shù)據(jù)的管理,包括給Follower發(fā)送添加日志、心跳檢測(HeartbeatTick 屬性)等命令。在Raft 節(jié)點運行過程中可能會有這種情況,即當(dāng)前Leader節(jié)點宕機(jī)了,這時其他節(jié)點會重新選出一個Leader并且運行了一段時間,宕機(jī)的Leader節(jié)點恢復(fù)了,新的Leader會給老的Leader發(fā)送消息,這時老的Leader發(fā)現(xiàn)消息里面的任期(Term字段)比自己當(dāng)前的Term更大,老的Leader就知道自己已經(jīng)不是最新的領(lǐng)導(dǎo)人了,它會主動轉(zhuǎn)換為Follewer角色。

1.3領(lǐng)導(dǎo)人選舉

根據(jù)1.2 的介紹我們知道,Raft 節(jié)點在一啟動的時候,就初始化為Follower 角色,并且每個Follower 角色都有一個ElectionTick 的隨機(jī)超時屬性。在超時的時間內(nèi)如果Leader 對它們發(fā)起請求或者心跳檢測,Follower 節(jié)點會使自己轉(zhuǎn)變?yōu)镃andidate 進(jìn)行領(lǐng)導(dǎo)人選舉。在領(lǐng)導(dǎo)人選舉過程中,每個節(jié)點最多只能給一個節(jié)點投票,如果有兩個節(jié)點發(fā)起投票,先到的那個節(jié)點會獲得選票。這樣能夠保證每次選舉期間最多只有一個節(jié)點當(dāng)選。論文中節(jié)點發(fā)起選舉參數(shù)和接收者實現(xiàn)規(guī)則如下圖所示:

1. Follower首先給自己發(fā)送一個消息,使自己變?yōu)镃andidate角色,設(shè)置Term+1 然后并發(fā)的對集群的所有節(jié)點發(fā)送選舉的消息。集群中每個節(jié)點保存了一個數(shù)組,這個數(shù)組保存集群中所有節(jié)點的信息

2. 收到消息的Follower節(jié)點會對選舉的請求做出參數(shù)校驗和選舉響應(yīng),如果Candidate發(fā)送的消息里面的Term比自己當(dāng)前記錄的更大,并且Candidate擁有自己已經(jīng)更新的最新日志信息,就同意為Candidate投票,否則拒絕投票。

3. 如果Candidate收到大多數(shù)節(jié)點的投票響應(yīng),那Candidate就會成為新一任的Leader。給其他發(fā)送消息,并更

4. 如果Candidate沒有當(dāng)選Leader,就使自己轉(zhuǎn)變?yōu)镕ollower角色,聽從Leader的指令。

步驟1 描述的操作可能會出現(xiàn)一個問題;假如集群出現(xiàn)網(wǎng)絡(luò)錯誤,使得集群中的節(jié)點(A 、B 、C 、D 、E 五個節(jié)點)被分成兩部分,節(jié)點A 、B 、C 和節(jié)點D 、E 。這時D 、E 沒收到Leader 節(jié)點的消息會重新發(fā)起選取,但是由于不能獲得多數(shù)節(jié)點的選票,因此Term 會不斷增大。如圖3 所示,當(dāng)網(wǎng)絡(luò)回復(fù)的時候,D 、E 節(jié)點的Term 已經(jīng)比正常集群的Term 更大,因此節(jié)點D 或節(jié)點E 將當(dāng)選Leader ,導(dǎo)致節(jié)點A 、B 、C 在節(jié)點D 、E 宕機(jī)時期添加的正常數(shù)據(jù)被清除,從而破壞集群的歷史數(shù)據(jù)。

這個問題在數(shù)據(jù)一致性的應(yīng)用場景是不允許出現(xiàn)的,Raft 算法采用PreVote 算法解決了這個問題。PreVote 算法在Candidate 發(fā)起選舉之前,會首先向其他節(jié)點發(fā)送一個預(yù)投票信息,這時Term 沒有加1 ,如果超過半數(shù)節(jié)點同意了選舉請求,Candidate 才設(shè)置Term+1 ,真正發(fā)起領(lǐng)導(dǎo)人選舉投票。在上述環(huán)境中D 、E 節(jié)點是沒辦法獲得超過半數(shù)節(jié)點的選票,因此節(jié)點只能反復(fù)Prevote ,Term 卻沒有機(jī)會增加。當(dāng)網(wǎng)絡(luò)恢復(fù)的時候當(dāng)收到Node A 的消息,立刻使自己變成Follower 繼續(xù)為集群服務(wù)。

1.4日志復(fù)制

Raft 集群中Leader 節(jié)點負(fù)責(zé)與客戶端的交互,當(dāng)非Follower 節(jié)點接收到客戶端請求的時候,該節(jié)點會把該請求重定向到leader 節(jié)點,這種集中式的處理方式大大降低了集群和客戶端的交互復(fù)雜度。用戶的每次請求操作在Raft 集群中最終都是一個日志復(fù)制的過程,我們來看看一次簡單日志復(fù)制過程:

1. 客戶端給Leader節(jié)點發(fā)送一個 SET X = 5 的請求。

2. Leader節(jié)點收到客戶端請求,首先把SET X = 5的指令寫到日志中,然后把請求并發(fā)發(fā)送到所有Follower節(jié)點。

3. Follower節(jié)點收到Leader請求,也把SET X = 5 寫到日志中,然后響應(yīng)Leader 節(jié)點。

4. 當(dāng)Leader節(jié)點收到大部分Follower節(jié)點寫入成功后,把 SET X = 5 這個指令 commit 這個時候,Leader 節(jié)點上的X 才真正等于5,并發(fā)送commit 成功給所有Follower節(jié)點。

5. Follower節(jié)點收到Leader節(jié)點commit成功的信息,也commit,這個時候就實現(xiàn)了Raft集群數(shù)據(jù)的一致性。

在日志復(fù)制過程中,節(jié)點會對相應(yīng)參數(shù)進(jìn)行檢測,避免無效信息被加入到日志中造成集群中數(shù)據(jù)不一致。論文中日志發(fā)送參數(shù)和節(jié)點校驗規(guī)則如圖5 所示:

添加日志消息的參數(shù)及其含義解釋如下:

1. Term: 發(fā)送者的任期號。

2. LeaderId: 領(lǐng)導(dǎo)人的id。

3. PrevLogIndex: 之前Leader發(fā)送給Follower的最后一條日志索引,用于 Follower 確認(rèn)與 Leader 的日志是否完全一致。新添加的日志會在這個索引之后開始。

4. PrevLogTerm: prevLogIndex索引對應(yīng)的任期,用于檢測Term的合法性。

5. Entries[]: 發(fā)送的日志數(shù)組,一次可以發(fā)送多條日志以提高效率。

6. LeaderCommit: Leader節(jié)點已經(jīng)提交的最大日志索引,當(dāng)節(jié)點收到這個記錄的時候就知道,自提交日志到這個索引值是安全的。

消息接收者的數(shù)據(jù)校驗規(guī)則:

1. 節(jié)點接收到消息時,如果發(fā)現(xiàn)消息來源的Term 小于自己當(dāng)前記錄的currentTerm,直接忽略這個消息。

2. 節(jié)點會根據(jù)prevLogIndex找到自己對應(yīng)log的任期號term,如果和term和prevLogTerm不匹配,則直接返回false,不處理這個消息。

3. 檢測entries數(shù)組是否和自己已經(jīng)保存的log有沖突,如果有沖突,則刪除這個沖突索引之后的所有日志。

4. 把所有新的日志entries加到自己的日志中。

5. 如果leaderCommit > commitIndex(自己已提交的最大索引),則設(shè)置 commitIndex = min( leaderCommit, entries)數(shù)組的最后一個元素的索引。

◆◆

2. Etcd raft 源碼分析

◆◆

研究了Raft 的算法設(shè)計思想,接下來我們看Etcd 是怎么實現(xiàn)Raft 算法的。研究的Etcd 源碼為V3.1.20 版本,下載地址https://github.com/etcd-io/etcd 。為了使讀者對raft 算法代碼結(jié)構(gòu)有一個大致認(rèn)識,我根據(jù)raft 啟動過程畫出以下代碼調(diào)用圖。建議要看源碼的讀者可以對著以下流程自己進(jìn)入代碼走一遍,對etcd 的raft 源碼結(jié)構(gòu)有一個大致印象,不然后面的代碼可能會看的不知所云。

Etcd 的啟動方法在etcdmain/main.go 文件中,應(yīng)用啟動后,raft 相關(guān)會啟動兩個協(xié)程,一個運行內(nèi)層函數(shù)(raftnode.go 文件的node.run 方法) ,另一個運行外層函數(shù)(etcdserver/raft.go 文件中的start 方法)。內(nèi)層函數(shù)主要處理節(jié)點數(shù)據(jù)內(nèi)部的變更,外層函數(shù)主要負(fù)責(zé)獲取內(nèi)層函數(shù)的變更信息與其他節(jié)點的通信。內(nèi)層函數(shù)和外層函數(shù)都有node 這個結(jié)構(gòu)體的引用,因此兩個協(xié)程之間可以通過node 里面的Channel 進(jìn)行通信。

在Etcd 的raft 實現(xiàn)中,所有消息都被封裝到一個結(jié)構(gòu)體中,參數(shù)根據(jù)消息類型進(jìn)行組裝,因此很多參數(shù)都是可選的。一開始覺得所有消息的屬性都集成在一個結(jié)構(gòu)體不太好,后來看代碼過程中發(fā)現(xiàn)這個結(jié)構(gòu)體中屬性并不多,而且大部分屬性在大部分消息中都是要用到的。只是冗余少數(shù)字段但是大大減少了消息結(jié)構(gòu)體的數(shù)量,使得消息參數(shù)整體看起來更簡單。消息類型和屬性如下圖所示:

介紹了消息類型,接下來我們來看看raft 算法中領(lǐng)導(dǎo)人選舉的具體源碼實現(xiàn)。領(lǐng)導(dǎo)人選舉的大致步驟為

1. Candidate: 內(nèi)層函數(shù)raft.run把消息添加到raft.msgs的slice結(jié)構(gòu)中。

2. Candidate: 內(nèi)層函數(shù)監(jiān)聽到raft.msgs有新消息寫入,把raft.msgs的消息封裝成一個Ready結(jié)構(gòu)體,然后把這個結(jié)構(gòu)體放入 node.readyc這個channel。

3. Candidate: 外層函數(shù)raftNode.start 監(jiān)聽到node.readyc這個channel中有數(shù)據(jù)到達(dá),拿到這個消息,根據(jù)消息的目標(biāo)節(jié)點把消息發(fā)送出去。

4. Follower: Follower收到Candidate的領(lǐng)導(dǎo)人選舉請求,對請求做出響應(yīng)。

5. Candidate:統(tǒng)計選舉的回復(fù),如果超過半數(shù)節(jié)點同意則當(dāng)選Leader,否則轉(zhuǎn)為Follower。

源碼部分我將從go.etcd.io/etcd/etcdserver/raft.go 文件的StartNode(c *Config, peers []Peer) 函數(shù)開始分析,從這里開始主要是Raft 算法的核心實現(xiàn)。在StartNode 方法結(jié)尾啟動了一個協(xié)程用于運行內(nèi)層函數(shù)。

在StartNode 方法中,節(jié)點啟動后初始化為Follower 角色,如果在選舉超時內(nèi)沒有收到Leader 的消息,會發(fā)出一個選舉超時事件,這個事件在內(nèi)層函數(shù)(raftnode.go 文件的node.run 方法) 中被捕獲,調(diào)用r.tick 函數(shù),tick 實際上是一個函數(shù)變量,在becomeFollower 函數(shù)中被賦值為tickElection ,因此r.tick 方法實際上調(diào)用的是tickElection 方法進(jìn)行領(lǐng)導(dǎo)人選舉。

tickElection 方法中會檢測相應(yīng)參數(shù),如果節(jié)點當(dāng)前可選舉,并且已超時則會節(jié)點的狀態(tài)機(jī)函數(shù)(raftraft.go 文件Step 函數(shù))傳入一個類型為pb.MsgHup 的選舉消息,這個消息不用于節(jié)點間通信,只是通知自己可以重新發(fā)起一輪領(lǐng)導(dǎo)人選舉。

進(jìn)入Step 狀態(tài)機(jī)函數(shù),pb.MsgHup 消息被捕獲到,還是先檢測相應(yīng)參數(shù),如果檢測到自己已經(jīng)是Leader ,就忽略這個消息。否則根據(jù)是否是Prevote 階段進(jìn)入相應(yīng)投票階段,前面我們說到,Prevote 解決了raft 集群網(wǎng)絡(luò)分區(qū)造成的問題。

campaign 方法(raftraft.go 文件campaign 函數(shù))中對消息做進(jìn)一步處理,這里節(jié)點會排除自己對其他每個節(jié)點“發(fā)送“預(yù)選舉/ 選舉的消息,注意這里并沒有真正發(fā)送消息,而是把消息加到raft 結(jié)構(gòu)體的msgs 切片中。Raft.msgs 結(jié)構(gòu)體主要用于臨時存放待發(fā)送的消息。

當(dāng)數(shù)據(jù)被寫到raft.msgs 切片時,內(nèi)層函數(shù)(raftnode.go 文件的node.run 方法)監(jiān)聽到raft.msgs 中有新數(shù)據(jù)待發(fā)送,會把數(shù)據(jù)封裝成一個Ready 結(jié)構(gòu)體放入node.readyc channel 中,清空raft.msgs 消息,并賦值advancec 為node.advancec channel 。一開始沒看懂為什么這樣寫,后來在外層函數(shù)看到有一個往node.advancec 寫入空結(jié)構(gòu)體的操作才明白。當(dāng)源節(jié)點往readyc 寫入ready 數(shù)據(jù)后,就是一個異步操作,根本不知道目標(biāo)節(jié)點什么時候能夠處理完這個消息。源節(jié)點和目標(biāo)節(jié)點通過node.advancec channel 就可以傳遞消息已經(jīng)處理完的信號,源節(jié)點收到這個信號就可以進(jìn)行后續(xù)的操作。

由于外層函數(shù)(etcdserver/raft.go 文件中的start 方法)擁有node.readyc 的引用,并且監(jiān)聽著這個管道,把readyc 到來的消息拿出來,根據(jù)消息結(jié)構(gòu)體中的目標(biāo)節(jié)點把消息發(fā)送出去,這個時候就是端到端節(jié)點消息的發(fā)送了。

Candidate 節(jié)點選舉的消息發(fā)出去后,目的節(jié)點會在狀態(tài)機(jī)函數(shù)(raftraft.go 文件Step 函數(shù))中捕獲到這個消息,捕獲的消息類型為pb.MsgVote 和pb.MsgPreVote 。在這里對Term 和消息的index 進(jìn)行了檢測。對Term 的檢測主要包括:1. 之前是不是已經(jīng)給這個節(jié)點投過票,如果沒投過票可以繼續(xù)投,這表示是投票的PreVote 階段。2. 投票節(jié)點的Term 是否大于自己的Term, 只有大于才是合理的Term 值。3. 已經(jīng)投過票,這次的投票請求者是否和自己投過票的節(jié)點一樣,這表示正式的投票階段,第二次投票的必須和第一次投票的一樣;對于Index 的檢測主要是發(fā)起投票節(jié)點日志的Term 必須比自己的大,或者Term 相同,但是日志索引更多,也就是說發(fā)起投票的節(jié)點至少要擁有和自己一樣或者更新的日志記錄。這部分檢測都通過后就回復(fù)同意投票的消息,否則回復(fù)否定投票的消息。

當(dāng)所有節(jié)點都回復(fù)完Candidate 選舉的請求時。這時候再回到Candidate 節(jié)點,消息仍然會在狀態(tài)機(jī)函數(shù)(raftraft.go 文件Step 函數(shù))被捕獲。因為消息類型既不是pb.MsgHup ,又不是pb.MsgVote ,沒有對應(yīng)消息,所以代碼會走到switch case 的default 部分,調(diào)用r.step(r, m) 函數(shù),step 是一個函數(shù)變量,因為當(dāng)前節(jié)點是Candidate 角色,所以r.step 對應(yīng)stepCandidate 函數(shù)。在這個函數(shù)里,Candidate 對選票進(jìn)行統(tǒng)計,如果獲得超過半數(shù)的選票,就當(dāng)選Leader ,否則變?yōu)镕ollower 。

到此,領(lǐng)導(dǎo)人選舉的源碼分析就大致完成了,日志添加過程,心跳檢測機(jī)制等其他操作其實是一樣的消息流轉(zhuǎn)機(jī)制,只不過消息類型不同,只要找到相應(yīng)的消息類型,一步步分析下來還是比較簡單的。另外Etcd存儲部分和Raft算法緊密相關(guān),并且Etcd V3版本相比與V2版本的存儲機(jī)制進(jìn)行了較大的更新和優(yōu)化,由于時間和篇幅的關(guān)系,這部分內(nèi)容下次再分析。Etcd raft算法實現(xiàn)原理初步分析就到這里,個人理解有限,如果有不對的地方歡迎指出!

— END—

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的raft算法mysql主从复制_Etcd raft算法实现原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。