跳到正文

ES 架构与核心概念 ​

一句话:ES 把索引切成主分片分散到多个节点,用副本换高可用和读吞吐;主分片数在创建时固定,所以分片规划是一次性决策,后面只能 reindex、split 或 shrink。

适用版本:Elasticsearch 8.x(8.0 起 type 已移除;7.x 的很多配置写法不再适用)。

为什么需要 ​

单机 Lucene 的瓶颈很直接:索引越大,查询和恢复越慢;机器一坏,数据就不可用。ES 的解法是把一个索引切成多个 primary shard,每个 shard 是一个独立的 Lucene 实例,分散到不同节点;每个 primary 再配 replica,负责故障接管和读扩展。

代价是:分片数决定路由,主分片数不能改;分片太多会拖垮 master 和集群状态,分片太大则恢复慢、单点压力高。架构篇的核心不是背概念,而是把分片数量和大小算清楚。

核心机制 ​

节点角色 ​

8.x 用 node.roles 显式声明角色:

角色职责生产建议
master集群状态、索引创建、分片分配3 个专用候选节点,奇数
data_hot / data_content热数据,频繁读写主力数据节点
data_warm / data_cold / data_frozen分层存储按生命周期使用
ingest写入前预处理可与 data 混部,重管道单独部署
coordinating(不配 data/master)请求路由、结果归并大集群可独立

每个节点都能做 coordinating;专用 coordinating 节点只在大查询归并压力明显时才有必要。

索引、分片与副本 ​

  • 文档路由:shard = hash(routing) % number_of_primary_shards,routing 默认是 _id。
  • 主分片数创建后不可改;要改只能 reindex、_split(拆分)或 _shrink(收缩)。
  • 副本数可动态调整;副本和主分片不会放在同一节点。
  • 写只能走主分片,读可以走主分片或副本。
  • 8.x 新建索引默认 number_of_shards=1、number_of_replicas=1;生产按容量显式指定。

分片容量测算 ​

经验值(社区/Elastic 官方指南,不是硬限制):

  • 单个分片 10–50GB 比较稳妥;超过 50GB 恢复和 merge 压力明显。
  • 每 GB 堆内存对应的分片数尽量 < 20;例如 30GB 堆 → 单节点约 600 个分片上限。
  • 分片总数还要考虑副本:副本也占分片名额和恢复带宽。

一个可落地的算法:

主分片数 = ceil(原始数据量 / 目标分片大小)
总副本数 = 主分片数 × (1 + 副本数)
单节点分片数 = 总副本数 / 数据节点数   // 要小于 20 × 堆内存 GB

例子:原始数据 1TB,目标分片 30GB,1 副本,3 个数据节点:

主分片 ≈ 1024 / 30 ≈ 35 → 取 36
总副本 = 36 × 2 = 72
单节点 ≈ 72 / 3 = 24 个分片

如果节点堆是 32GB,单节点上限约 640 个分片,24 个远低于上限;真正的约束是磁盘和恢复时间,不是分片名额。

用命令验证分片状态 ​

bash
# 每个分片在哪个节点、主还是副本、大小
GET _cat/shards?v&h=index,shard,prirep,state,docs,store,node

# 各节点分片数和磁盘
GET _cat/allocation?v

# 索引级别的主/副本数、文档数、存储大小
GET _cat/indices?v&h=index,pri,rep,docs.count,store.size

# 某个分片为什么没分配
GET _cluster/allocation/explain
{
  "index": "orders",
  "shard": 0,
  "primary": true
}

_cluster/allocation/explain 会给出 allocate_explanation 和各个 decider 的结果;磁盘水位、节点离线、副本数大于数据节点数,都会在这里体现。

最小上手 ​

bash
# 单节点验证(8.x)
docker run -d --name es -p 9200:9200 \
  -e discovery.type=single-node \
  -e xpack.security.enabled=false \
  docker.elastic.co/elasticsearch/elasticsearch:8.15.0
bash
# 建一个 3 主 1 副的索引
curl -X PUT "localhost:9200/orders" -H 'Content-Type: application/json' -d '
{
  "settings": { "number_of_shards": 3, "number_of_replicas": 1 },
  "mappings": {
    "properties": {
      "order_id": { "type": "keyword" },
      "status":   { "type": "keyword" },
      "amount":   { "type": "double" },
      "created_at": { "type": "date" }
    }
  }
}'

curl "localhost:9200/_cat/shards/orders?v"
curl "localhost:9200/_cluster/health?pretty"

单节点上副本会处于 UNASSIGNED,这是正常的;至少两个数据节点才能分配副本。

生产动作 ​

  • 分片数一次定好:按未来 12 个月数据量估,不按当前量估;主分片不可改,扩容靠加节点而不是加主分片。
  • 副本:至少 1 个;副本数大于数据节点数会产生未分配分片。
  • 专用 master:3 个奇数候选节点,跨机架/可用区;不要让 master 承担大量 data 角色。
  • 磁盘水位:默认低水位 85%、高水位 90%、flood stage 95%;到 95% 索引会变成只读。
  • 分配排障:_cluster/allocation/explain 是第一步,不要直接 reroute 强分配。
  • 监控:_cat/shards 的 UNASSIGNED、_cluster/health 的 relocating_shards、节点磁盘和堆。

边界 ​

  • 主分片数不能改:_split 和 _shrink 能调整,但都要求索引只读并复制数据,不是在线操作。
  • 分片不是越多越好:每个分片都有 Lucene 开销和集群状态开销;过度分片会让 master 和恢复变慢。
  • 分片不是越大越好:大分片恢复慢,单分片故障影响面大;50GB 是经验上限,不是硬指标。
  • 副本不是备份:副本在同一集群内,误删索引会一起没;备份要用 snapshot。
  • 单节点不能验证副本行为:副本会一直未分配,必须至少两个数据节点。

讲清楚 ​

ES 把索引切成主分片,主分片数在创建时固定,文档按 hash(_id) % 主分片数 路由;每个主分片可以有副本,负责故障接管和读扩展。主分片不能改,所以分片规划是一次性决策:先按「原始数据量 / 10–50GB」算主分片,再乘副本数,最后检查单节点分片数是否超过「20 × 堆内存 GB」。

分片太多会拖慢 master 和恢复,分片太大则恢复慢、故障影响面大。排障先看 _cat/shards 和 _cluster/allocation/explain,确认是磁盘水位、节点离线还是副本数大于节点数;不要一上来就 reroute。生产上 master 和 data 尽量分离,磁盘到 95% 会只读,副本不能当备份。

把一门技术讲透到既懂也会用