从Paxos到Raft:两种共识算法的通俗解读!

哈喽,博客园的各位朋友,大家好!我是你们的老朋友小明他不是名。今天咱们不聊高深莫测的理论,也不搬弄晦涩难懂的术语,就专门来聊聊分布式系统里那个“神一样”的存在——共识算法。我会用说故事的方式,从Paxos讲到Raft,争取让每一位朋友都能轻松看明白,快速掌握核心要点。
从 Paxos 到 Raft:共识算法演义,一看就懂的分布式一致性
一、开篇:为什么需要“大家说了算”?
想象一下,你和小明他不是名、小红、小刚三个人一起管理一个非常重要的“记账本”。这个账本只有一份,但每个人都能修改。问题来了:如果你们三个人同时想修改账本(比如给同一个人转账),以谁说的为准?怎么保证最后账本上的数据一模一样,不出乱子?
这就引出了分布式系统的核心难题:一致性。
在计算机的世界里,解决这个难题的“明星方案”就是共识算法。它的作用就是让一群可能出故障、可能网络延迟的服务器节点,能对某个值(比如“到底该给谁转账10块钱”)达成一致。
而在这个领域,有两个名字如雷贯耳:Paxos 和 Raft。今天,我们就来理一理它们的前世今生。
二、 Paxos:聪明但难懂的“老前辈”
Paxos 算法就像一位智商极高但表达方式非常抽象的数学教授。它早在 1990 年就被大神 Leslie Lamport 提出来了,但因为它实在太难懂了(连作者自己都用“希腊议会”的故事来比喻,让很多人更迷糊了),所以很长一段时间都活在论文里,很少有人能真正实现它。
Paxos 的核心思想(大白话版):
在一个 Paxos 集群里,有三个角色(但同一个节点可以身兼多职):
提议者 (Proposer):负责提出议案的人,比如“我建议把页面颜色改成绿色”。
接受者 (Acceptor):负责投票表决的人。多数(超过一半)接受者同意,议案就通过。
学习者 (Learner):不投票,只负责学习最终的结果。
Paxos 的核心流程分两步(两阶段提交):   https://www.kkkmir.com/dzycq/362.html  https://www.sofuba.com/dzycq/362.html  
准备阶段 (Prepare):提议者先生成一个唯一的编号(比如 001),然后问所有接受者:“我想提议编号 001,你们有没有已经批准过别的议案?” 接受者会回复:“我没批准过别的。如果你之后提议编号 001,我会只考虑你的。”
接受阶段 (Accept):提议者收到超过半数接受者的“承诺”后,就正式发送请求:“好,那请接受我的提议:页面改成绿色。”
代码模块 1:Paxos 准备阶段的简化模拟

# 小明他不是名的 Paxos 模拟小剧场
class Acceptor:
    def __init__(self, node_id):
        self.node_id = node_id
        self.promised_id = 0  # 承诺过的最大编号
        self.accepted_id = 0  # 已经批准的最大编号
        self.accepted_value = None

    def handle_prepare(self, proposal_id):
        # 准备请求:如果收到的编号大于承诺过的编号,就承诺
        if proposal_id > self.promised_id:
            self.promised_id = proposal_id
            return True, self.accepted_id, self.accepted_value
        else:
            return False, None, None

# 示例:提议者发送编号为 5 的准备请求
acceptor_1 = Acceptor("节点A")
result = acceptor_1.handle_prepare(5)
print(f"准备阶段结果: {result}")  # 输出:(True, 0, None) 代表成功

Paxos 的优点: 极其经典,正确性被数学证明,非常可靠。
Paxos 的缺点: 太难实现了!单轮决策可能死循环,需要额外机制(比如选出一个主提议者),理解成本和学习曲线陡峭。
三、Raft:为了“让人看懂”而生的后起之秀
时间来到了 2013 年。斯坦福大学的 Diego Ongaro 和 John Ousterhout 教授也忍不了 Paxos 的复杂了。他们说:“我们一定要设计一个和 Paxos 一样正确,但更容易理解的共识算法。”
于是,Raft 诞生了。
Raft 没有发明全新的理论,而是做了一次极其出色的“工程封装”和“模块化设计”。它把共识问题拆分成了三个相对独立的子问题:
领导者选举 (Leader Election):选出一个老大,平时由老大说了算。
日志复制 (Log Replication):老大把决策记录下来,并同步给所有小弟。
安全性 (Safety):保证万一老大出故障了,新的老大一定拥有最全的数据。
四、Raft 核心流程:像选班长一样简单
1. 领导者选举:   https://www.kfzhan.com/kfxx/455.html   https://www.99sf.com.cn/game/98.html  
一开始,所有节点都是“跟随者 (Follower)”。
如果一个跟随者在一段时间内没听到老大的“心跳”(空的日志复制请求),它就认为老大挂了,自己变成“候选者 (Candidate)”。
候选者给自己投一票,然后拉票:“请选我当新班长!”
其他节点收到拉票请求,如果还没有投给别人,就会同意。票数超过半数的节点成为新的领导者。
代码模块 2:Raft 选举超时的简单示意

