08 ES 生产运维——监控、容量规划与版本升级

摘要

ES 集群的日常运维围绕三个核心问题:集群现在是否健康(监控与告警)、集群还能支撑多久(容量规划)、如何安全地升级版本(版本升级策略)。本文从 SRE 视角出发,梳理 ES 的关键监控指标体系(哪些指标必须告警、哪些只需观察趋势),给出容量规划的量化方法(磁盘、Shard、JVM Heap 三个维度),以及滚动升级(Rolling Upgrade)与全量重建(Full Restart)两种升级路径的选择依据和操作 SOP。


第 1 章 监控指标体系:哪些指标决定集群生死

1.1 ES 的监控数据来源

ES 通过以下接口暴露监控数据,所有主流监控系统(Prometheus + Grafana、Elasticsearch Monitoring、Kibana Stack Monitoring)均支持采集:

接口内容常用场景
GET /_cluster/health集群整体状态(Green/Yellow/Red)及分片统计快速健康检查
GET /_cluster/stats节点数量、文档数、存储大小、内存使用等摘要统计容量趋势监控
GET /_nodes/stats每个节点的详细统计(JVM、GC、线程池、磁盘等)节点级性能诊断
GET /_cat/indices?v所有索引的分片数、文档数、存储大小索引级容量监控
GET /_cat/shards?v所有 Shard 的状态与位置分片分配问题诊断
GET /_cat/nodes?v节点列表、角色、磁盘使用、负载节点级负载均衡诊断

对于 Prometheus 生态,推荐使用 elasticsearch_exporter(官方推荐的第三方 exporter),它将上述接口的数据转化为 Prometheus Metrics 格式。

1.2 必须配置告警的核心指标

集群健康状态(P0 级告警):

elasticsearch_cluster_health_status{color="red"} == 1

集群变红意味着 Primary Shard 不可用,部分数据无法读写,必须立即响应。告警阈值:任何时刻变红,立即触发 PagerDuty/电话告警。

elasticsearch_cluster_health_status{color="yellow"} == 1

集群变黄意味着 Replica Shard 未分配,容错能力下降。告警阈值:持续超过 30 分钟,触发工单告警(非紧急,但需要在当天内处理)。

JVM Heap 使用率(P1 级告警):

elasticsearch_jvm_memory_used_bytes{area="heap"} / elasticsearch_jvm_memory_max_bytes{area="heap"} > 0.85

Heap 使用率持续超过 85%,说明 JVM 频繁 GC,下一步可能触发 OOM 或 Circuit Breaker。告警阈值:超过 85% 持续 5 分钟,触发告警。

GC 暂停时间(P1 级告警):

rate(elasticsearch_jvm_gc_collection_seconds_sum{gc="old"}[5m]) > 0.3

Old GC(Major GC / Full GC)的暂停时间占比超过 30%,说明节点大部分时间在 GC,响应严重受损。

磁盘使用率(P1 级告警):

elasticsearch_filesystem_data_available_bytes / elasticsearch_filesystem_data_size_bytes < 0.15

磁盘使用率超过 85%(剩余 < 15%),ES 内部的 DiskThresholdDecider 开始阻止新 Shard 分配。超过 90% 时开始迁移 Shard,超过 95% 时触发只读保护。提前在 85% 时告警,给运维留有足够时间。

索引速率下降(P2 级告警):

rate(elasticsearch_indices_indexing_index_total[5m]) < 阈值

写入速率突然大幅下降(如从 50000 doc/s 降到 5000 doc/s),可能预示着节点过载、磁盘 IO 瓶颈或写入拒绝(Rejected)。

线程池拒绝(P1 级告警):

rate(elasticsearch_thread_pool_rejected_count{type="write"}[5m]) > 0
rate(elasticsearch_thread_pool_rejected_count{type="search"}[5m]) > 0

ES 的线程池(Write/Search/Bulk/Get)有固定的队列大小,当队列满后,新请求被拒绝(rejected)。出现拒绝意味着 ES 已经处于过载状态,客户端会收到 429 Too Many Requests。任何级别的拒绝都应该告警。

1.3 需要观察趋势的关键指标

以下指标本身没有绝对的告警阈值,但其趋势变化是容量规划和问题预判的重要依据:

