共识问题是分布式系统最根本的问题:如何让多个独立节点就某个值达成一致决定,且这个决定是最终的、不可撤销的。
安全性 (Safety):永远不会返回错误结果——所有节点一旦决定,就决定了同一个值。
活跃性 (Liveness):只要多数节点存活且能通信,就一定能做出决定。
容错性 (Fault Tolerance):5 节点集群容忍 2 节点故障,3 节点集群容忍 1 节点故障。
共识的典型应用场景:
Raft 由 Diego Ongaro 和 John Ousterhout 在 2014 年发表(In Search of an Understandable Consensus Algorithm),获 USENIX ATC 最佳论文奖。核心理念:Paxos 难以理解是它的问题,不是共识本身的问题。
Raft 将共识分解为三个相对独立的子问题,每个都可以独立理解、实现和测试:
集群中必须有一个 Leader,所有写请求通过 Leader 处理。Leader 周期性发送心跳维持权威。如果 Follower 在 Election Timeout 内没收到心跳,就发起选举。
状态机:Follower → Candidate → Leader
Leader 接收客户端请求,追加到自己的日志,然后复制到所有 Follower。当多数 Follower 确认后,条目被提交(committed),Leader 通知客户端成功。
关键约束:如果两个日志条目有相同的 term 和 index,则它们存储的命令相同
Raft 的安全性保证:如果一个条目在某个 term 被提交,那么所有更高 term 的 Leader 都会包含这个条目。这通过选举限制实现——Candidate 必须让自己的日志至少和多数节点一样新才能赢得选举。
// Leader Election 核心逻辑
func (rf *Raft) tickElection() {
rf.electionTimer.Reset(randomTimeout(150ms, 300ms))
if rf.state != Leader {
rf.startElection()
}
}
func (rf *Raft) startElection() {
rf.state = Candidate
rf.currentTerm++
rf.votedFor = rf.me // 投自己一票
votes := 1
for _, peer := range rf.peers {
if peer == rf.me { continue }
go func(peer int) {
reply := &RequestVoteReply{}
ok := rf.sendRequestVote(peer, &RequestVoteArgs{
Term: rf.currentTerm,
CandidateId: rf.me,
LastLogIndex: rf.log.lastIndex(),
LastLogTerm: rf.log.lastTerm(),
}, reply)
if ok && reply.VoteGranted {
votes++
if votes > len(rf.peers)/2 && rf.state == Candidate {
rf.becomeLeader()
}
}
}(peer)
}
}
// Log Replication 核心逻辑
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
// 1. Reply false if term < currentTerm
if args.Term < rf.currentTerm {
reply.Success = false
return
}
// 2. Reply false if log doesn't contain entry at prevLogIndex
if !rf.log.hasEntry(args.PrevLogIndex, args.PrevLogTerm) {
reply.Success = false
return
}
// 3. Delete conflicting entries, append new ones
rf.log.truncateAndAppend(args.Entries)
// 4. Update commitIndex
if args.LeaderCommit > rf.commitIndex {
rf.commitIndex = min(args.LeaderCommit, rf.log.lastIndex())
}
reply.Success = true
}
Paxos 由 Leslie Lamport 在 1989 年提出(1998 年正式发表),是分布式共识的理论基石。Raft 本质上是 Paxos 的一个特定变体,但做了关键的设计选择来提升可理解性。
Basic Paxos 解决单值共识。三个角色:Proposer(提出提案)、Acceptor(投票接受)、Learner(学习结果)。
| 维度 | Raft | Paxos |
|---|---|---|
| 设计目标 | 可理解性 | 最小化消息数量 |
| Leader | 强 Leader——所有请求通过 Leader | 弱 Leader(Multi-Paxos 优化) |
| 日志管理 | 连续日志,简单覆盖冲突 | 允许日志空洞,更灵活但更复杂 |
| 成员变更 | Joint Consensus(两阶段) | 依赖外部配置管理 |
| 性能 | 与 Paxos 等价(论文证明) | 理论最优消息复杂度 |
| 实现难度 | 中等——分解为独立子问题 | 高——缺乏工程指导 |
| 生产使用 | etcd, Consul, CockroachDB, TiKV | Google Chubby, Spanner (变体) |
Lamport 本人也承认 Paxos 太难理解。他的论文用古希腊议会做比喻,很多人读完还是不知道怎么实现。Google Chubby 的作者 Mike Burrows 说:"There are significant gaps between the description of the Paxos algorithm and the needs of a real-world system." 这正是 Raft 诞生的原因。
etcd 是 Kubernetes 的状态存储,所有集群配置、Pod 状态、Service 发现都存在 etcd 中。使用 Raft 保证一致性。
关键数据(来源:etcd.io):
Consul 使用 Raft 实现 Leader Election 和日志复制,提供服务发现、Service Mesh、配置管理。
关键数据(来源:hashicorp.com):
CockroachDB 和 TiKV 都使用 Raft(变体)实现多副本一致性。不同的是,它们将 Raft 运行在每个 Range/Region 上,而不是全局——这是一个关键的架构决策:
# 节点 1
etcd --name node1 \
--listen-client-urls http://10.0.0.1:2379 \
--advertise-client-urls http://10.0.0.1:2379 \
--listen-peer-urls http://10.0.0.1:2380 \
--initial-advertise-peer-urls http://10.0.0.1:2380 \
--initial-cluster node1=http://10.0.0.1:2380,node2=http://10.0.0.2:2380,node3=http://10.0.0.3:2380 \
--initial-cluster-token my-etcd-cluster \
--initial-cluster-state new
# 写入数据
etcdctl put /config/key "value"
etcdctl get /config/key
# 查看 Leader
etcdctl endpoint status --write-out=table
# 查看集群成员
etcdctl member list --write-out=table
# 分布式锁
etcdctl lock my-lock -- ./my-script.sh
# Leader 选举
etcdctl elect my-election "candidate-value"
# Python 示例:监听 etcd key 变更
import etcd3
etcd = etcd3.client()
# Watch 一个前缀下的所有 key
events_iterator, cancel = etcd.watch_prefix('/services/')
for event in events_iterator:
if isinstance(event, etcd3.events.PutEvent):
print(f"Service registered: {event.key} = {event.value}")
elif isinstance(event, etcd3.events.DeleteEvent):
print(f"Service deregistered: {event.key}")
# 事务:CAS (Compare-And-Swap)
etcd.transaction(
compare=[etcd.transactions.version('/lock') == 0],
success=[etcd.transactions.put('/lock', 'owner-1')],
failure=[]
)
当多个 Agent 需要就"下一步做什么"达成一致时,这就是一个共识问题。考虑以下场景:
AutoGen 的 GroupChat 使用一个 GroupChatManager 来决定"下一个说话的 Agent 是谁"。这本质上是一个单 Leader 的弱共识——如果 Manager 挂了,整个对话就停了。没有选举机制,没有日志复制。
如果要构建生产级的多 Agent 系统,需要考虑:
CrewAI 使用 Crew 来编排 Agent,本质上是一个集中式调度器。这比 AutoGen 更结构化,但仍然是单点——如果 Crew Manager 失败,需要外部重启。
与 Agent 架构知识库 的联系:多 Agent 编排模式的选择(集中式 vs 分布式)直接影响系统的可用性和扩展性。
Raft 解决的是复制日志的一致性。它不解决:跨分片事务、读写分离的一致性、客户端看到的线性一致性(需要额外的 ReadIndex 机制)。
多数派保证了决策能达成,但不保证性能可接受。如果 5 节点中 2 个慢节点,虽然能工作,但每次写入要等最慢的 Follower 确认。实际部署中,网络延迟的差异比故障更常见。
Raft 选举依赖随机超时(150-300ms)。在最坏情况下,多个 Candidate 同时竞选,可能需要多轮选举才能产生 Leader。分区间隔意味着服务中断。