import random
import time

# 每个跟随者都有一个随机的选举超时时间(比如 150-300 毫秒)
election_timeout = random.randint(150, 300) / 1000  # 秒
print(f"当前节点的选举超时时间为: {election_timeout} 秒")

# 模拟等待心跳
start_time = time.time()
# 假设没有收到心跳
if time.time() - start_time > election_timeout:
    print("超时未收到心跳!我将成为候选者并发起选举。")

2. 日志复制:
选举完成后,客户端的请求都发给领导者。
领导者把客户端的操作(比如“设置 X=5”)写进自己的日志里,然后给所有跟随者发“日志复制”请求。
当超过半数的跟随者回复“已写入”后,领导者就把这条日志标记为“已提交”,然后应用到状态机(真正修改 X 的值)。
最后,领导者通知跟随者:“好了,这条日志已经提交了,你们也应用到自己的状态机吧!”
代码模块 3:Raft 日志条目的模拟  https://www.93game.com.cn/wtcmcq/84.html  https://www.787game.com.cn/xinfu/80.html  

class LogEntry:
    def __init__(self, term, command):
        self.term = term      # 这条日志产生时的任期编号
        self.command = command # 具体的操作指令,例如 {"key": "X", "value": 5}

# 创建一个日志条目
entry1 = LogEntry(1, "set X=5")
print(f"日志条目: 任期={entry1.term}, 指令={entry1.command}")

代码模块 4:领导者向单个跟随者发送 AppendEntries RPC 的简化结构

# AppendEntries RPC 请求的参数(通常)
def append_entries_request(leader_term, leader_id, prev_log_index, prev_log_term, entries, leader_commit):
    return {
        "term": leader_term,
        "leaderId": leader_id,
        "prevLogIndex": prev_log_index,  # 前一条日志的索引,用于一致性检查
        "prevLogTerm": prev_log_term,    # 前一条日志的任期
        "entries": entries,              # 要复制的日志条目(可多条)
        "leaderCommit": leader_commit    # 领导者已提交的日志索引
    }

# 模拟请求
req = append_entries_request(term=2, leader_id="nodeA", prev_log_index=5, prev_log_term=1, entries=[entry1], leader_commit=5)
print("领导者发送的日志复制请求结构:", req.keys())

代码模块 5:跟随者处理 AppendEntries 的简单逻辑

def follower_handle_append_entries(follower_log, req):
    # 一致性检查:检查自己 prev_log_index 位置的日志 term 是否匹配
    if len(follower_log) >= req["prevLogIndex"] and follower_log[req["prevLogIndex"]-1].term == req["prevLogTerm"]:
        # 匹配成功,追加新日志
        follower_log.extend(req["entries"])
        # 根据 leaderCommit 更新自己的提交索引
        return True, follower_log
    else:
        # 匹配失败,拒绝
        return False, follower_log

# 模拟跟随者日志
follower_log = []
success, new_log = follower_handle_append_entries(follower_log, req)
print(f"日志复制是否成功: {success}")

代码模块 6:Raft 节点状态的转换  https://www.387game.com.cn/xfsd/80.html  www.644game.com.cn  www.887game.com.cn  

class RaftNode:
    def __init__(self, node_id):
        self.node_id = node_id
        self.state = "Follower"  # 三种状态: Follower, Candidate, Leader
        self.current_term = 0
        self.voted_for = None

    def become_candidate(self):
        self.state = "Candidate"
        self.current_term += 1
        self.voted_for = self.node_id
        print(f"节点 {self.node_id} 在任期 {self.current_term} 成为候选者")

    def become_leader(self):
        self.state = "Leader"
        print(f"节点 {self.node_id} 在任期 {self.current_term} 成为领导者!")

# 模拟角色转换
node = RaftNode("小明他不是名")
node.become_candidate()
node.become_leader()

五、Paxos vs Raft:一图胜千言(思想比较)
特点    Paxos    Raft
设计目标    极致正确性    正确性 + 易于理解
角色分工    提议者、接受者、学习者(角色较抽象)    领导者、跟随者、候选者(角色清晰)
决策流程    两阶段(准备+接受),可并发提议    强领导者 + 日志复制,顺序清晰
领导者选举    没有明确选举机制,需额外设计    内置明确、易理解的选举机制(心跳+超时+投票)
难点    活锁问题、理解门槛高    成员变更、快照处理稍复杂     www.08game.com.cn   www.34game.com.cn  www.48game.com.cn   
工程应用    很多经典系统(如Chubby)内部使用,但被封装    更受欢迎:etcd, Consul, TiKV 等
代码模块 7:简单的“多数派”判断