Search Latency(搜索延迟):

elasticsearch_indices_search_fetch_time_seconds / elasticsearch_indices_search_fetch_total
elasticsearch_indices_search_query_time_seconds / elasticsearch_indices_search_query_total

建议分 P50/P95/P99 观察。搜索延迟的突增往往预示着 Merge 操作高峰、节点 GC 压力增大或 Shard 迁移导致的 IO 竞争。

Indexing Latency(写入延迟):

写入延迟的增大,通常与 Refresh 频繁(Segment 碎片多)、Translog fsync 压力、或节点 GC 有关。

Segment Count(分片内 Segment 数量):

Segment 数量长期居高不下(> 100/Shard),说明 Merge 速度跟不上 Refresh 速率,需要检查 Merge 线程配置或考虑降低 Refresh 频率。

Pending Tasks(待处理任务数量):

GET /_cluster/pending_tasks

Master 节点的 pending tasks 数量反映了集群管理操作的积压程度。持续积压说明 Master 处理能力不足(可能是 GC 导致的,或 Cluster State 过大导致广播慢)。

1.4 _nodes/stats 深度诊断

_nodes/stats 是 ES 最全面的节点级诊断接口。以下是 SRE 最常用的几个子路径:

JVM 详情:

GET /_nodes/stats/jvm

关注 mem.heap_used_percent(Heap 使用率)、gc.collectors.old.collection_count(Old GC 次数)和 gc.collectors.old.collection_time_in_millis(Old GC 累计耗时)。

线程池详情:

GET /_nodes/stats/thread_pool

关注 write.rejectedsearch.rejectedbulk.queue(当前队列深度)。

索引操作统计:

GET /_nodes/stats/indices

关注 indexing.throttle_time_in_millis(写入被 throttle 的时间,持续增长说明磁盘 IO 成为瓶颈)、refresh.total_time_in_millis(Refresh 总耗时)、merges.total_time_in_millis(Merge 总耗时)。


第 2 章 容量规划:磁盘、Shard 与 Heap 三维度

2.1 磁盘容量规划

ES 的磁盘占用不仅仅是原始数据大小,还包括:

总磁盘占用 = 原始数据 × (1 + 副本数) × 存储放大系数

存储放大系数的组成(典型值 1.5~3.0 倍):

  • 倒排索引:约原始数据的 20%~50%(取决于文本内容的唯一词数量);
  • Doc Values:约原始数据的 30%~100%(取决于字段类型和基数);
  • Merge 期间的临时空间:峰值时约需额外 50% 磁盘空间;
  • _source 存储:约原始数据的 80%~120%(JSON 格式,含字段名开销)。

实际规划示例:

每日日志量:200GB(原始 JSON)
副本数:1
存储放大系数:2.0(日志场景,text 字段多)
保留天数:30 天

所需磁盘总量 = 200GB × (1+1) × 2.0 × 30 = 24,000GB = 24TB

考虑 Merge 峰值和 75% 磁盘使用率上限:
实际配置磁盘 = 24TB / 0.75 = 32TB

建议保留 25%~30% 的磁盘空余,留给 Merge 操作的临时空间和突发写入高峰。

2.2 Shard 数量规划

在第 7 篇中已介绍了单 Shard 目标大小 10~50GB 的黄金区间。这里补充分片数量对查询并发的影响:

Shard 数量与查询并发的关系:

ES 的查询是分 Shard 并行执行的,每个 Shard 的查询在 search 线程池中占一个线程。默认线程池大小 = CPU 核心数 × 3 / 2 + 1

对于一个 8 核节点,search 线程池大小约为 8 × 3/2 + 1 = 13。如果该节点上有 30 个 Shard,单次查询会占用 min(30, 13) = 13 个线程,其余 17 个 Shard 的查询需要等待。

这说明:Shard 数量超过 search 线程池大小后,查询并不会继续加速,反而因为线程排队增加延迟。对于搜索密集型场景,应确保每个数据节点的 Shard 数量不超过 CPU 核心数 × 3~5

2.3 JVM Heap 规划

Heap 的规划需要考虑以下几个固定消耗:

消耗来源估算方式典型值
Cluster StateShard 数量 × 约 1KB/Shard1000 Shard → 约 1MB
In-flight 查询并发查询数 × 每次查询内存高峰 100 并发 × 10MB = 1GB
聚合操作视 Terms 聚合的基数高基数聚合可达 GB 级
Segment 元数据Segment 数量 × 约 50KB/Segment1000 Shard × 10 Segment × 50KB = 500MB

对于典型的中等规模集群(50 个索引、500 个 Shard),Heap 8~16GB 通常足够。超大规模集群(5000 Shard 以上),建议将 Heap 设置为 31GB。


第 3 章 常见故障的 SOP

3.1 集群变 Red 的处理流程

1. 快速定位问题:
   GET /_cluster/health
   GET /_cat/shards?v&h=index,shard,prirep,state,unassigned.reason,node

2. 查看未分配 Shard 的详细原因:
   GET /_cluster/allocation/explain

3. 根据原因分类处理:
   ├── NODE_LEFT(节点离线)
   │   ├── 节点可以恢复 → 等待节点重启,ES 自动恢复
   │   └── 节点永久丢失 → 执行 allocate_stale_primary(接受数据丢失)
   ├── ALLOCATION_FAILED(分配失败超过重试次数)
   │   └── POST /_cluster/reroute?retry_failed=true
   ├── NO_VALID_SHARD_COPY(无有效副本)
   │   └── 数据已丢失,只能从备份恢复
   └── DECIDERS(分配规则阻止)
       └── 检查磁盘空间、过滤规则配置,修复后自动恢复

3.2 Unassigned Shard 长期不恢复

现象:集群长期处于 Yellow 状态,_cat/shards 显示部分 Replica Shard 持续 UNASSIGNED

诊断步骤:

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

响应中的 node_allocation_decisions 字段会列出每个节点拒绝分配的原因。常见原因和解决方案:

"decider": "disk_threshold" → 节点磁盘使用率超过 85%,清理磁盘空间
"decider": "same_shard"    → Primary 和 Replica 不能在同一节点,添加数据节点
"decider": "filter"        → 节点不满足 Allocation Filtering 规则,检查 node.attr 配置

3.3 写入速率骤降排查

现象:Kafka Consumer Lag 突然增大,ES 写入速率从 50000 doc/s 降到 5000 doc/s。

排查路径:

1. 检查线程池拒绝:
   GET /_cat/thread_pool/write,bulk?v&h=name,active,queue,rejected

2. 检查索引 throttle:
   GET /_nodes/stats/indices?filter_path=**.indexing.throttle_time_in_millis

3. 检查 GC 情况:
   GET /_nodes/stats/jvm?filter_path=**.gc.collectors.*.collection_time_in_millis

4. 检查磁盘 IO:
   iotop / iostat -x 1 5(OS 级别)
   GET /_nodes/stats/fs?filter_path=**.io_stats

5. 检查 Merge 积压:
   GET /_cat/nodes?v&h=name,merges.current,merges.current_size,merges.total_time

第 4 章 版本升级:滚动升级的 SOP

4.1 两种升级路径

升级方式适用场景停机时间风险
Rolling Upgrade(滚动升级)小版本升级(如 7.15 → 7.17)、部分大版本升级零停机低(逐节点替换,可随时回滚)
Full Restart(全量重启)大版本跨越(如 6.x → 7.x),滚动升级不支持的场景需停机(或临时降级到双集群)高(需备份验证)

ES 的版本兼容性规则:

  • 小版本(7.x.y → 7.x.z):完全兼容,支持滚动升级;
  • 次要版本(7.x → 7.y):向前兼容,支持滚动升级(但集群中可以共存 7.x 和 7.y,直到所有节点升级完毕);
  • 大版本(6.x → 7.x):需要先升级到 6.x 最新版,再通过滚动升级升到 7.0,ES 支持从一个大版本到下一个大版本的滚动升级(但不能跨越两个大版本)。

4.2 滚动升级 SOP

前置检查:

# 1. 确认集群健康(必须是 Green 才能开始升级)
GET /_cluster/health
 
# 2. 确认没有 Pending Tasks
GET /_cluster/pending_tasks
 
