基于Raft分布式Kv存儲(chǔ):sendAppendEntries
sendAppendEntries()可以理解為針對(duì)某一個(gè) Follower執(zhí)行一次AppendEntries RPC然后把回復(fù)合并回 Leader 的 Raft 狀態(tài)。它不負(fù)責(zé)構(gòu)造請(qǐng)求。請(qǐng)求由doHeartBeat()提前構(gòu)造它只負(fù)責(zé)發(fā)送 RPC ↓ 等待結(jié)果 ↓ 檢查任期和 Leader 身份 ↓ 失敗回退 nextIndex 成功推進(jìn) matchIndex、nextIndex ↓ 嘗試推進(jìn) commitIndex源碼實(shí)現(xiàn)可見(jiàn)于該項(xiàng)目的raft.cpp。函數(shù)參數(shù)函數(shù)大致接收四個(gè)參數(shù)void Raft::sendAppendEntries( int server, std::shared_ptrAppendEntriesArgs args, std::shared_ptrAppendEntriesReply reply, std::shared_ptrint appendNums);含義分別是server 目標(biāo) Follower 的編號(hào)。 args doHeartBeat() 構(gòu)造好的 AppendEntries 請(qǐng)求。 包含 term、prevLogIndex、prevLogTerm、entries、leaderCommit。 reply 保存 Follower 返回的響應(yīng)。 appendNums 本輪心跳中共享的“成功節(jié)點(diǎn)數(shù)量”。 doHeartBeat() 創(chuàng)建它時(shí)通常初始化為 1代表 Leader 自己。使用shared_ptr是因?yàn)楹瘮?shù)運(yùn)行在detach()出去的線(xiàn)程中。doHeartBeat()返回后請(qǐng)求、回復(fù)和計(jì)數(shù)器仍必須存活。一、發(fā)送 RPC核心調(diào)用類(lèi)似bool ok m_peers[server]-AppendEntries( args.get(), reply.get() );這里值得注意的是執(zhí)行網(wǎng)絡(luò) RPC 時(shí)沒(méi)有持有m_mtx。這是正確的鎖邊界設(shè)計(jì)。網(wǎng)絡(luò)請(qǐng)求可能超時(shí)或阻塞如果在 RPC 期間一直持有 Raft 主鎖當(dāng)前節(jié)點(diǎn)將無(wú)法及時(shí)處理其他節(jié)點(diǎn)發(fā)來(lái)的 RPC接收更高任期處理客戶(hù)端請(qǐng)求更新選舉和心跳狀態(tài)。因此它采用無(wú)鎖執(zhí)行網(wǎng)絡(luò)調(diào)用 ↓ RPC 返回 ↓ 加鎖處理回復(fù)但這也意味著 RPC 飛行期間當(dāng)前節(jié)點(diǎn)的任期和身份可能發(fā)生變化所以后面必須重新校驗(yàn)。二、網(wǎng)絡(luò)失敗直接返回if (!ok) { return; }ok false通常表示連接失敗、超時(shí)或者底層 RPC 沒(méi)有成功完成。這種情況下函數(shù)不會(huì)修改 nextIndex 修改 matchIndex 修改 currentTerm 增加 appendNums它也不會(huì)立即重試。后續(xù)的周期性doHeartBeat()會(huì)再次嘗試。這是合理的因?yàn)榫W(wǎng)絡(luò)失敗不代表日志不匹配不能因?yàn)橐淮纬瑫r(shí)就隨意回退nextIndex。三、重新獲得 Raft 鎖std::lock_guardstd::mutex lock(m_mtx);從這里開(kāi)始回復(fù)處理期間的共享狀態(tài)修改都受同一把鎖保護(hù)包括m_currentTerm m_status m_votedFor m_nextIndex m_matchIndex m_commitIndex appendNums因此appendNums雖然只是普通int而不是原子變量但它的讀寫(xiě)發(fā)生在m_mtx內(nèi)當(dāng)前實(shí)現(xiàn)下不會(huì)因?yàn)槎鄠€(gè)回復(fù)線(xiàn)程同時(shí)執(zhí)行而產(chǎn)生直接的數(shù)據(jù)競(jìng)爭(zhēng)。四、處理更大的任期if (reply-term() m_currentTerm) { m_status Follower; m_currentTerm reply-term(); m_votedFor -1; return; }這是 Raft 非常重要的一條規(guī)則任何節(jié)點(diǎn)只要發(fā)現(xiàn)其他節(jié)點(diǎn)的任期比自己大就必須更新任期并退回 Follower。例如當(dāng)前節(jié)點(diǎn)認(rèn)為 自己是 term6 的 Leader Follower 回復(fù) term7這說(shuō)明 term 6 已經(jīng)過(guò)期集群至少已經(jīng)進(jìn)入 term 7。當(dāng)前節(jié)點(diǎn)不能再繼續(xù)發(fā)送 term 6 的日志必須立即退位。這里同時(shí)清空m_votedFor -1;表示新任期中還沒(méi)有投票。不過(guò)這份實(shí)現(xiàn)此處分支沒(méi)有明顯調(diào)用persist()。由于currentTerm和votedFor屬于 Raft 的持久化狀態(tài)更嚴(yán)格的實(shí)現(xiàn)應(yīng)該在釋放鎖或返回之前將它們持久化否則節(jié)點(diǎn)崩潰重啟后可能恢復(fù)出舊任期。五、丟棄較小任期的回復(fù)else if (reply-term() m_currentTerm) { return; }例如發(fā)送請(qǐng)求時(shí) currentTerm 6 等待 RPC 期間 當(dāng)前節(jié)點(diǎn)進(jìn)入 term 7并且重新成為 Leader 舊請(qǐng)求返回 reply.term 6這個(gè)回復(fù)屬于過(guò)去的任期不能再修改當(dāng)前狀態(tài)所以直接丟棄。隨后通常還有斷言assert(reply-term() m_currentTerm);經(jīng)過(guò)前面兩個(gè)分支后繼續(xù)執(zhí)行的回復(fù)原則上必須與當(dāng)前任期一致。六、再次確認(rèn)自己仍然是 Leaderif (m_status ! Leader) { return; }即使回復(fù)的任期等于m_currentTerm也不能說(shuō)明當(dāng)前節(jié)點(diǎn)仍然是 Leader。RPC 飛行期間可能發(fā)生Leader 發(fā)送 AppendEntries ↓ 收到合法的同任期 Leader 消息或狀態(tài)發(fā)生變化 ↓ 當(dāng)前節(jié)點(diǎn)轉(zhuǎn)為 Follower ↓ 舊 AppendEntries 回復(fù)返回此時(shí)不能再更新 Leader 專(zhuān)屬的nextIndex[] matchIndex[] commitIndex所以需要獨(dú)立檢查m_status。七、處理日志匹配失敗if (!reply-success()) { if (reply-updatenextindex() ! -100) { m_nextIndex[server] reply-updatenextindex(); } }success false一般說(shuō)明Follower 不存在 prevLogIndex或者Follower 在 prevLogIndex 位置的 term 和 Leader 給出的 prevLogTerm 不同F(xiàn)ollower 會(huì)通過(guò)updateNextIndex告訴 Leader下次應(yīng)該從哪里嘗試。例如Leader nextIndex[F] 8 本次發(fā)送 prevLogIndex 7 Follower 實(shí)際只有日志 14Follower 可以回復(fù)success false updateNextIndex 5Leader于是執(zhí)行m_nextIndex[F] 5;下一輪請(qǐng)求變成prevLogIndex 4 entries [5, 6, 7, ...]這就是 Raft 的日志回退過(guò)程。-100是這份代碼使用的特殊哨兵值表示 Follower 沒(méi)有提供可用的新下標(biāo)。工程上更清晰的方式是使用 Protobuf 的字段存在性或明確的狀態(tài)枚舉而不是魔法數(shù)字。八、成功時(shí)更新復(fù)制進(jìn)度成功分支首先增加本輪成功數(shù)*appendNums *appendNums 1;然后計(jì)算這次請(qǐng)求能夠確認(rèn)的最大日志下標(biāo)int replicatedIndex args-prevlogindex() args-entries_size();例如prevLogIndex 5 entries [6, 7, 8] entries_size 3那么replicatedIndex 5 3 8意味著 Follower 已經(jīng)確認(rèn)擁有截至日志 8 的完整前綴。為什么更新matchIndex要用max代碼類(lèi)似m_matchIndex[server] std::max( m_matchIndex[server], args-prevlogindex() args-entries_size() );原因是網(wǎng)絡(luò)回復(fù)可能亂序。假設(shè)同時(shí)存在兩個(gè)請(qǐng)求請(qǐng)求 A確認(rèn)到日志 8 請(qǐng)求 B確認(rèn)到日志 12如果 B 先回來(lái)matchIndex 12隨后舊請(qǐng)求 A 才回來(lái)。如果直接賦值就會(huì)錯(cuò)誤地變成matchIndex 8使用max可以保證matchIndex 只能前進(jìn)不能后退這是成功回復(fù)處理里做得比較穩(wěn)妥的地方。更新nextIndexm_nextIndex[server] m_matchIndex[server] 1;兩個(gè)字段的關(guān)系是matchIndex[i] 已確認(rèn) Follower i 擁有的最后日志下標(biāo) nextIndex[i] 下一次應(yīng)從哪個(gè)下標(biāo)繼續(xù)發(fā)送如果已經(jīng)確認(rèn) Follower 擁有到日志 8matchIndex 8 nextIndex 9下一次心跳如果 Leader 沒(méi)有新日志就會(huì)發(fā)送prevLogIndex 8 entries 空這就是純心跳。九、嘗試推進(jìn)commitIndex當(dāng)前實(shí)現(xiàn)使用if (*appendNums 1 m_peers.size() / 2) { *appendNums 0; if (args-entries_size() 0) { m_commitIndex std::max(m_commitIndex, m_matchIndex[server]); } }假設(shè)有 5 個(gè)節(jié)點(diǎn)多數(shù)派數(shù)量 1 5 / 2 3appendNums初始是 1因?yàn)?Leader 自己已經(jīng)有日志。收到兩個(gè) Follower 的成功回復(fù)后appendNums 3于是代碼認(rèn)為獲得多數(shù)派可以推進(jìn)提交位置。設(shè)置成0是為了避免本輪后續(xù)回復(fù)再次觸發(fā)提交邏輯。這里存在一個(gè)重要正確性問(wèn)題appendNums只統(tǒng)計(jì)“RPC 成功了幾個(gè)”但不同 Follower 成功確認(rèn)的日志位置可能不同。例如 5 節(jié)點(diǎn)集群Leader擁有日志到 10 Follower A成功確認(rèn)到 5 Follower B成功確認(rèn)到 10成功數(shù)量是Leader A B 3已經(jīng)過(guò)半如果 B 的回復(fù)正好讓appendNums達(dá)到 3當(dāng)前代碼可能執(zhí)行commitIndex 10但日志 10 實(shí)際只有Leader Follower B只有兩個(gè)節(jié)點(diǎn)并沒(méi)有過(guò)半。Follower A 只擁有到日志 5。正確算法應(yīng)該針對(duì)每個(gè)候選下標(biāo)N統(tǒng)計(jì)有多少節(jié)點(diǎn)滿(mǎn)足 matchIndex[i] N只有滿(mǎn)足多數(shù)節(jié)點(diǎn)的 matchIndex N 并且 log[N].term currentTerm才能把commitIndex推進(jìn)到N。這也是 Raft 論文描述的 Leader 提交規(guī)則。(usenix.org)該項(xiàng)目其實(shí)已經(jīng)存在類(lèi)似的leaderUpdateCommitIndex()回復(fù)成功后調(diào)用它會(huì)比appendNums更符合 Raft 語(yǔ)義。

