西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → etcd挂1个节点就写不进:quorum门槛算法我算错了

etcd挂1个节点就写不进:quorum门槛算法我算错了

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 7:21:20字体大小:A-A+

作者:xiix点击:2次评论:0次标签: etcd quorum Raft协议 分布式存储 集群一致性

  • 类型:数据库类大小:36.5M语言:中文 评分:10.0
  • 标签:
立即下载

上个月一个做配置中心的客户半夜给我打电话,说他们的 etcd 集群「挂了」,但监控面板上明明三个节点有两个是绿的,节点状态全是 healthy,只有客户端写入开始报 etcdserver: request timed out。我第一反应是磁盘满了或者网络抖了,让值班的同学去看,结果磁盘用了 30%,三个节点互相 ping 也通。

后来我们一起把配置翻出来看,才发现是他们的部署脚本在写启动参数时,把三个节点的 --initial-cluster 写成了两台半其中一台的 IP 还带着上次迁移前的旧地址。更要命的是他们把 --quota-backend-bytes 和 --max-request-bytes 搞混了,客户端一次批量写太多请求,直接把某个节点的 backend 写满。这两件事叠在一起,就出现了「节点都活着,但写不进去」这个看起来自相矛盾的现象。

如果你也在用 etcd 做配置中心或者 K8s 的元数据存储,或者正准备自己搭一套三节点集群,这两家的服务器可以先拿来做基准测试:skywolf(高可用云基础设施,多节点部署,节点间网络延迟可控)、搬瓦工(高性能 VPS,RAID-10 硬件配置适合做集群底层存储)。etcd 是个对延迟和磁盘 fsync 都极其敏感的东西,机器本身的稳定性直接决定集群的上限,下面我把这次排查的完整过程写下来。

etcd三个节点组成Raft多数派集群的架构示意图

一、先纠正一个最常见的误解:etcd 不是「三台机器」

很多人以为 etcd 集群就是起三台机器让它们同步一下,事情没那么简单。etcd 的核心是 Raft 共识算法,而 Raft 的一致性保证完全建立在「多数派」这个概念上。

所谓多数派(quorum),就是集群里超过一半的节点。三个节点需要 2 个达成多数,五个节点需要 3 个,偶数节点是纯浪费4 个节点和 3 个节点的容错能力完全一样(都是挂 1 个),但 4 个节点写操作的延迟更高,因为每次提交要等更多的副本确认。所以官方文档一直建议集群规模用奇数。

关键在于 etcd 的写入必须得到多数派确认才算成功。这个机制保证了不会出现「A 节点说写成功了,B 节点说没写过」这种数据分叉。这就是为什么三个节点挂掉一个,剩下两个还是 2/3 多数派,能读写;但如果你只有两个节点,挂掉一个就剩 1/2,不是多数派,集群立刻变成只读这就是「节点还活着但写不进去」的第一种原因。

但我那个客户的场景更隐蔽。他们的集群不是三节点挂了一个,而是 从 raft 的视角看,三个节点里有一个根本不承认自己是集群成员。etcd 启动时如果 --initial-cluster 里列的成员列表和实际启动的节点对不上,或者某个节点用了旧的 member list,那个节点就成了 unstarted 状态,跟另外两个根本不在同一个 raft group 里。这时候你从 etcdctl member list 看,三个节点都列出来了、都是 healthy,但实际参与投票的只有两个而且这两个恰好不在同一台物理机上,跨机房延迟一大,leader 选举就开始反复超时。

quorum多数派写入门槛示意图

二、quorum 门槛到底怎么算,别记混

我把公式写出来,因为这是最容易搞错的地方。设集群总节点数为 N,则:

写入成功需要的确认数 = floor(N/2) + 1
集群能容忍同时故障的节点数 = floor(N/2)

对着这个表看就一目了然了:

3 节点:需要 2 个确认,能容忍挂 1 个
5 节点:需要 3 个确认,能容忍挂 2 个
7 节点:需要 4 个确认,能容忍挂 3 个

我客户那个 bug 恰恰出在这里他们内部文档里写的是「三节点部署,容忍两台故障」。这当然是错的,三节点只能容忍一台。他们按两台的余量去做容量规划,某天机房一个机架整体断电,一次掉了两台,集群直接不可写。这不是 etcd 的 bug,是把公式算错了。

还有一个更隐蔽的坑:quorum 是针对每次写入独立计算的。也就是说,如果某一时刻只有 2 个节点活着,quorum 门槛就从 2 变成了 2(因为它按存活节点数算),看起来还能写。但当第三个节点重新加入、或者原 leader 恢复时,raft 要做日志对齐,如果这个节点的数据落后太多(--max-wals 或者 raft log 截断导致的),它会被踢出集群重新以 snapshot 方式加入。这个过程会触发一次 leader 选举,如果此时客户端连接没断,表现为几秒到几十秒的写延迟毛刺。

Raft领导者选举与日志复制过程示意图

三、租约(Lease)是另一个最容易踩的坑

etcd 除了 KV 存储,还有一个基于租约的机制:你可以给 key 挂一个 TTL,客户端再 KeepAlive 续约,续约断了 key 自动删除。Kubernetes 的 Node 对象心跳就是靠这个实现的节点定期续约,续约不到 etcd 就把该 Node 对象删掉,Node 生命周期控制器随即把上面的 Pod 驱逐掉。

租约的坑在于它的 TTL 是有讲究的。etcd 官方推荐 TTL 设置为心跳间隔的 3 倍到 5 倍,原因是要容忍偶发的网络抖动和 GC 停顿导致的续约延迟。我见过有人图省事把 TTL 设成 5 秒、心跳 2 秒,结果某次宿主机做了一次 full GC 或者磁盘 IO 打满,续约晚了 6 秒,一批 Node 对象就被误判失联,Pod 开始无端重建。

