Elasticsearch 系列 · 第六篇

写入一份数据要求"成功",到底是谁跟谁说了"成功"?

一个分片默认不只一份——主分片(primary)之外还有副本分片(replica)。写入一条文档的时候,数据什么时候才算真正安全?是主分片写完就算,还是要等副本也写完?如果这时候正好有个副本掉线了呢?这篇讲清楚 Elasticsearch 的主副本同步机制。

这篇的核心机制用可验证的 Python 模型复现和验证,并用本机真实集群返回的字段作为直接佐证。

同步复制
默认行为:主分片必须等所有在线副本都确认,才会告诉客户端"写成功"
_primary_term
本机真实写入返回的字段,防止"裂脑"写入的关键机制

写入的真实路径

一条写入请求到达之后,不是"哪个分片先写完就算数",是严格按顺序走的。

第一步

先写主分片

写入请求先被路由到主分片(上一篇讲过路由规则),主分片本地写完,分配一个本分片内递增的序号(_seq_no)。

第二步

同步复制给所有副本

主分片把这次写入同步转发给所有当前在线、状态正常的副本,等它们都确认写完。

第三步

全部确认,才返回成功

只有主分片和当前所有在线副本都写完了,客户端才会收到"写入成功"的响应——这是默认行为,不需要额外配置。

真实证据:每次写入都带着这两个字段

在本机真实运行的 Elasticsearch 上写两条数据,看返回结果。

真实实测 POST /orders/_doc { "order_id": 9999 } → {"result":"created", "_seq_no":202, "_primary_term":3, ...} POST /orders/_doc { "order_id": 9998 } → {"result":"created", "_seq_no":193, "_primary_term":3, ...}

每次写入的响应里都带着 _seq_no(这个分片内部的写入序号,单调递增)和 _primary_term(当前主分片的"任期号")。_primary_term 现在是 3,是因为这台机器在准备这个系列的过程中,这个分片对应的主分片经历过几次真实的重启/重新选主——每一次,这个数字都会真实地往上跳一格,不会退回去。

演示:一次正常写入,一次副本掉线,一次主分片故障切换

用一个可验证的最小模型(1 主 + 2 副本)复现完整流程。

主副本同步写入

verified simulation 这个演示的场景是:第一条写入一切正常;第二条写入时 replica-B 恰好掉线——不阻塞写入,但被移出"in-sync 集合";第三条写入延续这个状态;接着模拟主分片彻底失效,replica-A(还在 in-sync 集合里)被提升为新主分片,primary_term 从 1 跳到 2;最后验证一次带着"旧任期号"的写入会被拒绝,带着"新任期号"的会被接受——这是防止网络分区导致的旧主分片继续对外提供服务("裂脑")的核心机制。

副本掉线之后呢

被移出 in-sync 集合的副本,不代表被永久放弃。

副本节点恢复连接后,主分片会尝试把它重新拉回 in-sync 集合——如果掉线时间短、主分片还留着足够的操作历史(translog),可以直接把缺的那部分操作重放过去(增量恢复);如果掉线太久、历史记录已经被清理,就只能做一次完整的数据拷贝(全量恢复)。恢复完成之前,这个副本不参与新写入的同步确认,也不会被路由到用来提供读请求。

这套设计的核心权衡是:宁可让"确认过的写入"数量变少(容忍副本暂时掉线,不因为一个副本卡住就让所有写入超时失败),也不让"确认过的写入"变得不可靠(掉线的副本绝不会被当成"已经确认"的一员)。

参考与说明

  • 本文开头的 _seq_no/_primary_term 数值来自本机真实运行的 Elasticsearch 8.15.0 真实写入结果,未做删改。
  • 本篇的核心场景(1 主 + 2 副本的同步写入、副本掉线、主分片故障切换、旧任期号写入被拒绝)是验证过的 Python 仿真,不是在真实多节点集群上跑出来的——这台机器当前的可用内存不足以稳定运行第二个 Elasticsearch 节点(尝试过,新节点在完成启动前就会被系统直接终止,推测是内存分配失败),所以没有强行拼凑一次不可靠的多节点实验。仿真实现的规则(同步复制、in-sync 集合、primary_term 递增与陈旧写入拒绝)来自 Elasticsearch 官方文档描述的真实机制,用一套独立的暴力回放算法额外交叉验证过最终状态,不是凭空编的流程。
  • 没有涉及:wait_for_active_shards 参数如何调整"写入前至少要有多少份可用副本才允许尝试"这道门槛、translog 在副本恢复中的具体作用(概念上和第四篇 InnoDB 系列的 redo log 类似,但不是本文重点)、真实的主分片选举算法(基于 Raft 类似的共识协议,由集群协调层完成,是系列下一篇的话题)。
☕ 如果这篇文章帮到你,可以请作者喝杯咖啡 · 爱发电