相關(guān)新聞

Android分區(qū)變量配置與優(yōu)化實(shí)踐指南

Android分區(qū)變量配置與優(yōu)化實(shí)踐指南

1. Android分區(qū)專(zhuān)用變量概述在Android系統(tǒng)開(kāi)發(fā)中,分區(qū)專(zhuān)用變量扮演著關(guān)鍵角色。這些變量主要用于定義不同分區(qū)的屬性和行為,直接影響系統(tǒng)啟動(dòng)流程、應(yīng)用運(yùn)行環(huán)境和硬件配置。最常見(jiàn)的實(shí)現(xiàn)方式是通過(guò)build.prop文件和PRODUCT_PROPERTY_OVERRIDES機(jī)制進(jìn)行管…

2026/7/29 1:55:58 閱讀更多
YOLOv7 推理加速實(shí)戰(zhàn):OpenVINO 與 TorchORT 兩種流程對(duì)比

YOLOv7 推理加速實(shí)戰(zhàn):OpenVINO 與 TorchORT 兩種流程對(duì)比

YOLOv7 推理加速實(shí)戰(zhàn):OpenVINO 與 TorchORT 兩種流程對(duì)比 這篇教程根據(jù)我復(fù)現(xiàn) YOLOv7 推理加速流程時(shí)整理,重點(diǎn)演示環(huán)境安裝、數(shù)據(jù)準(zhǔn)備、普通模型推理、TorchORT 推理和結(jié)果可視化。 本文整理自我的學(xué)習(xí)和項(xiàng)目復(fù)現(xiàn)過(guò)程,盡量按實(shí)操順序保留 n…