# 总节点数
total_nodes = 5
# 需要获得的票数或成功响应数
majority = total_nodes // 2 + 1
print(f"5个节点中,需要至少获得 {majority} 票才能达成共识。")

# 模拟投票结果
votes_received = 3
if votes_received >= majority:
    print("已获得多数派支持,决策通过!")
else:
    print("未达多数派,决策失败。")

六、问答环节(一)
问:小明他不是名,我看网上有人说Raft就是“更容易实现的Paxos”,这种说法对吗?
答: 这样说有一定道理,但不完全准确。更精确的说法是:Raft 和 Paxos 在解决同一个问题,但思路不同。 Paxos 更像是一个数学公式,给你最大的灵活性,但也容易用错。Raft 则是在 Paxos 思想的基础上,重新设计了流程,通过“选出一个强领导者”和“模块化”的方式,把复杂的多提案场景变得有序可控。所以 Raft 不 是简化版的 Paxos,而是 “为可理解性重新设计的共识算法”。
七、成员变更与日志压缩(Raft 的进阶话题)   www.38game.com.cn  https://www.77jhw.cn/xiakezhinan/416.html  https://www.fugucqsf.cn/daq/799.html  
当然,实际生产环境比理论复杂。比如,集群想换一台机器(成员变更),怎么保证在变更过程中不出问题?Raft 采用了一种“两阶段”的方式,通过共同联合体 (Joint Consensus) 来平滑过渡。另外,日志不能无限增长,Raft 会定期做快照,把当前状态保存下来,然后丢弃之前的日志。
代码模块 8:Raft 快照的模拟(示意)

# 应用状态机当前的状态:一个简单的字典数据库
state_machine = {
    "user:1001": {"name": "小明他不是名", "balance": 100},
    "user:1002": {"name": "小红", "balance": 200}
}

# 创建快照:保存当前状态和最后一条日志的索引/任期
def create_snapshot(state_machine, last_included_index, last_included_term):
    snapshot = {
        "data": state_machine.copy(),
        "last_included_index": last_included_index,
        "last_included_term": last_included_term
    }
    # 这里可以写snapshot到磁盘
    return snapshot

snap = create_snapshot(state_machine, last_included_index=100, last_included_term=5)
print(f"创建了快照,包含 {len(snap['data'])} 条用户数据,最后日志索引为 {snap['last_included_index']}")

八、总结:我们该如何选择?
如果你是做学术研究,想深入理论底层,那么 Paxos 及其变种(Multi-Paxos, Fast Paxos)是必修课。
如果你是做工程开发,需要为自己的分布式系统(比如配置中心、分布式数据库)选择一个共识算法库,那么首选 Raft。因为它的实现(如 etcd 的 Raft 库)更成熟、文档更友好,出问题也更容易排查。
从 Paxos 到 Raft,我们看到的是计算机科学中“理论落地”的经典案例:一个极致的理论(Paxos)启发了后人,而一个“以人为本”的重设计(Raft)最终让技术普惠了大众。
希望看完这篇“演义”,你对这两位“共识巨星”有了更清晰的认识。分布式一致性的世界,并没有那么可怕。
九、问答环节(二)   https://www.wowanjh.cn/gljq/416.html  https://www.soufucq.cn/bbk/926.html  https://www.liuliujh.cn/kaiqudongtai/408.html 
问:小明他不是名,你说了半天,有没有最简明的记忆口诀?
答: 必须有!
Paxos 记忆点:两个阶段(准备+接受),一个核心(多数派)。如果看不懂,不是你的错,是它太“数学”。
Raft 记忆点:先选头,后干活,日志复制多数过;心跳超时防分裂,一切决策由老大定。
记住这几句话,你就抓住了核心脉络。

更多经验分享:

从混乱到清晰:后端如何为 Local First 任务管理提供可靠的增量同步 API! - 小明他不是名 - 博客园

后端学习总卡壳?三个坑避开少走弯路! - 小明他不是名 - 博客园

高并发下Redis 8个致命误区,你用对了吗? - 小明他不是名 - 博客园

线程池队列暗坑:LinkedBlockingQueue内存占用真相! - 小明他不是名 - 博客园

ForkJoinPool:看着智能,其实一阻塞就“卡壳”! - 小明他不是名 - 博客园

posted @ 2026-06-15 19:00  小明他不是名  阅读(9)  评论(0)    收藏  举报