手写分布式共识:用 Go 实现 Raft 算法的核心逻辑(选主与日志复制)

标签:#Raft #分布式系统 #Go语言 #算法 #架构设计 #源码解析


🌊 前言:Raft 的世界观

Raft 的核心思想只有一句话:强领导者 (Strong Leader)
整个集群只有 Leader 能写日志,Follower 只能被动接受。Raft 将共识问题分解为三个子问题:

  1. Leader Election:老大挂了,怎么选新老大?
  2. Log Replication:老大怎么把指令强行同步给小弟?
  3. Safety:怎么保证数据不丢、不乱?

Raft 状态流转图 (Mermaid):

超时 (Election Timeout)

获得大多数票

发现更高 Term / 选举超时

发现更高 Term

Follower

Candidate

Leader


🏗️ 一、 骨架:Raft 结构体定义

我们要严格按照论文中的 Figure 2 来定义数据结构。

typeStateintconst(Follower State=iotaCandidate Leader)typeRaftstruct{mu sync.Mutex state State// --- 持久性状态 (所有节点都有) ---currentTermint// 当前任期votedForint// 给谁投过票 (-1 表示没投)log[]LogEntry// 日志条目 {Term, Command}// --- 易失性状态 (所有节点都有) ---commitIndexint// 已知已提交的最高索引lastAppliedint// 已应用到状态机的最高索引// --- Leader 独有状态 ---nextIndexmap[int]int// 发送给每个 Follower 的下一个日志索引matchIndexmap[int]int// 每个 Follower 已经复制到的最高索引// --- 选举相关 ---electionTimer*time.Timer heartbeatTimer*time.Timer}typeLogEntrystruct{TermintCommandinterface{}}

🗳️ 二、 核心一:选主 (Leader Election)

选主的本质是心跳超时
Leader 必须不断发送心跳(空的AppendEntriesRPC)来压制 Follower。一旦 Follower 在ElectionTimeout时间内没收到心跳,它就造反。

1. 触发选举
func(rf*Raft)runElectionTimer(){timeout:=randomTimeout()// 随机 150-300msfor{<-rf.electionTimer.C rf.mu.Lock()ifrf.state!=Leader{// 变成 Candidate,任期 +1,给自己投票rf.state=Candidate rf.currentTerm++rf.votedFor=rf.me rf.persist()// 发起拉票gorf.startElection()// 重置定时器rf.electionTimer.Reset(randomTimeout())}rf.mu.Unlock()}}
2. 处理投票请求 (RequestVote RPC)

这是面试重点:什么情况下我才会给你投票?

  1. 你的 Term >= 我的 Term。
  2. 我这个 Term 还没投过票(或者投的就是你)。
  3. 关键点:你的日志至少和我一样新 (Up-to-Date)。
func(rf*Raft)RequestVote(args*RequestVoteArgs,reply*RequestVoteReply){rf.mu.Lock()deferrf.mu.Unlock()// 1. 如果对方任期小,直接拒绝ifargs.Term<rf.currentTerm{reply.VoteGranted=falsereply.Term=rf.currentTermreturn}// 2. 如果对方任期大,我变成 Followerifargs.Term>rf.currentTerm{rf.currentTerm=args.Term rf.state=Follower rf.votedFor=-1}// 3. 检查日志是否足够新 (比较 LastLogTerm 和 LastLogIndex)lastLogIndex:=len(rf.log)-1lastLogTerm:=rf.log[lastLogIndex].Term upToDate:=falseifargs.LastLogTerm>lastLogTerm{upToDate=true}elseifargs.LastLogTerm==lastLogTerm&&args.LastLogIndex>=lastLogIndex{upToDate=true}// 4. 投票逻辑if(rf.votedFor==-1||rf.votedFor==args.CandidateId)&&upToDate{reply.VoteGranted=truerf.votedFor=args.CandidateId rf.electionTimer.Reset(randomTimeout())// 投票也算收到消息,重置超时}else{reply.VoteGranted=false}reply.Term=rf.currentTerm}

📚 三、 核心二:日志复制 (Log Replication)

一旦选出 Leader,它就开始处理客户端请求,并将日志同步给 Follower。

日志复制流程图 (Mermaid):

FollowerLeaderClientFollowerLeaderClientalt[Log Match (一致)][Log Mismatch (不一致)]1. 发送命令 Set X=12. 写入本地 Log (Index=N)3. AppendEntries RPC (PrevIndex=N-1, Entries=[X=1])写入本地 LogSuccessFalse (Conflict)Decrement nextIndexRetry with older entries4. 收到多数派 Success ->> Commit5. 返回结果
1. Leader 发送日志

Leader 对每个 Follower 维护一个nextIndex

func(rf*Raft)sendAppendEntries(serverint){rf.mu.Lock()ifrf.state!=Leader{rf.mu.Unlock()return}prevLogIndex:=rf.nextIndex[server]-1prevLogTerm:=rf.log[prevLogIndex].Term entries:=rf.log[rf.nextIndex[server]:]// 发送从 nextIndex 开始的所有日志args:=AppendEntriesArgs{Term:rf.currentTerm,LeaderId:rf.me,PrevLogIndex:prevLogIndex,PrevLogTerm:prevLogTerm,Entries:entries,LeaderCommit:rf.commitIndex,}rf.mu.Unlock()varreply AppendEntriesReplyifrf.peers[server].Call("Raft.AppendEntries",&args,&reply){rf.mu.Lock()deferrf.mu.Unlock()ifreply.Term>rf.currentTerm{rf.state=Follower rf.currentTerm=reply.Termreturn}ifreply.Success{// 复制成功:更新 matchIndex 和 nextIndexrf.matchIndex[server]=prevLogIndex+len(entries)rf.nextIndex[server]=rf.matchIndex[server]+1rf.updateCommitIndex()// 尝试推进 CommitIndex}else{// 复制失败:回退 nextIndex (简单优化版:直接减一,或者利用 reply 中的 conflictIndex 快速回退)rf.nextIndex[server]--}}}
2. Follower 处理日志 (一致性检查)

这是 Raft 保证数据不乱的关键:Log Matching Property
Follower 在追加日志前,必须检查PrevLogIndexPrevLogTerm是否匹配。

func(rf*Raft)AppendEntries(args*AppendEntriesArgs,reply*AppendEntriesReply){rf.mu.Lock()deferrf.mu.Unlock()// 1. Term 检查ifargs.Term<rf.currentTerm{reply.Success=falsereply.Term=rf.currentTermreturn}// 保持 Follower 状态,重置选举超时rf.state=Follower rf.electionTimer.Reset(randomTimeout())// 2. 一致性检查:我这里的 PrevLogIndex 处是否有日志?且 Term 是否匹配?iflen(rf.log)<=args.PrevLogIndex||rf.log[args.PrevLogIndex].Term!=args.PrevLogTerm{reply.Success=falsereturn// 告诉 Leader 回退}// 3. 冲突解决:删除冲突后的所有日志,追加新日志// 注意:不要无脑覆盖,只有冲突时才截断,防止过期包覆盖新日志insertIndex:=args.PrevLogIndex+1fori,entry:=rangeargs.Entries{ifinsertIndex+i>=len(rf.log){rf.log=append(rf.log,entry)}elseifrf.log[insertIndex+i].Term!=entry.Term{rf.log=rf.log[:insertIndex+i]// 截断rf.log=append(rf.log,entry)}}// 4. 更新 CommitIndexifargs.LeaderCommit>rf.commitIndex{rf.commitIndex=min(args.LeaderCommit,len(rf.log)-1)}reply.Success=true}

🔐 四、 难点解析:Safety (Commit 的条件)

Leader 什么时候可以把日志标记为Committed并应用到状态机?
答:当这个日志条目被复制到了“大多数”节点上。

func(rf*Raft)updateCommitIndex(){// 寻找一个 N,满足 N > commitIndex,且大多数节点的 matchIndex >= N,且 log[N].term == currentTermforN:=len(rf.log)-1;N>rf.commitIndex;N--{count:=1// 自己算一票forpeer:=rangerf.peers{ifpeer!=rf.me&&rf.matchIndex[peer]>=N{count++}}ifcount>len(rf.peers)/2&&rf.log[N].Term==rf.currentTerm{rf.commitIndex=N// 触发 Apply 线程...break}}}

注意:Leader 只能提交当前 Term的日志。对于旧 Term 的日志,即使复制到了多数派,也不能直接提交,必须等待当前 Term 的日志提交时间接提交(Figure 8 问题)。


🎯 总结

手写 Raft 的过程,就是理解**“一致性”“可用性”**博弈的过程。

  • RequestVote保证了 Leader 的合法性。
  • AppendEntriesPrevLog检查保证了日志的连续性。
  • Majority Quorum保证了脑裂时不会产生两个有效 Leader。

当你能闭着眼睛画出nextIndex的回退流程时,你对分布式系统的理解已经超越了 90% 的 CRUD 工程师。

Next Step:
上面的代码是内存版的。尝试引入LevelDB或简单的文件 IO,在persist()函数中实现currentTermvotedForlog的持久化,让你的 Raft 在进程崩溃重启后依然能恢复数据。这就是一个微型 Etcd 的雏形!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1168295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学长亲荐2026自考AI论文网站TOP9:选对工具轻松过关

学长亲荐2026自考AI论文网站TOP9&#xff1a;选对工具轻松过关 一、不同维度核心推荐&#xff1a;9款AI工具各有所长 在自考论文写作过程中&#xff0c;从开题到最终提交&#xff0c;每一个环节都需要合适的工具来辅助。不同的AI平台在功能覆盖、操作便捷性、适用人群等方面各有…

分布式事务落地:Seata AT 模式 vs TCC 模式,在订单与库存扣减场景下的艰难抉择

标签&#xff1a; #Seata #分布式事务 #微服务 #SpringCloud #架构设计 #Java&#x1f4a3; 前言&#xff1a;微服务拆分后的“噩梦” 假设一个电商下单流程&#xff1a; 订单服务&#xff1a;创建订单 (INSERT ORDER).库存服务&#xff1a;扣减库存 (UPDATE STOCK SET count …

热设计知识库:整合行业标准、材料参数、案例库,提供智能设计建议。

&#x1f393;作者简介&#xff1a;科技自媒体优质创作者 &#x1f310;个人主页&#xff1a;莱歌数字-CSDN博客 &#x1f48c;公众号&#xff1a;莱歌数字&#xff08;B站同名&#xff09; &#x1f4f1;个人微信&#xff1a;yanshanYH 211、985硕士&#xff0c;从业16年 从…

完成比完美更重要:敏捷热管理方法

&#x1f393;作者简介&#xff1a;科技自媒体优质创作者 &#x1f310;个人主页&#xff1a;莱歌数字-CSDN博客 &#x1f48c;公众号&#xff1a;莱歌数字&#xff08;B站同名&#xff09; &#x1f4f1;个人微信&#xff1a;yanshanYH 211、985硕士&#xff0c;从业16年 从…

【tensorRT从零起步高性能部署】18-TensorRT基础-核心流程全面剖析(通俗易懂)

用通俗易懂的大白话&#xff0c;拆解TensorRT的核心组件、类/函数的作用和使用逻辑——其实TensorRT的整个流程&#xff0c;就像**“定制一个专属GPU的高速计算器”**&#xff1a;先画图纸、再优化造机器、最后用机器干活。下面我用“生活化比喻通俗步骤”&#xff0c;把所有核…

当你遇到裁员,你该如何面对呢?

裁员过后&#xff0c;肯定是几家欢喜几家愁&#xff0c;但太阳还是会照常升起&#xff0c;无论是那些留下来的“幸运儿”还是被裁减的“倒霉蛋”都需要面对今后的工作与生活。那么&#xff0c;该如何面对未来呢&#xff1f;01从容面对如果你是因为企业经营结构或战略调整而被裁…

‌AI驱动的崩溃模块预测:软件测试从业者实战指南

高风险模块的AI预测已成测试效能跃迁的关键引擎‌在2026年的软件交付节奏下&#xff0c;‌基于历史缺陷数据的AI预测模型&#xff0c;已从研究概念演变为测试团队的标配能力‌。通过融合代码变更频率、历史缺陷密度、圈复杂度与测试覆盖率四大核心特征&#xff0c;结合XGBoost、…

为什么你的自动化测试失败率居高不下?可能是AI训练数据错了

自动化测试失败率的隐忧 在快速迭代的软件开发中&#xff0c;自动化测试已成为质量保障的基石。然而&#xff0c;许多团队报告其失败率持续居高不下——据行业调研&#xff08;如Gartner 2025报告&#xff09;&#xff0c;超过60%的企业面临自动化测试脚本频繁报错的问题&…

dpwsockx.dll文件丢失怎么办? 免费下载方法分享

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

“发票未付,服务将停”:意大利主机商客户成钓鱼新目标,域名与网站控制权正被悄然转移

在数字世界里&#xff0c;一个域名可能只值几十元&#xff0c;但对一家中小企业而言&#xff0c;它可能是品牌、客户流量、甚至全部营收的唯一入口。正因如此&#xff0c;当一封标题为“您的 Aruba 账户存在未支付发票&#xff0c;服务将于24小时内暂停”的邮件出现在收件箱时&…

2026 MBA必看!8个降AI率工具测评榜单

2026 MBA必看&#xff01;8个降AI率工具测评榜单 2026年MBA必备的降AI率工具测评指南 随着学术规范日益严格&#xff0c;AIGC检测技术不断升级&#xff0c;传统的降重方式已难以满足需求。对于MBA学生而言&#xff0c;论文不仅是学术成果的体现&#xff0c;更是职业发展的重要筹…

钓鱼邮件新变种:利用HTML表格伪造二维码绕过安全检测

随着网络安全防护体系的不断演进&#xff0c;攻击者也在持续寻找新的技术路径规避检测机制。近日&#xff0c;一种新型钓鱼邮件攻击手法在国际安全社区引发广泛关注&#xff1a;攻击者不再使用传统的图片嵌入方式生成二维码&#xff0c;而是通过纯HTML表格结构“绘制”出视觉上…

NXP解析蓝牙 ® 声道探测技术将如何赋能汽车数字钥匙

汽车领域对该技术的期待颇高 —— 不仅看重其高精度测距能力&#xff0c;也关注其安全增强特性&#xff0c;目前行业组织已在推进将其应用于数字钥匙标准。恩智浦日本微控制器部门的 Kuniyuki Tomizu&#xff08;参与蓝牙低功耗营销工作&#xff09;&#xff0c;重点介绍了蓝牙…

2026必备!10个AI论文软件,助研究生轻松搞定论文写作!

2026必备&#xff01;10个AI论文软件&#xff0c;助研究生轻松搞定论文写作&#xff01; AI 工具革新论文写作&#xff0c;研究生迎来高效新纪元 在学术研究日益精细化的今天&#xff0c;研究生们正面临前所未有的挑战。从选题构思到论文撰写&#xff0c;再到查重降重&#xff…

AI英语口语教练APP的费用

开发一款AI英语口语教练APP的费用&#xff0c;通常由人力开发成本&#xff08;前期投入&#xff09;和AI云服务成本&#xff08;长期运营&#xff09;两大部分组成。在2026年的市场环境下&#xff0c;根据应用复杂程度的不同&#xff0c;预估费用如下。一、 核心开发阶段费用&a…

借助蓝牙 ® 随机解析私有地址(RPA)更新,提升设备隐私性与能效表现

蓝牙 随机 RPA 更新功能&#xff0c;增强了对 “可解析私有地址” 的管理能力&#xff0c;同时提升了蓝牙 LE 设备的隐私性与能效表现。本文将说明蓝牙 随机 RPA 更新的重要性、工作原理&#xff0c;并为刚接触这一蓝牙增强功能的读者提供实用背景信息。 背景 所有蓝牙设备都…

【2026】 LLM 大模型系统学习指南 (3)

Judge Boi&#xff1a;大模型的 “智能阅卷老师”—— 从原理到实用操作在和大模型打交道时&#xff0c;我们常遇到一个问题&#xff1a;AI 生成的回答到底好不好&#xff1f;比如让 AI 解数学题、写作文&#xff0c;或是生成编程代码&#xff0c;怎么快速判断它的输出是否准确…

Springboot旅游景点管理系统2fj40iq6(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表项目功能用户&#xff0c;景点分类&#xff0c;路线分类&#xff0c;车票分类&#xff0c;景点信息&#xff0c;酒店信息&#xff0c;游玩路线&#xff0c;车票信息&#xff0c;门票订单&#xff0c;酒店订单&#xff0c;车票订单&#xff0c;留言反馈开题报告…

链表实现超详细讲解:从概念到代码手把手教你

链表是一种基础且重要的数据结构&#xff0c;它通过节点间的指针链接来组织数据&#xff0c;与数组的连续存储方式形成鲜明对比。理解链表的实现原理&#xff0c;是掌握动态数据管理、深入学习更复杂结构&#xff08;如树、图&#xff09;的关键一步。本文将从基本概念出发&…