# 3. 备份快照(强烈建议)
PUT /_snapshot/my-backup/pre-upgrade-snapshot
{
  "indices": "*",
  "ignore_unavailable": false
}

逐节点升级流程:

对每一个节点(从数据节点开始,Master 节点最后):

步骤 1:禁用分片自动分配(防止 Shard 在升级期间大量迁移)
PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.enable": "primaries"
  }
}

步骤 2:等待 in-flight 同步完成
POST /_flush/synced

步骤 3:停止目标节点上的 ES 进程
systemctl stop elasticsearch

步骤 4:升级节点上的 ES 软件包
yum upgrade elasticsearch-7.17.0

步骤 5:启动节点
systemctl start elasticsearch

步骤 6:等待节点加入集群并恢复分片
GET /_cat/nodes?v  # 确认节点出现
GET /_cat/recovery?v  # 确认分片恢复完成

步骤 7:重新启用分片自动分配
PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.enable": null
  }
}

步骤 8:等待集群恢复 Green
GET /_cluster/health?wait_for_status=green&timeout=60s

步骤 9:重复以上步骤,处理下一个节点

生产避坑

升级期间禁用 allocation.enable 是关键步骤,否则每停止一个节点,ES 就会将该节点的 Shard 迁移到其他节点,等节点重启后又迁回来,产生大量无意义的 IO 操作,可能占满带宽影响正常业务,且大大延长整体升级时间。

升级 Master 节点的特殊注意:

Master 节点升级时,当前 Active Master 被停止,集群会触发重新选举。在升级 Master 之前,确保:

  1. 集群已经是 Green 状态(所有 Shard 已分配);
  2. 升级 Master 前已经有其他 Master-eligible 节点运行新版本(确保选举出的新 Master 是新版本);
  3. 升级期间观察 _cluster/health,确认 Master 切换正常。

4.3 大版本升级的特殊处理(6.x → 7.x)

ES 6.x → 7.x 有几个 Breaking Changes 需要提前处理:

Mapping type 的废弃:

ES 6.x 中每个 Index 可以有多个 Type(如 _doclogs),ES 7.x 废除了多 Type,每个 Index 只能有一个 Type(统一为 _doc)。升级前需要确认所有索引没有使用多 Type,如果有需要提前合并。

include_type_name 参数:

6.x 到 7.x 迁移时,Mapping API 默认行为变化。过渡期间,所有创建/修改 Mapping 的 API 调用需要加上 ?include_type_name=false 参数(或修改代码适配新 API)。

_default_ Mapping 废弃:

6.x 支持 _default_ 作为所有 Type 的默认 Mapping,7.x 中已废除。升级前需要将 _default_ 中的配置合并到具体 Type 中。

升级路径:

6.7 → 6.8(6.x 最新版)→ 7.0 → 7.17(7.x 最新版)→ 8.x

跨大版本不能跳过中间大版本(不能直接从 6.x 升到 8.x),必须逐个大版本滚动升级。


第 5 章 快照与数据备份

5.1 Snapshot API

ES 的官方备份机制是 Snapshot,将索引数据存储到共享文件系统(NFS、S3、HDFS 等)中。快照是增量的——第一次全量快照后,后续快照只存储变更的 Segment 文件。

注册 Snapshot Repository(以 S3 为例):

PUT /_snapshot/s3-backup
{
  "type": "s3",
  "settings": {
    "bucket": "my-es-backups",
    "region": "cn-shanghai",
    "base_path": "elasticsearch/backups"
  }
}

创建快照:

PUT /_snapshot/s3-backup/snapshot-2026-03-04
{
  "indices": "logs-*",
  "ignore_unavailable": true,
  "include_global_state": false
}

查看快照状态:

GET /_snapshot/s3-backup/snapshot-2026-03-04

从快照恢复:

POST /_snapshot/s3-backup/snapshot-2026-03-04/_restore
{
  "indices": "logs-2026-03-01",
  "rename_pattern": "(.+)",
  "rename_replacement": "restored-$1"
}

5.2 SLM(Snapshot Lifecycle Management)

类似于 ILM 管理索引生命周期,SLM 自动化管理快照的创建和清理:

