副本为什么不能和主分片在同一台机器?

从「反亲和规则」到「节点宕机全流程恢复」,一步到位讲透

一、铁律:副本和主分片绝不共处一节点

Elasticsearch 有一条硬约束,叫 Shard Allocation Awareness(分片分配感知)

⚠️

错误配置 ES 会拒绝

如果把 P0 和 P0 的副本 R0 放在同一台 Node-1 上,该节点宕机时,数据和备份一起丢失——副本形同虚设。

正确配置(ES 默认行为)

P0 在 Node-1,R0 在 Node-2。Node-1 宕机 → R0 在 Node-2 上毫发无损 → 立即提升为主分片,数据零丢失

核心原因一句话:「不要把鸡蛋放在同一个篮子里」。如果主分片和它的副本在同一台物理机上,这台机器挂了,这个分片的所有数据就彻底没了。副本存在的意义就是「跨节点冗余」。

二、眼见为实 — 点击按钮看效果

一个 3 节点集群,有 3 个主分片 + 1 个副本。观察 P0 和 R0 的位置。

集群 GREEN
P0 主副本在同一节点:否 ✅
[11:53:00] 集群就绪:P0 在 Node-1,R0 在 Node-2 —— 反亲和规则生效

三、节点宕机后,ES 的 9 步完整恢复流程

从一个节点心跳丢失,到集群恢复 GREEN,中间发生了什么?

T+0s
① 心跳丢失 — Node-1 不再响应
Master 节点每隔 1 秒 ping 所有节点。连续 3 次无响应(默认 30s 超时),Master 判定 Node-1 已宕机。
T+30s
② Master 确认宕机 — 集群状态变为 RED
Master 发现 P0 的主分片在宕机节点上,该分片不可用 → 集群状态 RED(部分数据不可读写)。
T+30.1s
③ Master 检查副本 — 发现 R0 存活且数据最新
Master 查询集群元数据,发现 P0 的副本 R0 在 Node-2 上,且数据与主分片完全同步(in-sync replica)。
T+30.2s
④ 副本提升 — R0 立刻被提升为新主分片 P0'
Master 下发指令:将 Node-2 上的 R0 标记为新的主分片。此操作几乎瞬间完成,是内存中的元数据变更。
T+30.3s
⑤ 集群状态降级 — RED → YELLOW
新主分片 P0' 已就绪,数据可读写。但由于副本数量不足(少了一个 R0),集群变为 YELLOW。
T+30.4s
⑥ 协调节点更新路由表
所有节点更新内部路由表:P0 的新位置是 Node-2。后续写入和读取请求自动路由到新主分片。
T+30.5s
⑦ Master 发起副本重建
Master 选择 Node-3(健康节点,与 P0' 不在同一机器),下发指令:从 P0' 拷贝数据,创建新的 R0。
T+31s ~ T+?s
⑧ 数据拷贝 — 从新主分片全量同步到新副本
Node-3 从 Node-2 的 P0' 通过网络拷贝 Segments 文件。耗时取决于分片大小(可能秒级到小时级)。
T+?s
⑨ 副本就绪 — 集群恢复 GREEN
新 R0 在 Node-3 上创建完成,副本数恢复 → 集群 GREEN。整个过程对客户端透明。

四、几个你可能会追问的问题

Q1:如果 Master 节点自己宕机了怎么办?

其他 Master-eligible 节点会发起选举,选出新 Master。这个过程叫 Zen Discovery(ES7+ 用新的协调层)。 新 Master 上来第一件事就是读取集群元数据,然后接管故障恢复流程。所以即使 Master 挂了,恢复也不会中断。

Q2:如果在副本提升完成之前,又有新数据写入怎么办?

写入会失败并返回错误,因为目标主分片不可用。客户端需要重试。一旦副本提升完成(通常毫秒级),路由表更新后写入即可正常进行。
这就是为什么推荐设置 wait_for_active_shards=all —— 确保写入时所有副本都确认。

Q3:那副本设置几个合适?

至少 1 个副本(生产环境底线)。允许容忍 1 个节点宕机不丢数据。
如果集群很大(比如 10+ 节点),可设 2 个副本,容忍 2 个节点同时宕机。
代价:每增加 1 个副本,存储翻倍,写入延迟增加(需要等更多 ACK)。

Q4:acker 的分片分配感知怎么配置?

可以通过 cluster.routing.allocation.awareness.attributes 配置机架感知,确保分片不但在不同节点、更在不同机架/可用区。