面试知识库
进阶

分片与副本机制#

一句话答案#

ES 通过 Primary Shard 水平拆分数据(创建后不可修改数量),通过 Replica Shard 提供高可用和读扩展;路由公式 shard = hash(_routing) % number_of_shards,单个 Shard 建议 10-50GB。

核心要点

分片模型#

Index: products (5 Primary + 1 Replica = 10 Shards)

Node1:  [P0] [R2] [R4]
Node2:  [P1] [R3] [P4]
Node3:  [P2] [R0] [R1]  ← Primary 和 Replica 分布在不同节点

规则:Primary 和 Replica 不在同一节点(保证节点故障不丢数据)
plaintext

路由机制#

shard_num = hash(_routing) % number_of_primary_shards

默认 _routing = _id(文档ID)
自定义路由:按 user_id 路由,同一用户数据在同一分片 → 避免跨分片查询

PUT /orders/_doc/123?routing=user_456
plaintext

为什么分片数创建后不能修改?#

因为路由公式依赖 number_of_primary_shards:
  hash("doc_123") % 5 = 2  (5 分片时在 shard 2)
  hash("doc_123") % 7 = 4  (改成 7 分片后找不到了)

解决方案:需要增加分片时 → reindex 到新索引
         或提前创建足够多分片(但不能太多)
plaintext

分片数规划#

因素建议
单 Shard 大小10-50GB(过大影响恢复速度)
每节点 Shard 数≤ 20 个/GB heap(避免内存压力)
集群总 Shard 数几千个还行,上万需注意 Master 负载
初始规划预估数据量 / 30GB = 分片数
例:预计 300GB 数据
  分片数 = 300GB / 30GB = 10 个 Primary Shard
  副本数 = 1(生产必备)
  总 Shard = 10 × 2 = 20
  3 节点集群 → 每节点约 7 个 Shard
plaintext

副本的作用#

  1. 高可用:Primary 所在节点挂了,Replica 自动提升为 Primary
  2. 读扩展:查询请求可以分摊到 Replica(读多写少时有效)
  3. 数据安全:多副本防止数据丢失

副本同步流程#

写入流程:
1. 请求路由到 Primary Shard
2. Primary 写入成功
3. Primary 并行转发到所有 Replica
4. 所有 Replica 确认 → 返回客户端成功

配置:
  wait_for_active_shards: "all" (强一致) / 1 (仅 Primary) / "majority"
plaintext

副本一致性模型(深挖)#

ES 用的是 primary-backup 复制,不是 quorum 写。 写入只认 Primary:Primary 先在本地写成功,再把操作并行转发给当前 in-sync 集合里的所有 Replica,等它们全部确认后才返回客户端成功。这里没有”写多数派就算成功”的概念——in-sync 的副本必须全部确认。

in-sync 副本机制(类似 Kafka 的 ISR):

in-sync 集合 = 当前与 Primary 数据同步、可参与确认的副本集合

某个 Replica 复制失败 / 响应超时:
  1. Primary 不会一直死等,也不会因为它失败就让整个写入失败
  2. Primary 上报 Master:把这个慢/挂的 Replica 标记为 stale
  3. Master 将该 Replica 踢出 in-sync 集合(从 allocation 中移除)
  4. 后续写入只需剩下的 in-sync 副本确认 → 保证可用性
  5. 该副本后续重新追上数据后,再被加回 in-sync 集合
plaintext

这样既保证”返回成功的写一定落在所有 in-sync 副本上”(一致性),又不会被一个掉队副本拖垮(可用性)。

local checkpoint / global checkpoint(同步进度追踪):

概念含义
seq_no每条写操作在分片上的单调递增序列号
local checkpoint单个分片(Primary 或某 Replica)上”该序号及之前的操作都已落盘”的位点
global checkpoint所有 in-sync 副本的 local checkpoint 取最小值——代表”全集群都已安全持有”的位点

global checkpoint 的作用:① 主从切换 / 副本恢复时,落后的副本只需从 global checkpoint 之后做增量补齐(基于 seq_no 的 ops-based recovery),不必整段拷贝;② 标记数据安全水位。

与 Raft 的本质区别:

  • ES 数据面(文档读写)= 主备复制:Primary 单点决定写顺序,Replica 被动跟随,靠 in-sync + checkpoint 保证不丢,不跑共识算法
  • ES 集群面(元数据/选主)= 类 Raft 的共识协议(7.x 起内置基于 Raft 思想的协调层,保证集群状态唯一)。
  • 所以”ES 写入要多数派 / ES 用 Raft 写数据”是典型错误:选 Master 才用共识,写文档走的是 primary-backup。

分片分配策略#

ES 自动均衡分片:
- 新建索引时均匀分配到各节点
- 节点加入/离开时自动 rebalance
- 可通过 allocation awareness 指定机架/区域感知

手动控制:
PUT /_cluster/settings
{
  "transient": {
    "cluster.routing.allocation.exclude._name": "node_hot_1"
  }
}
plaintext
面试回答(2分钟版)

ES 分片分 Primary 和 Replica。Primary Shard 决定数据怎么拆分,路由公式是 hash(routing) % primary_shard_num,所以分片数创建后不能改(改了路由就乱了)。单个分片建议 10-50GB,太大恢复慢,太小浪费资源。Replica 提供高可用——Primary 所在节点挂了 Replica 自动提升,同时还能分摊读请求。写入时先写 Primary,再并行同步到所有 Replica,全部确认才返回成功。分片规划的关键是预估数据增长量:300GB 数据大约需要 10 个分片。Primary 和 Replica 必须在不同节点,ES 自动做分片均衡和故障恢复。需要扩容分片数时只能 reindex 到新索引,所以初期规划要留余量。

追问与易错

追问方向:

  • “怎么扩容?”→ 增加节点 ES 自动 rebalance;增加分片数需要 reindex
  • “自定义路由有什么好处?”→ 同一维度数据在同一分片,避免跨分片查询(如按租户路由)
  • “副本越多越好吗?”→ 不是,副本多写入变慢(每次写入要同步),且占磁盘
  • “节点挂了多久恢复?”→ 默认 1 分钟探测超时后开始分配 Replica 为 Primary + 复制新副本
  • “ES 写入是 quorum/多数派吗?”→ 不是,是 primary-backup + in-sync 副本:要 in-sync 集合里的副本全部确认;副本失败则被 Master 标记 stale 踢出 in-sync,不阻塞写入
  • “ES 用 Raft 吗?”→ 只有选主/集群元数据用类 Raft 共识;文档读写是主备复制,靠 local/global checkpoint 追踪进度,不跑共识

易错点:

  • ❌ “分片数可以动态调整”——Primary 数创建后不可变,只能 reindex
  • ❌ “副本数也不能改”——副本数可以动态调整(PUT index/_settings)
  • ❌ “分片越多查询越快”——每个分片有查询开销,过多分片反而慢(协调节点合并压力)