PUT /_slm/policy/daily-snapshots
{
  "schedule": "0 30 1 * * ?",
  "name": "<daily-snap-{now/d}>",
  "repository": "s3-backup",
  "config": {
    "indices": ["*"],
    "include_global_state": true
  },
  "retention": {
    "expire_after": "30d",
    "min_count": 5,
    "max_count": 50
  }
}

此策略每天凌晨 1:30 自动创建全量快照,保留 30 天内的快照,最少保留 5 个,最多 50 个。SLM 消除了手动创建快照的运维负担,是生产环境的标配。

生产避坑

快照只是数据备份,不是高可用手段。Replica Shard 才是高可用保障。快照解决的是数据误删、集群级灾难恢复的场景,而不是单节点故障恢复。不要混淆这两者。


第 6 章 与其他系统的选型对比

ES 在不同场景下有不同的竞争对手,理解其定位有助于正确选型:

场景ES竞争方案选型建议
全文搜索✅ 最强(BM25 + 向量检索)Solr(也基于 Lucene)ES 生态更活跃,新特性更多;大型遗留系统可能在用 Solr
日志分析(ELK)✅ 主流Clickhouse(更高写入吞吐)日志搜索需求强烈 → ES;纯聚合分析性能要求更高 → ClickHouse
实时 OLAP⚠️ 勉强(聚合误差、写入吞吐有限)ClickhouseDorisES 不擅长复杂 OLAP,亿级数据聚合应选专业 OLAP 引擎
向量检索✅ 支持(HNSW)Milvus、Qdrant、Weaviate纯向量检索场景,专业向量数据库性能更优;需要混合检索(BM25 + 向量)→ ES
时序数据⚠️ 支持(Date Histogram)Prometheus、InfluxDBES 存储时序数据成本高;指标类时序数据应用 Prometheus;日志类时序用 ES
分布式配置中心❌ 不适合ETCD、ZookeeperES 不提供强一致性保证,不适合配置中心

小结

本文以 SRE 视角完成了 ES 运维的三条主线:

  • 监控告警:集群 Red/Yellow 状态、Heap 使用率、GC 暂停时间、磁盘使用率、线程池拒绝是必须告警的 P0/P1 指标;_cluster/allocation/explain_nodes/stats 是诊断问题的核心工具;
  • 容量规划:磁盘 = 原始数据 × (1+副本数) × 放大系数,保留 25%30% 余量;Heap 按 50% 物理内存且不超过 31GB;Shard 数量控制在单 Shard 1050GB;
  • 版本升级:滚动升级是首选,逐节点替换,全程禁用自动 Shard 分配减少迁移 IO;大版本升级需要逐版本推进,不能跨版本。

至此,Elasticsearch 核心原理专栏全部 8 篇文章完成。从倒排索引的数据结构(02),到写入链路的 Segment 生命周期(03),到查询评分与向量检索(04),到聚合的内存模型(05),到集群的选主与分片管理(06),再到性能调优(07)和生产运维(08)——形成了一条从原理到实践的完整知识链路。


思考题

  1. ES 建议 JVM 堆大小不超过物理内存的 50%——另一半留给 OS 的 Page Cache(缓存 Lucene Segment 文件)。如果堆设为 30GB(总内存 64GB),Page Cache 有 34GB 用于缓存索引数据。如果堆设为 50GB,Page Cache 只剩 14GB——查询性能可能反而下降。在什么数据量下 Page Cache 的大小成为查询性能的关键因素?
  2. ES 的 JVM 堆不应超过 32GB——因为超过 32GB 后 JVM 无法使用 Compressed Oops(压缩指针),每个对象引用从 4 字节增加到 8 字节,实际可用堆内存反而减少。这个’32GB 陷阱’在 ES 社区是常识。但如果你的节点有 128GB 内存,应该如何配置——运行一个 30GB 堆的 ES 实例,还是两个 30GB 堆的实例?
  3. 慢查询日志(index.search.slowlog.threshold.query.warn: 10s)帮助定位性能差的查询。常见的慢查询原因包括:通配符查询(wildcard: {field: "*abc*"})、高基数 terms 聚合、深分页。在排查慢查询时,_explain API 和 profile API 分别提供什么信息?如何根据 profile 结果优化查询?