2026/7/29 1:55:58 閱讀更多
基于Arduino與藍(lán)牙BLE的智能氛圍燈DIY:從電路設(shè)計(jì)到3D打印全解析

基于Arduino與藍(lán)牙BLE的智能氛圍燈DIY:從電路設(shè)計(jì)到3D打印全解析

1. 項(xiàng)目概述:當(dāng)硬核技術(shù)遇上浪漫心意“智能蘑菇燈”這個(gè)項(xiàng)目,乍一聽(tīng)像是極客圈里一個(gè)普通的DIY小玩意兒,但加上“送給心儀女孩最好的禮物”這個(gè)后綴,整個(gè)項(xiàng)目的內(nèi)核就完全不一樣了。它不再僅僅是一個(gè)技術(shù)實(shí)現(xiàn)的載體,而…

2026/7/29 4:36:03 閱讀更多
Arduino入門(mén)實(shí)戰(zhàn):從呼吸燈到循跡小車(chē),十一假期玩轉(zhuǎn)開(kāi)源硬件

Arduino入門(mén)實(shí)戰(zhàn):從呼吸燈到循跡小車(chē),十一假期玩轉(zhuǎn)開(kāi)源硬件

1. 項(xiàng)目概述:用Arduino點(diǎn)亮你的十一假期十一長(zhǎng)假,與其在景區(qū)人擠人,或者在家刷手機(jī)虛度時(shí)光,不如動(dòng)手玩點(diǎn)有意思的。作為一個(gè)玩了十多年硬件的“老創(chuàng)客”,我強(qiáng)烈推薦你試試Arduino。這玩意兒不是什么高深莫測(cè)的黑科技&…

