上一篇解决了数据怎样分布到 shard 上——路由公式和分片数不可变的约束。这一篇进入 shard 的副本机制和故障恢复。

每个 primary shard 可以有零个或多个 replica shard。Replica 提供两个能力:高可用(primary 故障时 replica 接管)和读扩展(搜索请求可以命中 replica)。理解 primary-replica 的同步模型,才能在一致性和可用性之间做出正确的配置选择。

本文只抓一个问题:primary 和 replica 之间怎样同步数据,primary 故障时怎样恢复。

写入模型

一条文档的写入路径经过 primary shard 再转发到 replica:

1
2
3
4
5
6
7
8
9
10
Client
→ Coordinating Node(路由到 primary shard 所在节点)
→ Primary Shard:
1. 本地执行写入(buffer + translog)
2. 并行转发给所有 in-sync replica
→ Replica Shard(s):
3. 本地执行同样的写入
4. 返回确认给 primary
→ Primary 收到所有 in-sync replica 的确认
→ 返回成功给 client

这个过程涉及 4 个角色的交互,时序上有严格的先后依赖:

sequenceDiagram
    participant C as Client
    participant CN as Coordinating Node
    participant P as Primary Shard
    participant R as Replica Shard(s)

    C->>CN: 写入请求
    CN->>P: 路由到 primary
    P->>P: 写入 buffer + translog
    P->>R: 并行转发写入
    R->>R: 本地执行写入
    R->>P: 返回确认
    P->>CN: 所有 in-sync replica 已确认
    CN->>C: 返回成功

关键点在第 4-6 步:primary 写入成功后并行转发给所有 in-sync replica,等全部确认后才向上返回。任何一个 in-sync replica 超时未响应,master 会将它从同步集合中移除,而不是无限等待。

这是同步复制模型——primary 等待所有 in-sync replica 确认后才返回成功。wait_for_active_shards 参数可以控制需要多少个活跃 shard 确认才算写入成功:

含义
1(默认) 只要 primary 写入成功就返回(replica 异步)
all 所有 shard(primary + 所有 replica)都确认
N(数字) 至少 N 个 shard 确认

注意默认值 1 意味着只等 primary 确认。Replica 的写入是在 primary 返回成功之后并行进行的——如果 primary 在转发到 replica 之前崩溃,这条数据可能丢失。设 wait_for_active_shards: all 可以保证所有 replica 都写入成功,但延迟更高。

In-Sync Allocation IDs

ES 维护一个 in-sync allocation IDs 集合,记录哪些 shard 副本和 primary 保持同步。这个概念类似 Kafka 的 ISR(In-Sync Replicas)。

当一个 replica 落后太多(比如网络分区、节点重启),master 会把它从 in-sync 集合中移除。被移除的 replica 不再接收写入请求,需要做 recovery 才能重新加入。

读取模型

搜索请求可以命中 primary 或任何 in-sync replica。ES 使用 adaptive replica selection(ARS)自动选择延迟最低的副本:

1
2
3
4
搜索请求
→ Coordinating Node
→ 对每个 shard,从 primary 和 replica 中选择响应最快的
→ 返回结果

ARS 综合考虑节点的搜索队列长度、历史响应时间、节点负载来做选择。这意味着搜索负载自动在 primary 和 replica 之间负载均衡。

Replica 数量越多,搜索吞吐越高(更多副本可以服务搜索请求),但写入吞吐越低(每次写入需要同步到更多副本)。

故障恢复

Primary 故障

当 primary shard 所在节点故障时:

1
2
3
4
5
1. Master 检测到节点故障
2. Master 从 in-sync replica 中选择一个提升为新 primary
3. 新 primary 开始接收写入
4. Master 更新 cluster state,广播给所有节点
5. 当故障节点恢复后,其上的旧 primary 变成 replica,做 recovery 追赶

从 primary 故障到恢复完成,整个集群经历一次状态迁移:

stateDiagram-v2
    [*] --> Normal: 集群正常运行
    Normal --> Detected: primary 所在节点故障
    Detected --> Promoting: master 选择 in-sync replica
    Promoting --> NewPrimary: replica 提升为 primary
    NewPrimary --> Serving: 新 primary 接收读写
    Serving --> Recovery: 故障节点恢复上线
    Recovery --> Syncing: 旧 primary 作为 replica 追赶数据
    Syncing --> Normal: recovery 完成,重新加入 in-sync 集合

