想象一场盛大的合唱演出:指挥家挥动指挥棒,几十位乐手各拿一份谱子,同一瞬间发出同一个音,才能成就一段和谐旋律。可如果每个乐手各唱各调,或者临时换了指挥却没人知道该听谁的,舞台瞬间就会变成灾难现场。分布式系统的世界,正是这样一个“合唱团”:几十台服务器(乐手)必须对同一份“乐谱”(日志)达成一致,才能对外提供可靠服务。Raft,就是那套让整个合唱团不跑调、不乱序的“排练总规则”。 Raft把“如何让一堆机器达成一致”这个看似高深的问题,拆成了三件老百姓都听得懂的小事:选指挥(Leader选举)、发谱子(日志复制)、守规矩(安全性约束)。它不追求刻意刁钻的数学模型,而是坚持“人一定要看得懂”——正因如此,它比曾经高高在上的Paxos亲民得多,也成了etcd、Consul等现代系统验证过的“实战指挥棒”。接下来,让我们从合唱团的第一次排练开始,一步步揭开Raft日志复制的全部秘密。
学会Raft日志复制,你就握住了分布式共识的钥匙——这是理解etcd、Kubernetes等工业级系统内幕的根基,也是面向高可用架构师岗位必考的硬核技能。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
1. 角色与转换
合唱团里,指挥家挥舞指挥棒,乐手们安静等待指令,而一旦指挥家缺席,总得有人站出来临时带队。Raft把集群中的每台服务器也划成了三种身份:Leader(指挥家)、Follower(乐手)、Candidate(候选人)。Leader是唯一能向客户端承诺写入的人;Follower只是被动响应Leader的指令;Candidate则是选举阶段“站出来参选”的临时身份。
把视角从舞台搬到机房:每台服务器都运行着Raft模块,它们通过日志复制共同维护一份状态机。客户端只把写请求发给Leader,Leader把操作封装成日志条目广播给所有Follower。Follower收到后写入本地日志并返回确认。整个过程就像指挥家把新乐谱逐页传给乐手,乐手们逐一确认“谱子收到”。
三种角色并非一成不变。Raft用状态机描述转换关系:Follower在一段时间内收不到合法Leader的心跳,就转为Candidate并发起选举;Candidate获得多数选票后转正为Leader;如果选举失败,则退回Follower等待下一轮机会。任何时刻集群中最多只有一个有效的Leader,这是整个共识机制的基石。
三种角色的切换就像乐团的岗位轮换:人人皆可竞选,但同一时刻只能有一位上台执棒。这个简单直观的状态机,为后续日志复制、安全性规则搭建好了舞台。
- 初始状态(Follower)所有节点启动时都是Follower,像刚入团的乐手,先安静等待指挥。
- 触发转换(选举超时)Follower在随机超时内没有接收到Leader的有效心跳,就举起手宣布“我要竞选”。
- 投票环节(多数票)Candidate把当前任期加一,先投自己一票,再向所有其他节点广播RequestVote请求。
- 转正上台(Leader)一旦票数过半,Candidate立即成为新一任Leader,开始向全员发送心跳确立权威。
2. 任期与选举
每届指挥家都有自己独一无二的“任期拍数”:第一届拍一下,第二届拍两下。乐手们只要听拍数,就知道现在该听谁的。Raft的任期(Term)正是这样的拍数——一个全局单调递增的整数,每次选举失败或成功都会加一,用来标记“这是第几届领导”。
选举是怎么打起来的?Leader会周期性向所有Follower发送心跳(AppendEntries空请求),像指挥家时不时敲一下指挥棒。只要Follower在随机设定的选举超时(通常150~300毫秒)内收到心跳,就继续保持Follower。可一旦超时仍未收到心跳,Follower会断定“指挥家可能倒下了”,于是任期加一,变成Candidate开始拉票。
拉票规则很直白:Candidate先投自己一票,再向所有节点发送RequestVote RPC。每个节点在同一任期内只投一票。票数率先过半的候选人直接转正为Leader,并立即开始发送新任期的心跳,阻止新一轮选举。这个“过半”条件,正是分布式系统的多数派(Quorum)思想——只要有超过一半的节点认可,就认为整个系统做了决定。
任期像指挥家的“当值拍数”一样,每次换人都要递增。随机超时和先到先得的设计,确保了选举不会陷入“两人对峙”的僵局——这正是Raft在实战中稳定可靠的关键细节。
- 投票指示函数\\( \mathbb{1}[v_i = c] \\) 表示第 i 个节点是否投票给候选人 c,投了记 1,没投记 0。
- 求和统计对全部 N 个节点的投票结果求和,得到候选人 c 获得的总票数。
- 多数判定票数必须严格大于 N/2。N=5时至少3票,N=3时至少2票。
3. 日志结构
翻开合唱团的乐谱,每一页都标着页码和拍号。Raft的日志也像一本乐谱,由许多“日志条目”组成,每个条目都带着三个关键信息:自己的索引号(页码)、所在任期(拍号)、以及要执行的命令(音符)。日志条目一旦被Leader创建,就会按顺序追加到日志末尾。
日志索引(log index)从1开始连续递增,代表“这是第几条乐谱”。任期编号则记录了这条日志是哪一任Leader创建的,用来区分新旧。命令(command)才是真正的业务操作,比如“把user的余额设为100”,它会在日志提交后被应用到每个节点的状态机中。
正因为每个节点都保存着这样一串日志,且按照完全相同的顺序执行命令,各节点的状态机才能保持一致。日志必须持久化存储——哪怕节点重启,也要重新读回本地日志继续排练,绝不能因为断电而丢失已经写下的谱子。
一本乐谱最重要的就是“页码不乱、拍号清楚、音符准确”。Raft的日志条目把这三件事固化成数据结构,使得任何节点拿出日志都能对齐位置、分清新旧、统一执行。
- 索引定位\\( \mathrm{index}_i \\) 是条目在日志中的位置,从 1 开始连续编号,不可跳跃。
- 任期标记\\( \mathrm{term}_i \\) 记录了创建该条目时的Leader任期,用于跨节点比较新旧。
- 业务命令\\( \mathrm{cmd}_i \\) 是客户端写入的具体操作,最终被送入状态机执行。
4. 复制流程
排练开始了。指挥家把新乐谱第5页发给每位乐手,乐手们翻到第4页确认无误后,才把第5页夹进谱夹。Raft的日志复制也是这个流程:Leader通过AppendEntries RPC把新日志条目发给Follower,同时附上“前一条日志的索引和任期”,要求Follower先做一致性检查。
Follower收到AppendEntries后,先检查自己的日志在prevLogIndex位置是否存在、且任期与prevLogTerm一致。这就相当于乐手先翻到指定页码确认内容,再决定要不要夹进新的一页。如果检查通过,Follower把新条目追加到日志末尾,返回成功;不通过则直接拒绝,让Leader知道“你的谱子跟我的对不上”。
Leader维护着每个Follower的nextIndex(下一个要发送的日志索引)。一旦发现Follower拒绝了某次AppendEntries,Leader就把该Follower的nextIndex减一,回退到更早的位置重新发送,直到找到双方一致的公共前缀,再一次性补齐后面的日志。当一条日志被复制到多数节点,Leader就把它标记为已提交(commit),并推进commitIndex,通知所有节点应用这条命令。
整个复制过程就像师生对谱:每一页都要先确认前一页无误,才敢翻下一页。这种“保守对齐”虽然看似笨拙,却是Raft在乱序网络中依然能保持日志一致的定海神针。
- 前序定位Leader 在 RPC 中携带 \\( \mathrm{prevIndex} \\) 和 \\( \mathrm{prevTerm} \\),告诉 Follower“新条目应该接在哪个音符后面”。
- 任期核对Follower 检查自己日志中该索引处条目的任期是否等于 \\( \mathrm{prevTerm} \\)。
- 追加与返回任期一致则删除冲突条目并追加新条目,返回成功;否则返回失败让 Leader 回退重试。
5. 日志匹配
合唱团里有个绝妙的默契:只要两位乐手在第10页看到的音符完全一致,那么他们前9页一定也完全一致。Raft把这种“前缀一致性”上升为一条铁律,称为日志匹配特性(Log Matching Property)。它的成立不靠猜测,而是靠复制流程中的强制检查一步步推导出来的。
为什么这个性质必然成立?关键在于每一条新日志都必须通过前一条的“索引+任期”检查才能被追加。如果两个节点在索引 i 处的条目相同,说明该条目是通过同一次AppendEntries把前一条也一起对齐后写入的;于是可以往前递推,索引 i-1 处必然也相同,再推到 i-2,直到日志开头。这就是数学归纳法的力量。
日志匹配特性带来了一个杀手级应用:Leader只需找到与Follower第一个匹配的索引,就能确定双方公共前缀,直接删除Follower中所有冲突的后续条目,补齐缺失部分。它让日志对账从“逐个比较所有条目”变成“从后往前快速定位”,极大降低了网络开销。
这段推理如同多米诺骨牌:推倒第一块(找到匹配的索引),后面的所有骨牌(更早的日志)都会依次倒下。日志匹配特性让Raft在日志修复时不必“从头对到底”,成为一个既有数学美感又极具工程效率的设计。
- 前提假设假设两个节点的日志在索引 i 处条目完全相等——包括任期和命令。
- 递推论证由于任何条目的写入都要求前一条匹配,所以索引 i-1 处也必然相等。
- 归纳延伸反复使用该推理,直至索引 1,得到所有前序条目都相同。
6. 安全性规则
有了选举、复制和匹配,Raft还差最后一道防线——安全性规则。就像合唱团规定“只有熟练所有旧曲目的乐手才能当指挥”,Raft要求投票者只把票投给日志“足够新”的候选人。这保证了新Leader一定拥有所有已提交的日志条目,绝不带着“残缺的乐谱”上台。
选举限制的具体规则是:候选人必须拥有比投票者更新的日志才能获得选票。日志谁更新?先比较最后一个条目的任期,任期大的更新;任期相同则比较索引,索引大的更新。这条规则从源头杜绝了“日志落后的节点当选Leader”的可能,也因此保证新Leader一定包含所有已提交的日志。
此外还有提交限制:Leader不能通过统计旧任期日志的数量来直接提交旧日志,只能通过提交当前任期的新日志来“顺带”推进旧日志的提交。这个微妙设计防止了一个经典陷阱——旧Leader带着旧日志复活,在分区期间提交了多数但最终被新Leader覆盖。这两条规则合在一起,构成了Raft的安全性基石:已提交的日志永远不可能丢失。
这就像指挥家竞选时的“资历审查”:乐手们只认那个练得最多、谱子最全的人。看似苛刻的条件,恰恰是保证全部乐手最终能回到同一节奏的安全锁。
- 定义新旧\\( \mathrm{term}^{\mathrm{last}} \\) 和 \\( \mathrm{index}^{\mathrm{last}} \\) 分别表示节点日志最后一条的任期与索引。
- 先比任期候选人的最后任期大于投票者,即认为候选人日志更新;相等再看索引。
- 再比索引任期相等时,索引大的日志更长,视为更新。
- 投票决策只有满足上述比较结果时,投票者才会投出宝贵一票。
7. 对比Paxos
在Raft诞生前,Paxos统治着共识算法领域。但Paxos像一份由数学教授写给小众同行的论文级谱面——概念高度抽象,把单轮共识拆成Prepare、Accept、Learn等多个协议,难度极大,以至于连Lamport自己都承认“大多数人第一遍都看不懂”。工程界迫切需要一套“人人都能照做”的指挥手册。
Raft的战略就是“分而治之”:把共识问题拆成领导者选举、日志复制、安全性三个相对独立的子问题。每个子问题都有直观的日常类比(指挥家、乐谱、规矩),整体复杂度大幅下降。这种“化整为零”正是Raft的核心理念:同样是解决共识,Raft选择了一条让工程师愿意读、读得懂、也改得动的路。
从消息开销上看,Paxos正常运行时需要多轮RPC才能完成一次共识;Raft的正常路径只要一轮AppendEntries广播,加上多数派确认即可提交。虽然极端场景下Paxos也未必慢多少,但Raft把“常用路径”做到了最简单,用工程上的绝大多数情况换取可维护性——这笔账在工业界算得明明白白。
如果说Paxos是一本让人望而却步的“天书”,Raft就是那本装订清晰、循序渐进的操作指南。它牺牲了一点点理论优雅,换来了十倍的可理解性——这也是它在工程界被广泛验证、快速胜出的根本原因。
- 发送广播Leader 向除自己外的 N-1 个 Follower 发送 AppendEntries RPC。
- 接收响应每个收到请求的 Follower 返回一个确认响应,Leader 共接收 N-1 条响应。
- 合计开销一轮完整复制消耗的消息总数为两者之和。
8. 工业应用
当合唱团的经验足够成熟,就会被请进真正的音乐厅演出。Raft同样在工业界找到了自己的舞台:etcd和Consul就是最闪亮的两位“独奏家”。etcd是Kubernetes背后的数据中枢,把集群配置、服务发现信息以键值对形式存储在Raft日志中;Consul则利用Raft实现跨数据中心的服务注册与健康检查,保证“无论哪个节点宕机,服务目录依旧完整”。
为什么要“多数派”?因为它天然赋予了系统容错能力。一个5节点的Raft集群,只要仍有3个节点存活,就能正常选举Leader并提交日志。也就是说,最多允许2台节点同时宕机而系统不停止服务。这个“过半即活”的特性被广泛应用于生产环境的容灾设计:3节点容忍1故障,5节点容忍2故障,以此类推。
工程实践进一步丰富了Raft的细节:Leader需要定期持久化当前任期和日志,避免重启后产生混淆;选举超时要加入随机抖动,防止多个Candidate同时分裂选票;网络分区时,少数派一侧会不断选举失败,恢复后自动回滚日志与多数派对齐。这些细节让Raft从“纸面正确”走向“生产可用”。
从合唱团排练到音乐厅演出,Raft用工业级实践证明了自己不只是“好看的算法”——在etcd的键值存储、Consul的服务目录、各类分布式数据库的日志层中,它都是那根最可靠的指挥棒,让成千上万台服务器始终演奏同一首曲目。
- 半数取整先计算 \\( \lceil N/2 \rceil \\),即 N 除以 2 后向上取整,得到多数派所需最少节点数。
- 减去自身多数派以外的节点允许离线,数量为 Quorum 减 1。
- 代入实例N=5 时,Quorum = 3,最多容忍 2 台宕机;N=3 时,最多容忍 1 台。
AI 应用场景
etcd与Kubernetes的配置中枢
阶段:系统架构与高可用设计
解决的问题:Kubernetes集群需要所有节点对“当前状态”达成一致。若每台机器各自为政,调度器、控制器和节点之间立刻陷入混乱。Raft通过日志复制让etcd成为唯一可信的配置真相源——所有写请求经Leader落盘到多数节点后提交,系统永远对外呈现一致状态。
具体案例:你在Kubernetes中创建Deployment,API Server把资源对象写入etcd。Raft负责在etcd集群内部复制这条写入日志,3节点etcd中2个确认即返回成功。即便1台etcd节点宕机,集群仍能继续读写,Kubernetes控制面因此保持全部功能可用。
Consul服务发现与容灾切换
阶段:服务网格与控制面一致性
解决的问题:微服务架构中,服务注册表一旦不一致,调用方可能把流量打到已宕机的实例上。Consul利用Raft日志复制,在服务实例注册、注销时同步到多数节点,确保所有数据中心(Data Center)的查询都能看到同一个稳定视图。
具体案例:某金融系统用Consul管理3个数据中心的2000个微服务实例。当某实例宕机,健康检查触发注销操作,Raft把注销日志复制到多数节点后才响应客户端,从而避免“部分节点以为服务还活着”的脏读问题。
Python 演示
class LogEntry: """日志条目:相当于乐谱上的一个音符""" def __init__(self, index, term, command): self.index = index self.term = term self.command = command class RaftNode: """简化版 Raft 节点:只关注日志复制与多数提交""" def __init__(self, node_id, peers): self.node_id = node_id self.peers = peers self.log_list = [] self.term = 0 self.commit_index = 0 self.matched_index_map = {} def become_leader(self, term): self.term = term for peer in self.peers: self.matched_index_map[peer] = 0 def append(self, command): entry = LogEntry(len(self.log_list) + 1, self.term, command) self.log_list.append(entry) return entry def on_append_response(self, peer, acked_index): if acked_index > self.matched_index_map.get(peer, 0): self.matched_index_map[peer] = acked_index self.maybe_commit() def maybe_commit(self): count = 1 for peer, idx in self.matched_index_map.items(): if idx >= len(self.log_list): count += 1 if count > len(self.peers) // 2: self.commit_index = len(self.log_list) def simulate(): leader = RaftNode(1, [2, 3]) leader.become_leader(1) leader.append("set user=alice") leader.on_append_response(2, 1) print("Follower2 确认,票数 2/3,尚未构成多数") print("commit_index =", leader.commit_index) leader.on_append_response(3, 1) print("Follower3 确认,3 节点中 2 个确认,已构成多数") print("commit_index =", leader.commit_index) if __name__ == "__main__": simulate()
self.matched_index_map = {}记录每个Follower已确认的最新日志索引,相当于乐谱对账表。def become_leader(self, term):新Leader上台时初始化任期,并把所有Follower的确认进度清零。entry = LogEntry(len(self.log_list) + 1, self.term, command)每个新条目都带着索引和任期,缺一不可。if acked_index > self.matched_index_map.get(peer, 0):Follower确认更高的索引时才更新对账表,防止旧确认覆盖新进度。count = 1多数统计从Leader自己开始,因为Leader本就持有这条日志。for peer, idx in self.matched_index_map.items():遍历所有Follower的确认情况。if count > len(self.peers) // 2:确认数严格过半,才推进commit_index。leader.append("set user=alice")模拟一次客户端写入操作,日志中增加一条命令。leader.on_append_response(2, 1)模拟Follower 2确认收到了索引1的日志条目。
常见误区
误区“Raft要等所有Follower都确认收到日志,才能算提交成功。不然没收到日志的节点不就丢数据了吗?”
正解Raft只需要多数派确认,标准是“过半即提交”。少数落后节点会在恢复后通过Leader的日志修复机制补齐缺失条目。比如3节点集群,2个Follower确认就能提交,第三个节点稍后追平即可。追求全量确认不仅慢,而且违背了分布式系统“部分节点故障不影响整体”的设计初衷。
为何常见:人们习惯用单机思维理解数据安全——总觉得“我自己的数据必须保存好”。但在分布式系统中,安全由多数派冗余保证,而不是单点完整性。
误区“既然已经选了Leader出来,只要它不宕机,就一直由它当家做主,不会变。”
正解Raft的Leader任期不是终身制。一旦Leader所在节点发生网络分区、长时间失控,其余节点的Follower会在选举超时后发起新一轮选举并产生新Leader。旧Leader恢复后即使仍认为自己是指挥家,也会因为任期落后而主动退位成Follower。领导地位永远属于“当前任期最高的多数派认可者”。
为何常见:人习惯性地认为“选出来就稳定了”,却没意识到网络环境天然存在抖动和分区。Raft恰恰要用不断更新的任期来矫正这类“权威幻觉”。
误区“所有Follower设置一样的选举超时,大家同步发起选举,效率更高。”
正解恰恰相反。如果所有Follower在相同时间点超时,它们会同时变成Candidate,票数被分票,谁也拿不到多数,最终进入反复选举的僵局。Raft引入随机化选举超时,让每个节点的超时时间略有差异,最先超时的Candidate有很大概率先发起拉票并胜出,避免撕裂选票。
为何常见:直觉上“同步”听起来高效,但分布式系统最怕的就是“一群节点同时行动”。随机化不是不严谨,而是防止共振的成熟工程智慧。
误区“上一任Leader已经把某条日志复制到了多数节点,那新Leader一上台看到这条日志已经过半确认,当然可以立刻把它标记为已提交。”
正解Raft明确规定:Leader不能直接通过“数旧任期日志的副本数”来提交该日志。它只能等到自己任期内产生了新日志,再把旧日志随新日志一并“顺手”提交。因为旧日志虽然可能过半,但无法确定是否会被更新的Leader覆盖;只有当前任期日志提交了,才能借提交推进机制确认旧日志安全。
为何常见:人们容易把“多数派存在”直接等同于“已提交”。Raft的微妙之处在于,旧任期的多数派并不可靠,必须用当前任期的新日志作为“安全引信”去点燃旧日志的提交。
面试题
Q1. Raft集群中的每个节点可能扮演哪些角色?
- A.仅 Leader 与 Follower
- B.Leader、Follower、Candidate← 正确答案
- C.主节点、从节点、备份节点
- D.生产、消费、协调节点
答案:B。Raft只有三种角色:Leader、Follower、Candidate。C的“备份”和D的“生产消费”都是干扰项,A缺少选举所需的Candidate,无法描述选举状态机。
Q2. Follower在选举超时后首先会变成什么?
- A.直接变成 Leader
- B.变成 Candidate 发起选举← 正确答案
- C.继续保持 Follower
- D.向其他节点请求提交
答案:B。Follower超时收不到心跳,会自增任期并转为Candidate,然后先投自己一票再请求他人投票。它不会直接变成Leader,必须经过选举并获得多数票。
Q3. Raft中候选人当选Leader的最基本条件是什么?
- A.获得超过半数的节点投票← 正确答案
- B.获得所有节点的投票
- C.日志长度必须最长
- D.任期编号必须最大
答案:A。选举只要求“过半选票”,即Quorum。日志最长和任期最大都不是直接条件,而是安全性规则约束候选人日志足够新,但最终获胜仍需多数票。
Q4. Leader向Follower发送日志复制请求使用的是哪个RPC?
- A.RequestVote RPC
- B.AppendEntries RPC← 正确答案
- C.Heartbeat RPC
- D.CommitEntry RPC
答案:B。AppendEntries RPC同时承担心跳和日志复制的双重职责。RequestVote用于选举投票,没有CommitEntry这种RPC,心跳只是AppendEntries的空条目形式。
Q5. 日志匹配特性最核心的保证是什么?
- A.日志永远不会被删除
- B.相同索引处的日志相同则前序全部相同← 正确答案
- C.所有节点的日志长度始终一致
- D.日志可以按照任何顺序执行
答案:B。日志匹配特性指若两个日志在某索引处条目相等,则所有更早索引的条目也相等。这是数学归纳的结果,也是Leader安全覆盖Follower冲突日志的前提。
Q6. 关于Raft的安全性规则,下列说法正确的有?
- A.候选人的日志不能比投票者旧← 正确答案
- B.新Leader一定包含所有已提交日志← 正确答案
- C.Leader可以直接提交旧任期日志
- D.已提交的日志永远不会丢失← 正确答案
答案:ABD。选举限制要求候选人日志足够新,保证新Leader包含全部已提交日志;已提交日志的持久性由安全性规则守护。C错误——Leader不能直接提交旧任期日志,必须先提交当前任期日志来间接推进。
Q7. 与Paxos相比,Raft最核心的设计理念是什么?
- A.更高的吞吐性能
- B.拆分子问题以提升可理解性← 正确答案
- C.用更少的节点达成共识
- D.完全不需要选举过程
答案:B。Raft把共识拆成选举、复制、安全三个子问题,大幅降低理解门槛。性能并非Raft的核心卖点,它和Paxos解决同一类问题、都需要选举,可理解性才是Raft胜出的关键。
Q8. 在一个5节点etcd集群中,下列哪些说法正确?
- A.最多容忍2台节点同时宕机← 正确答案
- B.最多容忍3台节点同时宕机
- C.提交日志至少需要3个节点确认← 正确答案
- D.提交日志至少需要2个节点确认
答案:AC。5节点集群多数派为3,因此至少3个节点确认才能提交,最多容忍2台宕机。选项B和D分别混淆了容忍度和确认数的计算。
Q9. 旧Leader恢复后与当前Leader共存时会发生什么?
- A.继续以Leader身份发送指令
- B.因任期落后而退位为Follower← 正确答案
- C.与新Leader共同组成双主
- D.自动成为Candidate再次竞选
答案:B。Raft通过任期区分新旧权威。旧Leader的任期小于当前任期,其AppendEntries会被Follower拒绝;收到更高任期的请求后,旧Leader也会发现自己任期落后,退位为Follower并同步日志。
Q10. Follower收到AppendEntries后发现prevLogIndex位置没有对应条目时,应该怎么做?
- A.直接接受新日志并追加
- B.返回成功让Leader继续发送
- C.拒绝该请求并让Leader回退← 正确答案
- D.立刻发起新一轮选举
答案:C。一致性预检查不通过时,Follower返回失败,Leader据此将该节点的nextIndex回退并重试,直到找到共同的日志前缀。直接接受会导致日志乱序,发起选举也不符合规则。