2026/7/29 4:36:03 閱讀更多
SEATA AT模式:低侵入分布式事務(wù)解決方案的原理與實(shí)踐

SEATA AT模式:低侵入分布式事務(wù)解決方案的原理與實(shí)踐

1. 項(xiàng)目概述:為什么我們需要SEATA的AT模式? 在微服務(wù)架構(gòu)里,一個(gè)業(yè)務(wù)操作經(jīng)常需要跨多個(gè)服務(wù)、多個(gè)數(shù)據(jù)庫(kù)來(lái)完成。比如一個(gè)電商下單流程,你可能需要調(diào)用訂單服務(wù)創(chuàng)建訂單,調(diào)用庫(kù)存服務(wù)扣減庫(kù)存,再調(diào)用賬戶(hù)服…

2026/7/29 4:36:03 閱讀更多
密碼安全進(jìn)階:鹽與胡椒在加密存儲(chǔ)中的關(guān)鍵作用

密碼安全進(jìn)階:鹽與胡椒在加密存儲(chǔ)中的關(guān)鍵作用

1. 密碼安全的核心要素解析當(dāng)我們?cè)谟懻撁艽a安全時(shí),大多數(shù)人第一反應(yīng)就是"加密"——這確實(shí)沒(méi)錯(cuò),但遠(yuǎn)遠(yuǎn)不夠。就像做一道好菜,光有主料不行,還需要調(diào)味料來(lái)提升風(fēng)味。在密碼學(xué)領(lǐng)域,"鹽"(Salt)和&qu…