另一个坑是 KeepAlive 的连接数上限。etcd 服务端默认允许的 keepalive 连接数是 --max-client-connections(默认 1000),但很多客户端库默认只开一条连接复用,高并发场景下所有客户端挤在少数几条 TCP 连接上,续约请求互相排队,延迟直接反映到 TTL 上。稳妥的做法是客户端侧把连接池开到合理数量,并开启 auto-reconnect。

还有个容易忽略的:租约过期删除是异步的。TTL 到期后 key 会被放进一个批处理队列,由后台 goroutine 批量删。所以从「TTL 到期」到「key 真的消失」之间可能有几百毫秒到几秒的延迟。如果你的业务逻辑依赖「key 一定已经没了」,得自己处理这个时间窗。

四、真出现「节点活着但写不进」怎么查

我把这套排查顺序固定下来了,丢过人的时候十分钟能定位:

第一步,看 raft 视角的成员列表,不是健康检查。etcdctl endpoint status --cluster -w table 和 etcdctl member list -w table 两个都要看。重点看三列:ID、RAFT INDEX、RAFT TERM。如果某个节点的 RAFT INDEX 明显落后,说明它在追赶;如果某个节点压根没出现在 member list 里,那它是个孤儿节点。

第二步,看是不是 leader 反复切换。etcdctl endpoint status 里如果各节点的 RAFT TERM 不一致、或者频繁变化,说明选举在反复发生。leader 频繁切换的典型原因有三个:网络分区(跨机房延迟超过 election timeout)、磁盘 fsync 太慢(heartbeat 写不出去)、或者 --heartbeat-interval / --election-timeout 设得不合理。

第三步,看磁盘 fsync 延迟。这是最容易被忽视但影响最大的一项。etcd 每次写都要 fsync,NVMe 的 fsync 在 0.5ms 以内,SATA SSD 在 5-20ms,机械盘或者云盘被别的 IO 抢占时能到几百 ms。如果你的机器上还有别的重 IO 任务,etcd 的心跳可能写不出去,选举就炸了。判断方法:iostat -x 1 看 fawait,或者直接看 etcd 日志里的 took too long 和 leader failed to send out heartbeat on time。

第四步,看后端配额。etcdctl endpoint status 里的 DB SIZE 跟 --quota-backend-bytes 对比。我客户那次就是 DB SIZE 到了配额上限,etcd 进入 NOSPACE alarm 状态,所有写请求直接被拒。而且这个状态是 粘性的必须先 etcdctl alarm disarm 再压缩历史版本,光删几个 key 没用。这是我那次排查最大的弯路。

第五步,看日志里的具体错误类型。不同错误对应完全不同的方向:request timed out 往网络和选举查;mvcc: database space exceeded 是配额;leader changed 是选举;raft: no leader 是彻底失去多数派。

机房内三台服务器组成高可用集群的场景

五、部署参数里那几个真正要改的地方

说完排查,说说怎么一开始就少踩坑。我现在给新集群做初始化,会固定这么配:

三个节点必须放在不同故障域。同机架甚至同可用区都不行,必须跨机架跨可用区,最好跨机房。etcd 对 RTT 敏感,同机房 0.2ms 和跨城 30ms 对选举稳定性影响巨大。跨机房方案要用 etcd 的 --peer-network-proxy 或者专线。

把 --initial-cluster 写死并校验。这正是我客户踩的坑。最稳妥的做法是所有节点用同一份配置模板生成,把 member list 里的地址从模板变量渲染出来,部署后立刻用 member list 核对一遍。另外记得加 --initial-cluster-state=new(首次)和 existing(扩容时),搞混了会直接起不来。

把 --quota-backend-bytes 设成你实际用量的 4 倍以上。默认 2GB 对小集群够用,但一旦开了 --auto-compaction 之前的窗口期,db 可能涨得比你想象的快。配额是硬闸门,撞上了整个集群变只读,比慢一点严重得多。

务必开自动压缩。--auto-compaction-mode=periodic --auto-compaction-retention=1,保留 1 小时内的历史版本即可。这不是优化,是必须的etcd 的 mvcc 不会自动清理旧版本,不开压缩的话 db 会单调增长直到撞配额。这也是很多「etcd 用着用着突然满了」的真凶。

把 --max-request-bytes 调到够用,同时给客户端做批量限制。默认值 1.5MB,很多客户端批量写一个大配置就超了,报 etcdserver: request is too large。

心跳和选举超时按延迟调。默认 heartbeat 100ms、election timeout 1000ms。网络延迟高(尤其跨机房)的时候 election timeout 要放大到延迟的 10 倍以上,否则抖动一下就误判 leader 失联。

运维工程师在终端查看etcd集群健康状态

六、写在最后

etcd 的坑说到底就两类:一类是算错了 quorum,把偶数节点和三节点的容错能力想当然;另一类是部署参数没对齐,节点列表、地址、状态三项不一致,导致你以为有三个节点其实只有一个在参与投票。

真正让人后怕的是第二类它是静默的。节点全绿、状态 healthy、日志里连 warn 都没有,但集群实际上已经残了一条腿。所以我现在加 etcd 集群有一条硬规矩:部署完必须用 member list 核对成员,用 endpoint status 核对 RAFT INDEX 和 TERM,两者都对不上就是没部署成功。这不是可选项,是上线前的必经步骤。

另外强烈建议把 DB SIZE / quota 做成监控告警,quorum 相关指标(leader 切换次数、commit 延迟、apply 延迟)也一并盯上。这几个指标一超阈值就告警,比半夜被电话叫醒靠谱得多。

如果你也在用 etcd,欢迎在评论区说说你们集群的节点数和遇到过的问题,我看到会补充到后面。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