etcd 使用 Raft 为复制状态的变化确定顺序。副本可以暂时落后,共识不表示每个成员在每一瞬间都保存相同字节。领导者协调复制,投票多数决定能否继续推进。判断操作是否成功时,应区分选举、提交、本地应用与客户端收到确认,不能把这些阶段压缩成一个含义不明的“已经写入”。
每个任期的领导者与投票限制
候选人需要多数票,每位投票者在同一任期最多投一票。日志新旧也参与判断,任期更大并不意味着日志落后的候选人就能取得所有选票。随机选举超时可以减少反复平票。任期排列领导权尝试,不衡量经过的时间,也不能单独证明某个成员曾经发生网络分区。
对于当前任期的条目,领导者复制到多数后可以推进提交;旧任期条目会随着当前任期条目提交而间接提交,不能只数旧条目已有多少副本就完整解释 Raft 规则。在协议的存储与故障假设内,后续领导者保留已提交条目。多数持久数据同时丢失,或恢复到旧备份,是另一类灾难恢复问题,不能用共识安全性代替备份。
选举期间,进展会暂停到领导者建立。客户端请求可能等待、超时或失败,取决于截止时间和连接情况。超时不能证明提议从未提交。应用需要合适的重试或事务设计,不能把每次失败响应都解释成可以重复非幂等业务效果的许可。诊断时还应区分服务端是否提交与客户端是否知道结果。
数投票者,不是数进程
包含 N 位投票者的配置,需要 floor(N/2)+1 形成多数。三位允许一位不可用,四位仍只允许一位,却需要三位参与;五位和六位都容忍两位失效。与前一个奇数相比,偶数配置没有增加可容忍失效数,不过受控成员变更期间可能暂时经过偶数配置。这个算术描述稳定投票配置,不意味着成员可以随意删改。
learner 接收复制但不投票,因此加入它不会改变投票仲裁数;追赶检查通过后的提升才改变计数。这样避免立即依赖一份刚加入的空副本投票,但追赶流量仍消耗资源,可能影响可用性。仲裁数不变,不保证加入 learner 绝不引起超时或促成选举;容量、网络与磁盘负载仍需观察。
坏掉的样子
缺少可达多数时,集群无法正常推进写入。serializable 读取可以使用某个成员的本地状态,因此可能返回旧值;linearizable 读取需要建立当前状态的协调,不能理解成只是给本地读取换了名字。它也不要求每次读都追加一个日志条目,所以把所有线性一致读直接称为复制写入,会掩盖实际实现和性能差异。
网络分区中的少数派不能独立提交另一段冲突历史。分区恢复的具体行为取决于配置。etcd 3.5 默认启用 pre-vote,以减少失联成员重新加入时的扰动,因此恢复分区并不必然引发更高任期选举和写入中断。应检查配置、时序与日志,不能断言每个孤立成员都会不断提升正式任期,也不能仅凭某个较大值推导全部故障经过。
丢失一个跟随者时,剩余投票者可能仍足够继续服务,但落后会减少冗余,也会影响本地读取。不能等到多数消失才重视它。端点探测失败同样可能来自客户端路由、TLS、权限、超时或活动告警,而不等于投票者已死亡。客户端可达性与成员之间的仲裁通信,是两种需要分别检查的观察。
快速验证
下面的 Python 3 算术用例不联系任何集群。它打印投票门槛,并检查 learner 不参与计数的区别,不实现领导者选举或日志复制。用这个小实验先验证配置算术,再把结果与真实成员清单对应,避免把进程总数直接当成投票者数量。
def quorum(voters):
return voters // 2 + 1
for voters in (1, 2, 3, 4, 5, 6):
required = quorum(voters)
print(f"voters={voters}, quorum={required}, tolerated={voters-required}")
assert quorum(3) == 2 and quorum(4) == 3
assert 2 < quorum(4) # Neither side of a 2+2 partition has a majority.
# A learner replicates data but is not included in the voting count.
voters, learners = 3, 1
assert quorum(voters) == 2
print("PASS: adding a learner leaves the voting quorum unchanged")
一至六名投票者的输出仲裁数依次是一、二、二、三、三、四。四位投票者分成二加二时,两侧都不构成多数;三位投票者再加一位 learner,仍需要两票。PASS 只证明这些计算,不证明真实集群在故障中的行为,更不替代日志持久化和网络分区测试。
下面保留原有运维示例,本轮没有重跑。这些私有地址和 /registry/ 前缀只是占位示例;只能在另行获准的测试集群中替换为相应端点、TLS 和认证设置,以及不敏感的测试键。本轮没有运行经过认证的集群或分区实验,不应把它们写成已经通过的集成验证。
etcdctl --endpoints=https://10.0.1.11:2379 endpoint status --write-out=table
etcdctl --endpoints=https://10.0.1.11:2379,https://10.0.1.12:2379,https://10.0.1.13:2379 endpoint health --write-out=table
etcdctl --endpoints=https://10.0.1.11:2379 member list --write-out=table
etcdctl --endpoints=https://10.0.1.11:2379 --consistency=s get /registry/ --prefix --keys-only | head
第一条命令只查询一个端点,其表格不能证明整个集群恰好有一个领导者。应从所有成员采集状态,再比较领导者身份、任期和已应用索引进展;采样也不一定同时发生。最后的查询有意使用本地 serializable 语义,head 只限制展示行数,不限制服务端读取范围,成功不能证明仲裁健康。保留探测错误,不要为了让表格变绿就更改成员配置,恢复动作必须依据完整故障证据。
来源
- etcd 故障模式,领导者、多数丢失与网络分区,已核验 2026-10-15。
- 运行时重新配置,投票成员与 learner 提升,已核验 2026-10-15。
- etcd learner 设计,不投票的复制状态与追赶负载,已核验 2026-10-15。
- etcd API 保证,线性一致与 serializable 操作,已核验 2026-10-15。
- etcd 3.5 配置,默认启用 pre-vote,已核验 2026-10-15。
- Raft 论文,第 5.2 与 5.4 节的投票和当前任期提交规则,已核验 2026-10-15。
- etcdctl 3.5.21 端点检查,健康读取与告警检查,已核验 2026-10-15。