Elasticsearch 集群规划与运维——为什么你的集群总是"黄" / Elasticsearch Cluster Planning and Operations
📅 创建时间:2026-07-28 🏷️ 标签:#Elasticsearch #集群 #分片策略 #集群运维 #性能调优 #高可用 📚 前置知识:[[./00-overview]](搜索引擎概览) [[./02-elasticsearch-queries]](ES 查询 DSL)
📋 本章目标
- 理解 ES 集群的四种节点角色及其职责分工
- 掌握主分片与副本分片的策略:数量计算、过分配问题、动态扩展
- 理解写入流程的全链路:refresh → flush → translog → 近实时搜索
- 能够诊断集群健康状态:Green / Yellow / Red 的含义与排查
- 掌握集群运维操作:滚动重启、快照备份、跨集群复制 CCR
- 理解性能调优的核心参数:heap 设置、bulk 写入优化、查询缓存
第1部分:集群架构
1.1 ES 集群的物理拓扑
┌─────────────────────────────────────────────────────────────┐
│ ES 集群物理拓扑 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ES Cluster "search-prod" │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │
│ │ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ │
│ │ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌───────┐ │ │ │
│ │ │ │Shard P0│ │ │ │Shard R0│ │ │ │Shard P1│ │ │ │
│ │ │ │(主分片)│ │ │ │(副本0) │ │ │ │(主分片)│ │ │ │
│ │ │ └────────┘ │ │ └────────┘ │ │ └───────┘ │ │ │
│ │ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌───────┐ │ │ │
│ │ │ │Shard R1│ │ │ │Shard P2│ │ │ │Shard R2│ │ │ │
│ │ │ │(副本1) │ │ │ │(主分片)│ │ │ │(副本2) │ │ │ │
│ │ │ └────────┘ │ │ └────────┘ │ │ └───────┘ │ │ │
│ │ └──────────────┘ └──────────────┘ └───────────┘ │ │
│ │ │ │
│ │ 主分片 P0、P1、P2 → 各存 1/3 数据(唯一数据源) │ │
│ │ 副本分片 R0、R1、R2 → 完整数据备份 │ │
│ │ 任意一个节点挂掉,数据不丢,服务不中断 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 关键概念: │
│ • 一个 Index 的数据被水平切分成多个 Shard(主分片) │
│ • 每个主分片可以有 0-N 个 Replica(副本分片) │
│ • 主分片和副本分片永远不会在同一个节点上 │
│ • 写操作先到主分片,再由主分片同步到副本分片 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 节点角色详解
┌─────────────────────────────────────────────────────────────┐
│ 四种节点角色 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Master Node(主节点) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 职责:集群管理,不处理查询和写入 │ │ │
│ │ │ • 创建/删除 Index │ │ │
│ │ │ • 分配 Shard 到节点 │ │ │
│ │ │ • 节点加入/离开集群 │ │ │
│ │ │ • 维护集群状态(Cluster State) │ │ │
│ │ │ │ │ │
│ │ │ 配置:node.roles: [master] │ │ │
│ │ │ 数量:3 个(专用节点,小集群可兼 data) │ │ │
│ │ │ 资源:轻量,CPU/内存需求低 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Data Node(数据节点) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 职责:存储数据,执行查询和聚合 │ │ │
│ │ │ • 存储 Shard 数据 │ │ │
│ │ │ • 执行搜索、聚合、写入操作 │ │ │
│ │ │ • 消耗最多的 CPU、内存、磁盘 IO │ │ │
│ │ │ │ │ │
│ │ │ 配置:node.roles: [data] │ │ │
│ │ │ 数量:与数据量成正比,通常 3-几十个 │ │ │
│ │ │ 资源:大内存(31GB heap 以下)、SSD 必须 │ │ │
│ │ │ │ │ │
│ │ │ 子角色(ES 7.x+): │ │ │
│ │ │ • data_content: 内容数据(默认) │ │ │
│ │ │ • data_hot: 热数据(频繁读写) │ │ │
│ │ │ • data_warm: 温数据(偶尔读) │ │ │
│ │ │ • data_cold: 冷数据(很少读) │ │ │
│ │ │ • data_frozen: 冻结数据(几乎不读) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Ingest Node(摄取节点) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 职责:文档写入前的预处理 │ │ │
│ │ │ • 执行 Ingest Pipeline(字段转换、丰富) │ │ │
│ │ │ • 不存储数据 │ │ │
│ │ │ • 降低 Data Node 的 CPU 负担 │ │ │
│ │ │ │ │ │
│ │ │ 配置:node.roles: [ingest] │ │ │
│ │ │ 使用场景:大量写入需要预处理时 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Coordinating Node(协调节点) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 职责:请求路由和结果合并 │ │ │
│ │ │ • 接收客户端请求 │ │ │
│ │ │ • 分发查询到各 Data Node │ │ │
│ │ │ • 合并和排序结果 │ │ │
│ │ │ • 不存储数据,不处理主节点事务 │ │ │
│ │ │ │ │ │
│ │ │ 配置:所有角色都设为 false │ │ │
│ │ │ 使用场景:大集群需要独立的查询负载均衡层 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.3 集群拓扑演进:从小到大的部署模式
┌─────────────────────────────────────────────────────────────┐
│ 集群拓扑演进路线 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Level 1: 单节点(开发/测试) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Node: master + data + ingest (全角色) │ │
│ │ 分片: 1 primary, 0 replica │ │
│ │ 适用: 本地开发,数据可丢 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ 上生产 │
│ Level 2: 三节点(小型生产) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3 nodes: master + data + ingest (全角色) │ │
│ │ 分片: 1 primary, 1 replica (共2份拷贝) │ │
│ │ 最小 master 节点数 = 3 (防脑裂) │ │
│ │ 适用: 小型搜索服务,数据 < 100GB │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ 业务增长 │
│ Level 3: 数据与主节点分离(中型生产) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3 master nodes (专用) + 5 data nodes │ │
│ │ 分片: 3 primary, 1 replica │ │
│ │ 适用: 数据 100GB-1TB,查询 QPS > 100 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ 大规模 │
│ Level 4: 全角色分离 + 冷热分层(大型生产) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3 master + 2 coordinating + 2 ingest │ │
│ │ + 6 hot data nodes (SSD) + 4 warm data nodes (HDD)│ │
│ │ 分片: 5 primary, 1 replica │ │
│ │ 适用: 数据 > 1TB,高并发读写,日志分析等 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:分片策略
2.1 分片数量计算
┌─────────────────────────────────────────────────────────────┐
│ 分片数量决策矩阵 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 主分片数量 = f(数据量, 写入QPS, 查询QPS, 节点数) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 维度 计算公式 / 经验值 │ │
│ │ ───────────────────────────────────────────── │ │
│ │ 数据量 (GB) 每个分片 10-50GB(推荐 30GB) │ │
│ │ 分片数 ≈ 总数据量 / 30 │ │
│ │ │ │
│ │ 写入吞吐 每个分片处理约 1-2 万 docs/s │ │
│ │ │ │
│ │ 查询性能 每个 CPU 核心 1-2 个分片 │ │
│ │ │ │
│ │ 堆内存 (Heap) 每 1GB heap → 20 个分片 │ │
│ │ (每个分片约占 50MB heap) │ │
│ │ │ │
│ │ ES 限制 每个节点最多 1000 个分片 │ │
│ │ 每个索引最多 1024 个分片 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 案例计算: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 场景:商品搜索,当前数据 10GB,预计 1 年增长到 │ │
│ │ 200GB,3 个数据节点(每节点 4 核 16GB RAM) │ │
│ │ │ │
│ │ 数据量约束: 200GB / 30GB ≈ 7 个分片 │ │
│ │ Heap 约束: 16GB heap × 20 = 320 个分片上限 │ │
│ │ CPU 约束: 3×4=12 核,12×2=24 个分片上限 │ │
│ │ │ │
│ │ 推荐:5-10 个主分片,1 个副本分片 │ │
│ │ 预留了未来扩展空间,同时保持查询性能 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.2 过分配问题
┌─────────────────────────────────────────────────────────────┐
│ 分片过多 vs 过少的代价 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 分片过多的代价(更常见、更致命): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 每个分片消耗内存(约 50MB heap) │ │
│ │ 1000 个分片 × 50MB = 50GB heap(很容易 OOM) │ │
│ │ │ │
│ │ • 每次查询需要广播到所有分片 │ │
│ │ 100 个分片 = 100 次内部网络请求 │ │
│ │ 协调节点合并 100 个结果 = CPU 瓶颈 │ │
│ │ │ │
│ │ • 集群状态 (Cluster State) 增大 │ │
│ │ 分片越多,Master 维护的元数据越多,更新越慢 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 分片过少的代价: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 无法利用多节点并行能力 │ │
│ │ • 单个分片过大(>100GB)导致 rebalance 极慢 │ │
│ │ • 后续无法增加分片数(主分片数量不可变) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 黄金法则: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 分片大小:10-50GB(目标 30GB) │ │
│ │ 2. 分片数量:数据节点 CPU 核心数 × 1.5 ~ 3 │ │
│ │ 3. 宁可稍多,不要过少(分片少无法扩展) │ │
│ │ 4. 预留 20% 增长空间 │ │
│ │ 5. 使用 Rollover + Index Lifecycle Management │ │
│ │ 按时间切分索引,而不是无限扩大单个索引 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.3 Shard 分配策略
┌─────────────────────────────────────────────────────────────┐
│ Shard 分配感知 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ES 支持多种分配感知 (Allocation Awareness),保证副本 │
│ 不会落在同一故障域: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Rack Awareness(机架感知) │ │
│ │ node.attr.rack: rack1 / rack2 / rack3 │ │
│ │ → 主分片和副本分片不会放在同一机架 │ │
│ │ │ │
│ │ Zone Awareness(可用区感知,云环境) │ │
│ │ node.attr.zone: zone-a / zone-b / zone-c │ │
│ │ → 副本分片分配在不同可用区 │ │
│ │ │ │
│ │ Shard Filtering(分片过滤) │ │
│ │ "index.routing.allocation.include.zone": "zone-a" │ │
│ │ → 强制某个索引的分片只在特定节点上 │ │
│ │ → 热温冷数据分层的关键配置 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 强制感知配置: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ cluster.routing.allocation.awareness.attributes: │ │
│ │ rack, zone │ │
│ │ cluster.routing.allocation.awareness.force.zone: │ │
│ │ true ← 如果无法满足 zone 感知,分片不分配 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:写入流程
3.1 从写入请求到可搜索的完整链路
┌─────────────────────────────────────────────────────────────┐
│ 写入流程全链路 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端 │
│ │ POST /products/_doc/1 │
│ ▼ │
│ Coordinating Node │
│ │ 1. 根据 _routing (默认 _id) 计算目标 shard │
│ │ shard_num = hash(_routing) % num_primary_shards │
│ ▼ │
│ Primary Shard (Node A) │
│ │ │
│ │ 2. 写入 Translog(先写 WAL,保证不丢) │
│ │ ┌───────────────────────────────────────────┐ │
│ │ │ Translog (Write-Ahead Log) │ │
│ │ │ • 顺序写入,磁盘 fsync │ │
│ │ │ • 节点崩溃后用于恢复未 flush 的数据 │ │
│ │ │ • 类似于 MySQL 的 binlog │ │
│ │ └───────────────────────────────────────────┘ │
│ │ │
│ │ 3. 写入 In-Memory Buffer(内存缓冲区) │
│ │ ┌───────────────────────────────────────────┐ │
│ │ │ In-Memory Buffer │ │
│ │ │ • 最新文档的倒排索引先存在内存 │ │
│ │ │ • 此时文档还不可搜索! │ │
│ │ └───────────────────────────────────────────┘ │
│ │ │
│ │ 4. 转发到 Replica Shards │
│ │ → 每个 replica 重复步骤 2-3 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 定时 Refresh (默认 1s) │ │
│ │ │ │
│ │ 5. 将 In-Memory Buffer 中的文档写入 Segment │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ Segment (磁盘上独立的倒排索引文件) │ │ │
│ │ │ • 此时文档可搜索! → 近实时 (NRT) │ │ │
│ │ │ • Segment 不可变 (immutable) │ │ │
│ │ │ • 每个 refresh 生成一个新 Segment │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 定时 Flush (默认 30min 或 512MB)│ │
│ │ │ │
│ │ 6. 将所有 Segment fsync 到磁盘 │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ • 执行 fsync(真正的磁盘持久化) │ │ │
│ │ │ • 清空 Translog(数据已安全落盘) │ │ │
│ │ │ • Commit Point 更新 │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 后台 Merge │ │
│ │ │ │
│ │ 7. 合并多个小 Segment 为大 Segment │ │
│ │ • 减少文件句柄和搜索时的 Segment 遍历开销 │ │
│ │ • 删除标记为 deleted 的文档(物理删除) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.2 Refresh 间隔:近实时搜索的代价
┌─────────────────────────────────────────────────────────────┐
│ Refresh 间隔的权衡 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 默认 refresh_interval = 1s │
│ → 文档写入后最多 1 秒即可被搜索到 │
│ → 代价:每秒产生新的 Segment,后续需要频繁 Merge │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 场景 推荐 refresh_interval │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ 实时搜索(电商、新闻) 1s (默认) │ │
│ │ 日志分析 (ELK) 30s - 60s │ │
│ │ 批量导入数据中 -1 (禁用 refresh) │ │
│ │ 归档数据/只读索引 -1 (永远不需要 refresh) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 批量写入优化: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PUT /my_index/_settings │ │
│ │ { │ │
│ │ "refresh_interval": "-1", // 禁用 │ │
│ │ "number_of_replicas": 0 // 禁用副本 │ │
│ │ } │ │
│ │ // ... bulk 导入 ... │ │
│ │ PUT /my_index/_settings │ │
│ │ { │ │
│ │ "refresh_interval": "1s", │ │
│ │ "number_of_replicas": 1 │ │
│ │ } │ │
│ │ POST /my_index/_forcemerge?max_num_segments=1 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.3 Translog:数据安全的最后防线
┌─────────────────────────────────────────────────────────────┐
│ Translog 机制详解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Translog 的两种持久化策略: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ request(默认):每次写入都 fsync translog │ │
│ │ → 最安全,节点崩溃不丢数据 │ │
│ │ → 写入性能受磁盘 fsync 频率限制 │ │
│ │ │ │
│ │ async:每隔 sync_interval 异步 fsync │ │
│ │ → 写入吞吐更高(减少 fsync 次数) │ │
│ │ → 崩溃可能丢失 sync_interval 内的数据 │ │
│ │ → 配置: │ │
│ │ "index.translog.durability": "async" │ │
│ │ "index.translog.sync_interval": "5s" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Translog 在崩溃恢复中的作用: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 节点崩溃重启后: │ │
│ │ │ │
│ │ 1. 读取最后一次 Flush 的 Commit Point │ │
│ │ 2. 重放 Translog 中 Commit Point 之后的所有操作 │ │
│ │ 3. Replay 完成后,数据恢复到崩溃前状态 │ │
│ │ │ │
│ │ Translog 的位置: │ │
│ │ data/nodes/0/indices/<index_uuid>/0/translog/ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第4部分:集群健康
4.1 Green / Yellow / Red
┌─────────────────────────────────────────────────────────────┐
│ 集群健康三色状态 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GREEN(绿) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 所有主分片和副本分片都已分配且可用 │ │ │
│ │ │ 这是理想状态,一切正常 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ YELLOW(黄) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 所有主分片已分配,但至少一个副本分片未分配 │ │ │
│ │ │ 数据安全:主分片正常 → 写入正常、搜索正常 │ │ │
│ │ │ 风险:主分片所在的节点挂掉 → 数据不可用 │ │ │
│ │ │ │ │ │
│ │ │ 常见原因: │ │ │
│ │ │ • 单节点集群(副本无法分配) │ │ │
│ │ │ • 节点数 < 副本数+1 │ │ │
│ │ │ • 磁盘满了 (disk watermark) │ │ │
│ │ │ • 分片分配被手动禁用 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ RED(红) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 至少一个主分片未分配 │ │ │
│ │ │ 数据安全:丢失数据或不可用 │ │ │
│ │ │ 影响:写入/搜索返回 503 / partial results │ │ │
│ │ │ │ │ │
│ │ │ 常见原因: │ │ │
│ │ │ • 持有主分片的节点宕机且无副本 │ │ │
│ │ │ • 磁盘满了导致分片不可写入 │ │ │
│ │ │ • 脑裂导致多主分片冲突 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.2 Unassigned Shards 排查流程
┌─────────────────────────────────────────────────────────────┐
│ Unassigned Shards 排查五步法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 查看未分配原因 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /_cat/shards?v&h=index,shard,prirep,state, │ │
│ │ unassigned.reason │ │
│ │ │ │
│ │ 常见 reason: │ │
│ │ • INDEX_CREATED: 索引刚创建,副本尚未分配 │ │
│ │ • CLUSTER_RECOVERED: 集群从 full restart 恢复 │ │
│ │ • ALLOCATION_FAILED: 分配失败(磁盘满等) │ │
│ │ • NODE_LEFT: 节点离开,副本需要重新分配 │ │
│ │ • REALLOCATED: 分片正在重新分配中 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 2: 查看分配解释 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /_cluster/allocation/explain │ │
│ │ { │ │
│ │ "index": "products", │ │
│ │ "shard": 0, │ │
│ │ "primary": false │ │
│ │ } │ │
│ │ → 返回详细的分配失败原因和决策过程 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 3: 检查磁盘水位线 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ cluster.routing.allocation.disk.watermark.low: 85% │ │
│ │ → 达到此值,不再分配新分片到该节点 │ │
│ │ cluster.routing.allocation.disk.watermark.high: 90%│ │
│ │ → 达到此值,尝试将分片迁移到其他节点 │ │
│ │ cluster.routing.allocation.disk.watermark.flood: │ │
│ │ 95% (固定) │ │
│ │ → 达到此值,ES 将索引设为 read-only-allow-delete │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 4: 手动重试分配 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ POST /_cluster/reroute?retry_failed=true │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 5: 如果还不行,检查配置 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 是否禁用了 shard allocation? │ │
│ │ GET /_cluster/settings │ │
│ │ → cluster.routing.allocation.enable: "none" │ │
│ │ │ │
│ │ • 恢复: │ │
│ │ PUT /_cluster/settings │ │
│ │ {"transient": {"cluster.routing.allocation. │ │
│ │ enable": "all"}} │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第5部分:集群运维
5.1 滚动重启(Rolling Restart)
┌─────────────────────────────────────────────────────────────┐
│ 滚动重启操作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 目标:升级 ES 版本或修改配置,服务不中断。 │
│ │
│ Step 1: 暂停分片分配(防止重启期间无意义的迁移) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PUT /_cluster/settings │ │
│ │ {"transient": {"cluster.routing.allocation. │ │
│ │ enable": "none"}} │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 2: 执行 sync flush(加速恢复) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ POST /_flush/synced │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 3: 逐个重启节点 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ For each node: │ │
│ │ a) 停止 ES 进程 │ │
│ │ b) 执行升级/配置修改 │ │
│ │ c) 启动 ES 进程 │ │
│ │ d) 等待节点重新加入集群 │ │
│ │ GET /_cat/health?v → 确认状态正常 │ │
│ │ e) 确认没有 unassigned shards │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 4: 恢复分片分配 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PUT /_cluster/settings │ │
│ │ {"transient": {"cluster.routing.allocation. │ │
│ │ enable": "all"}} │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 5: 等待集群恢复 Green 状态 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /_cat/health?v │ │
│ │ GET /_cat/recovery?v │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.2 快照备份与恢复
┌─────────────────────────────────────────────────────────────┐
│ 快照备份策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 快照是 ES 唯一的全量备份方式,基于增量机制。 │
│ │
│ Step 1: 注册快照仓库 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PUT /_snapshot/my_backup │ │
│ │ { │ │
│ │ "type": "fs", │ │
│ │ "settings": { │ │
│ │ "location": "/mnt/backups/es", │ │
│ │ "compress": true │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 2: 创建快照 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PUT /_snapshot/my_backup/snapshot_2024_01_15 │ │
│ │ { │ │
│ │ "indices": "products,orders,logs-*", │ │
│ │ "ignore_unavailable": true, │ │
│ │ "include_global_state": true │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 3: 监控快照进度 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /_snapshot/my_backup/snapshot_2024_01_15 │ │
│ │ GET /_snapshot/my_backup/_status │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Step 4: 恢复快照 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ POST /_snapshot/my_backup/snapshot_2024_01_15/ │ │
│ │ _restore │ │
│ │ { │ │
│ │ "indices": "products", │ │
│ │ "rename_pattern": "products", │ │
│ │ "rename_replacement": "products_restored" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ SLM (Snapshot Lifecycle Management) 自动备份策略: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每天凌晨 2 点 → 增量快照 │ │
│ │ 每周日凌晨 3 点 → 全量快照 │ │
│ │ 保留策略:最近 7 天 + 最近 4 周 + 最近 3 月 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.3 跨集群复制 (CCR)
┌─────────────────────────────────────────────────────────────┐
│ 跨集群复制 CCR │
├─────────────────────────────────────────────────────────────┤
│ │
│ CCR 是 ES 白金版功能,用于跨数据中心的数据同步。 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 机房 A(主集群) 机房 B(从集群) │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Node 1-3 │ │ Node 1-3 │ │ │
│ │ │ (Leader) │ ───→ │ (Follower) │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │ │ │
│ │ 写入正常 只读(或故障切换后可写) │ │
│ │ │ │
│ │ 同步方式:基于 Translog 的增量复制 │ │
│ │ 延迟:秒级(网络延迟 + 写入延迟) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 使用场景: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 灾备:主集群宕机后切到从集群 │ │
│ │ • 就近搜索:中国用户 → 中国集群,美国用户 → 美国集群│ │
│ │ • 数据聚合:多个集群的数据同步到一个中心集群 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 配置 Follower Index: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PUT /products/_ccr/follow │ │
│ │ { │ │
│ │ "remote_cluster": "cluster_beijing", │ │
│ │ "leader_index": "products" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第6部分:性能调优
6.1 Heap 与内存管理
┌─────────────────────────────────────────────────────────────┐
│ Heap 内存配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ES 是基于 JVM 的,Heap 配置是最关键的性能参数。 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 32GB 诅咒: │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ JVM 在使用超过 32GB heap 时,对象指针从 │ │ │
│ │ │ 32-bit 压缩指针变为 64-bit 普通指针 │ │ │
│ │ │ → 每个对象增加约 50% 的内存占用 │ │ │
│ │ │ → 等效于花了更多内存却没什么性能收益 │ │ │
│ │ │ │ │ │
│ │ │ 所以 Heap 建议:不超过 30.5GB (31GB) │ │ │
│ │ │ 官方推荐: 50% 物理内存,但最大 31GB │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Heap 分配策略: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 剩余 50% 内存给 OS Page Cache (Lucene 使用) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 总内存 64GB 的机器: │ │ │
│ │ │ ┌──────────────────────────────────────┐ │ │
│ │ │ │ Heap: 31GB (JVM) │ │ │
│ │ │ │ ├─ young gen: ~3GB │ │ │
│ │ │ │ └─ old gen: ~28GB │ │ │
│ │ │ ├──────────────────────────────────────┤ │ │
│ │ │ │ OS Page Cache: ~31GB (给 Lucene) │ │ │
│ │ │ ├──────────────────────────────────────┤ │ │
│ │ │ │ OS + 其他进程: ~2GB │ │ │
│ │ │ └──────────────────────────────────────┘ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 为什么 OS Page Cache 如此重要? │ │
│ │ • Lucene 的 Segment 文件存在磁盘上 │ │
│ │ • OS Page Cache 将这些文件缓存到内存 │ │
│ │ • 命中 Page Cache 的查询比命中磁盘快 100+ 倍 │ │
│ │ • 如果 Heap 太大,挤占了 Page Cache,反而更慢 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.2 Bulk 写入优化
┌─────────────────────────────────────────────────────────────┐
│ Bulk 写入优化参数 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Bulk API 是 ES 写入的唯一高性能方式。单个 doc 索引请求 │
│ 意味着每次都是一次完整的网络往返 + refresh + translog │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 优化维度 推荐值 说明 │ │
│ │ ───────────────────────────────────────────── │ │
│ │ bulk size 5-15MB 太小→网络 │ │
│ │ 太大→OOM │ │
│ │ bulk 条数 500-2000 配合size使用 │ │
│ │ concurrent requests 2-4 per node 不是越大越好 │ │
│ │ refresh_interval -1 (导入中) 导入完恢复 │ │
│ │ number_of_replicas 0 (导入中) 导入完补副本 │ │
│ │ translog durability async (可选) 牺牲安全换 │ │
│ │ 吞吐 │ │
│ │ index buffer size 10-20% heap 越大→越少 │ │
│ │ refresh │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Bulk 大小测试方法: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 从 1MB 开始 → 逐步增大到 20MB │ │
│ │ 观察指标:写入 TPS、GC 频率、请求超时率 │ │
│ │ 找到 TPS 不再增长但 GC 开始增加的那个拐点 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.3 查询缓存全景
┌─────────────────────────────────────────────────────────────┐
│ ES 查询缓存体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ES 有四层缓存,从快到慢依次是: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. Request Cache (Shard 级别) │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ 条件:size=0 的查询(即只查聚合) │ │ │
│ │ │ 缓存:整个查询的 JSON 结果 │ │ │
│ │ │ 命中条件:完全相同的 JSON 请求体 │ │ │
│ │ │ 失效:shard refresh 后自动失效 │ │ │
│ │ │ 监控:GET /_stats/request_cache │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. Query Cache (Node 级别) │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ 条件:filter context 的查询 │ │ │
│ │ │ 缓存:filter 匹配的文档 ID 集合 (BitSet) │ │ │
│ │ │ 命中条件:完全相同的 filter 条件 │ │ │
│ │ │ 大小:默认 10% heap,LRU 淘汰 │ │ │
│ │ │ 监控:GET /_stats/query_cache │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. Field Data Cache (Node 级别) │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ 用途:排序、聚合、script 用的字段数据 │ │ │
│ │ │ 针对:text 字段的 fielddata(默认禁用) │ │ │
│ │ │ keyword/numeric 的 doc_values │ │ │
│ │ │ 大小:默认 40% heap (但尽量别让聚合吃满) │ │ │
│ │ │ 注意:fielddata 是堆内,doc_values 是堆外 │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 4. OS Page Cache (OS 级别,最重要的缓存) │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ 缓存:Lucene Segment 文件的磁盘页 │ │ │
│ │ │ 优点:不需要 JVM Heap,利用 OS 内存管理 │ │ │
│ │ │ 监控:无法直接查看,通过 disk IO 间接判断 │ │ │
│ │ │ (iostat, node_stats 的 disk 指标) │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 缓存监控关键指标: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /_nodes/stats/indices/query_cache, │ │
│ │ request_cache,fielddata,segments?human │ │
│ │ │ │
│ │ 关注: │ │
│ │ • query_cache.hit_count / miss_count │ │
│ │ • fielddata.evictions(驱逐过多说明内存不够) │ │
│ │ • segments.memory_in_bytes(Segment 内存占用) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:节点角色
Master → 集群管理(创建索引、分配分片、节点管理)
Data → 数据存储 + 搜索执行(CPU/IO 密集)
Ingest → 写入预处理(Pipeline 处理)
Coordinating → 请求路由 + 结果合并(负载均衡)总结2:写入链路
文档 → Coordinating Node → Primary Shard
→ Translog (WAL) → In-Memory Buffer
→ Refresh (1s) → Segment (可搜索, NRT)
→ Flush (30min) → fsync + 清 Translog
→ Merge → 合并小 Segment总结3:集群健康
Green → 所有主+副本已分配
Yellow → 主分片正常,部分副本未分配(数据安全但高可用受损)
Red → 有主分片未分配(数据可能丢失)总结4:性能调优关键参数
Heap: 31GB 上限(32GB 压缩指针诅咒)
OS Page Cache: 至少占物理内存的 50%
Bulk: 5-15MB batch, 2-4 并发
Refresh: 根据场景调整(批量导入时设为 -1)
副本: 批量导入时设为 0,导入完后补副本章节测试
测试1:节点角色
如果集群有 3 个节点,全部配置为 master: true, data: true,当其中 1 个节点宕机时会发生什么?
测试2:分片计算
100GB 的数据,3 个数据节点(每节点 4 核 32GB RAM),应该设置多少个主分片?
测试3:写入流程
新写入的文档为什么不能立即被搜索到?ES 是如何做到"近实时"搜索的?
测试4:集群健康
Yellow 状态意味着什么?在单节点集群中,这是正常的吗?
测试5:内存
为什么 ES 的 heap 不建议超过 31GB?OS Page Cache 的作用是什么?
测试6:备份
你需要设计一个灾备方案:主集群在北京,希望在上海有一个热备份集群。应该用什么方案?
测试7:综合故障
凌晨 3 点,集群变 Red。你是值班人员。描述完整的排查和处理流程。
参考答案
测试1答案
答案:剩余 2 个节点会选举出新的 Master(因为 discovery.zen.minimum_master_nodes 或 cluster.initial_master_nodes 的设置),集群继续运行。但由于副本分片分布在剩余节点上,集群可能短暂 Yellow(副本迁到其他节点后恢复)。如果宕机节点持有主分片且无副本,则集群变 Red。
测试2答案
答案:
- 数据量约束:100GB / 30GB = 3-4 个
- CPU 约束:3×4=12 核 × 2 = 24 个上限
- Heap 约束:31GB × 20 = 620 个上限
- 推荐:5-6 个主分片(预留增长空间,后续可扩展节点数)
- 副本数:1(2 份拷贝,安全 + 可集群扩容)
测试3答案
答案:新文档先写入 In-Memory Buffer,此时未构建倒排索引的 Segment 文件,所以不可搜索。每隔 1 秒(默认 refresh_interval),ES 将 Buffer 中的文档写入新的 Segment 文件并打开,文档变为可搜索。这就是"近实时"(Near Real-Time, NRT)——不是实时的,有最多 1 秒的延迟。
测试4答案
答案:Yellow 表示所有主分片已分配,但至少一个副本分片未分配。在单节点集群中,这是正常的(副本无法分配到同一节点,违反高可用原则)。在生产多节点集群中,Yellow 通常需要排查(节点不足、磁盘满、分片迁移中等)。
测试5答案
答案:因为 JVM 的压缩对象指针(Compressed OOPs)在 heap 超过 32GB 时失效,每个对象指针从 32-bit 变为 64-bit,内存占用增加约 50%。建议 heap 设 31GB 或更小。
OS Page Cache 缓存 Lucene 的 Segment 文件到内存,是 ES 最重要的缓存层。由于 Lucene 数据文件不可变,OS Page Cache 的命中率极高。如果 Heap 太大挤占了 Page Cache,反而导致大量磁盘 IO,查询性能断崖式下跌。
测试6答案
答案:使用 CCR (Cross-Cluster Replication) + 上海集群作为 Follower 索引。北京集群正常写入,CCR 基于 Translog 将变更秒级同步到上海集群。故障切换时,将上海集群的 Follower 提升为常规索引,流量切过去。同时需要配置客户端(应用/网关层)的集群切换逻辑。
测试7答案
答案:排查流程:
确认 Red 范围和影响面
GET /_cat/health?v GET /_cat/indices?v&health=red查看未分配分片及原因
GET /_cat/shards?v&h=index,shard,prirep,state,unassigned.reason GET /_cluster/allocation/explain (针对 Red 的分片)常见原因及处理:
- 如果 reason=NODE_LEFT → 检查节点是否宕机,尝试重启
- 如果磁盘水位过高 → 清理磁盘或扩容,然后
POST /_cluster/reroute?retry_failed - 如果多节点同时宕机 → 先恢复节点,若数据真丢了则从快照恢复
如果确认数据丢失且无副本:
POST /_cluster/reroute?retry_failed=true {放弃分配的命令}或
DELETE问题索引,从快照恢复记录故障时间线、通知相关方、事后复盘
相关笔记
- [[./00-overview]] - 搜索引擎概览
- [[./02-elasticsearch-queries]] - ES 查询 DSL 深入
- [[/03-web/07-middleware/06-elasticsearch]] - ES 倒排索引深入
下一步学习
- [ ] 阅读 04 - Meilisearch 与替代方案
学习状态:🟡 开始学习