注意 Promoting 到 NewPrimary 这一步——master 只从 in-sync 集合中选择候选者,落后太多的 replica 不会被提升,避免数据回退。故障节点恢复后角色反转:原来的 primary 变成 replica,通过 peer recovery 追赶新 primary 的数据。

Shard Recovery

当一个 shard 需要恢复(节点重启、replica 重新加入)时,ES 执行 peer recovery——从 primary 复制数据到需要恢复的 replica:

1
2
3
4
5
6
Phase 1: 复制 segment 文件
primary → 发送 Lucene segment 文件给 recovering shard

Phase 2: 重放 translog
primary → 发送 recovery 期间新增的 translog 操作
recovering shard → 回放 translog 操作,追赶到最新状态

Recovery 期间 primary 继续正常服务。Recovery 完成后,恢复的 shard 加入 in-sync 集合,开始接收新的写入和搜索请求。

Unassigned Shards

当 shard 无法被分配到任何节点时,状态变为 UNASSIGNED。常见原因:

  • 节点数不足(replica 不能和 primary 在同一节点)
  • 磁盘水位线超标
  • allocation filtering 排除了所有可用节点

_cluster/allocation/explain 定位原因:

1
2
GET _cluster/allocation/explain
{ "index": "my-index", "shard": 0, "primary": false }

实验:观察副本行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建 1 primary + 1 replica 的 index
PUT /replica-demo
{ "settings": { "number_of_shards": 1, "number_of_replicas": 1 } }

# 单节点集群中,replica 无法分配
GET _cat/shards/replica-demo?v
# shard 0 primary STARTED, shard 0 replica UNASSIGNED

# 查看为什么 replica 无法分配
GET _cluster/allocation/explain
{ "index": "replica-demo", "shard": 0, "primary": false }
# 原因:same node allocation not allowed

# 设置 replica 为 0(单节点集群)
PUT /replica-demo/_settings
{ "index": { "number_of_replicas": 0 } }

GET _cluster/health
# status: green

模式提炼:主从复制 + 同步确认

1
2
write → primary → replicas (sync/async) → ack
read → primary or replica (load balanced)
维度 同步复制 异步复制
持久性 高(所有副本确认) 低(可能丢失最近写入)
写入延迟 高(等待最慢的副本)
适用场景 金融数据、不可丢失 日志、可重放
ES 配置 wait_for_active_shards: all wait_for_active_shards: 1(默认)

工程迁移表

概念 Elasticsearch Kafka MySQL MongoDB
复制单元 Shard (primary/replica) Partition (leader/follower) Database (master/slave) ReplicaSet (primary/secondary)
同步机制 转发写入操作 Log replication Binlog replication Oplog replication
同步集合 In-sync allocation IDs ISR (In-Sync Replicas) Semi-sync replicas Majority write concern
读取路由 Adaptive replica selection Consumer fetches from leader/follower 读写分离中间件 Read preference
故障切换 Master 提升 replica Controller 选新 leader MHA / orchestrator 自动选举

常见误解

误解一:replica 越多数据越安全。 Replica 数量增加提升了读吞吐和容错能力,但每个 replica 都需要存储完整的 shard 数据。3 个 replica 意味着 4 倍存储。且写入延迟随 replica 数增加。通常 1-2 个 replica 就足够。

误解二:replica 和 primary 的数据完全实时一致。 默认 wait_for_active_shards: 1 时,写入返回后 replica 可能还没完成同步。极短时间窗口内搜索 replica 可能看不到最新写入。

误解三:单节点集群设置 replica 有意义。 单节点集群中 replica 无法分配(不能和 primary 在同一节点),集群状态始终是 yellow。单节点应该设 number_of_replicas: 0

练习

  1. 在单节点集群中创建一个 1 replica 的 index,用 _cluster/allocation/explain 查看 replica 无法分配的原因。然后设 replica 为 0,观察集群状态变为 green。

  2. _cat/shards?v 查看 shard 分布,理解 prirep(p=primary, r=replica)、statenode 字段的含义。

  3. 查看 _cat/recovery 了解正在进行或最近完成的 recovery 操作。

系列导航

上一篇 下一篇
分片与路由:数据分布的核心机制 集群协调:Master 选举与集群状态同步

参考资料