2026/7/29 4:36:03 閱讀更多
uni-app路由跳轉(zhuǎn)全解析:六種方式、實(shí)戰(zhàn)場(chǎng)景與性能優(yōu)化

uni-app路由跳轉(zhuǎn)全解析:六種方式、實(shí)戰(zhàn)場(chǎng)景與性能優(yōu)化

1. 項(xiàng)目概述:為什么uni-app的路由跳轉(zhuǎn)值得深挖?在uni-app的開(kāi)發(fā)日常里,頁(yè)面跳轉(zhuǎn)是比呼吸還頻繁的操作。從最簡(jiǎn)單的商品列表到詳情頁(yè),到復(fù)雜的多級(jí)表單流程,再到需要登錄攔截的權(quán)限控制,路由跳轉(zhuǎn)是串聯(lián)起整個(gè)…

2026/7/29 4:36:03 閱讀更多
python爬取貝殼中二手房的數(shù)據(jù)

python爬取貝殼中二手房的數(shù)據(jù)

前言:通過(guò)代碼爬取貝殼中二手房的數(shù)據(jù),以此給更多需要了解爬蟲(chóng)或者二手房信息的人提供便利。 第一部分:爬取地址 1.1貝殼首頁(yè)地址 jiujiang.ke.com 第二部分:爬取數(shù)據(jù) 2.1輸入要爬多少頁(yè) int(input(輸入一共要多少頁(yè)&#xf…

2026/7/29 4:26:03 閱讀更多
面試官大笑:“一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,不比 1 個(gè)快 5 倍?“我搖頭:“快不了,還可能更慢“

面試官大笑:“一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,不比 1 個(gè)快 5 倍?“我搖頭:“快不了,還可能更慢“

前兩個(gè)月,我在重構(gòu) AlgoMooc 網(wǎng)站過(guò)程中,發(fā)現(xiàn)一個(gè)問(wèn)題:在 Claude Code 里把一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,結(jié)果可能比 1 個(gè) agent 從頭干到尾還慢? 大多數(shù)人的第一反應(yīng)是反過(guò)來(lái)的:活是并行干的&#…

2026/7/29 0:15:24 閱讀更多
# 鴻蒙 HarmonyOS 應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號(hào)與動(dòng)畫(huà)渲染精講

# 鴻蒙 HarmonyOS 應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號(hào)與動(dòng)畫(huà)渲染精講

一、應(yīng)用概述 骰子(Dice Roller) 是一款經(jīng)典的休閑娛樂(lè)應(yīng)用,模擬了真實(shí)擲骰子的過(guò)程。應(yīng)用投擲兩個(gè)骰子(六面標(biāo)準(zhǔn)骰),使用 Unicode 骰面符號(hào)直觀展示每個(gè)骰子的點(diǎn)數(shù),并伴有快速滾動(dòng)的動(dòng)畫(huà)效果?!?/p>

2026/7/29 0:15:24 閱讀更多