Elasticsearch 底层原理系列(九):生产实践与性能调优
2020-12-31·6 分钟阅读
前言
将 Elasticsearch 应用于生产环境需要考虑诸多因素:硬件选型、JVM 配置、索引设计、查询优化、监控告警等。本章将总结生产环境的最佳实践和性能调优技巧。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 硬件选型 | ⭐⭐ | 实战价值 | ✅ |
| JVM 调优 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 索引设计 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 性能监控 | ⭐⭐⭐ | 实战价值 | ✅ |
| 问题排查 | ⭐⭐⭐⭐ | 实战价值 | ✅ |
面试考点
- ES 生产环境的硬件应该如何选型?
- JVM 堆内存应该如何配置?为什么有 32GB 限制?
- 如何设计索引以获得最佳性能?
- ES 常见的性能问题有哪些?如何排查?
硬件选型
存储选型
┌─────────────────────────────────────────────────────────────────────────┐
│ 存储选型建议 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 存储类型对比: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 存储类型 随机读写 顺序读写 成本 推荐场景 │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ HDD 低 中 低 冷数据 │ │
│ │ SATA SSD 中 高 中 温数据 │ │
│ │ NVMe SSD 高 极高 高 热数据 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 推荐: │
│ • 热数据:NVMe SSD │
│ • 温数据:SATA SSD │
│ • 冷数据:HDD 或对象存储 │
│ │
│ 注意事项: │
│ • 避免使用网络存储(NFS、NAS) │
│ • 禁用 swap │
│ • 使用 XFS 文件系统 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
内存配置
┌─────────────────────────────────────────────────────────────────────────┐
│ 内存配置建议 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ JVM 堆内存: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 堆内存 ≤ 物理内存的 50% │ │
│ │ • 堆内存最大 31GB(压缩指针阈值) │ │
│ │ • 设置 Xms = Xmx(避免动态调整) │ │
│ │ │ │
│ │ 示例:64GB 物理内存 │ │
│ │ • 堆内存:31GB │ │
│ │ • 留给 OS Cache:33GB │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 为什么有 31GB 限制? │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ JVM Compressed OOPs(压缩指针): │ │
│ │ • < 32GB:使用 32 位指针,内存效率高 │ │
│ │ > 32GB:使用 64 位指针,内存开销增大 │ │
│ │ │ │
│ │ 超过 32GB 后: │ │
│ │ • 有效内存反而可能减少 │ │
│ │ • GC 停顿时间增加 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 内存锁定配置: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ # elasticsearch.yml │ │
│ │ bootstrap.memory_lock: true │ │
│ │ │ │
│ │ # 系统配置 │ │
│ │ ulimit -l unlimited │ │
│ │ │ │
│ │ # 或在 /etc/security/limits.conf │ │
│ │ elasticsearch soft memlock unlimited │ │
│ │ elasticsearch hard memlock unlimited │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
CPU 配置
┌─────────────────────────────────────────────────────────────────────────┐
│ CPU 配置建议 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 不同角色的 CPU 需求: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 节点角色 CPU 核心 说明 │ │
│ │ ────────────────────────────────────────────────────── │ │
│ │ Master 4-8 轻量级,核心数不必太多 │ │
│ │ Data 8-32+ 计算、索引、合并都需要 CPU │ │
│ │ Coordinating 4-16 请求解析、结果聚合 │ │
│ │ Ingest 8-16 数据预处理 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 线程池配置: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ # 默认配置通常足够 │ │
│ │ thread_pool.search.size: 可用处理器数 │ │
│ │ thread_pool.search.queue_size: 1000 │ │
│ │ thread_pool.write.size: 可用处理器数 │ │
│ │ thread_pool.write.queue_size: 10000 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
JVM 调优
JVM 配置
# jvm.options
# 堆内存设置
-Xms31g
-Xmx31g
# GC 配置(ES 8.x 默认使用 G1GC)
-XX:+UseG1GC
# G1GC 调优
-XX:G1HeapRegionSize=16m
-XX:G1ReservePercent=25
-XX:InitiatingHeapOccupancyPercent=30
# 内存溢出时生成堆转储
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/elasticsearch/heapdump.hprof
# GC 日志
-Xlog:gc*,gc+age=trace,gc+heap=debug:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
GC 监控
┌─────────────────────────────────────────────────────────────────────────┐
│ GC 监控指标 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 正常指标: │
│ • GC 频率:< 1 次/秒 │
│ • Young GC 时间:< 50ms │
│ • Full GC 频率:< 1 次/小时 │
│ • Full GC 时间:< 1s │
│ │
│ 异常信号: │
│ • 频繁 Full GC │
│ • GC 时间过长 │
│ • 堆内存使用率 > 75% │
│ │
│ 查看命令: │
│ GET /_nodes/stats?filter_path=nodes.*.jvm.gc │
│ │
└─────────────────────────────────────────────────────────────────────────┘
索引设计最佳实践
Mapping 设计
┌─────────────────────────────────────────────────────────────────────────┐
│ Mapping 设计原则 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 禁用不需要的特性 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ PUT /my_index │ │
│ │ { │ │
│ │ "mappings": { │ │
│ │ "properties": { │ │
│ │ "title": { │ │
│ │ "type": "text", │ │
│ │ "norms": false, // 不需要评分时禁用 │ │
│ │ "index_options": "freqs" // 不需要位置信息 │ │
│ │ }, │ │
│ │ "status": { │ │
│ │ "type": "keyword", │ │
│ │ "doc_values": true, // 聚合排序需要 │ │
│ │ "index": false // 不需要全文搜索 │ │
│ │ }, │ │
│ │ "timestamp": { │ │
│ │ "type": "date", │ │
│ │ "doc_values": true │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 使用合适的字段类型 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • keyword: 精确匹配、聚合、排序 │ │
│ │ • text: 全文搜索 │ │
│ │ • date: 时间字段 │ │
│ │ • integer/long: 数值范围查询 │ │
│ │ • nested: 嵌套对象数组 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 避免动态映射 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ PUT /my_index │ │
│ │ { │ │
│ │ "mappings": { │ │
│ │ "dynamic": "strict", // 或 false │ │
│ │ "properties": { ... } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
分片规划
┌─────────────────────────────────────────────────────────────────────────┐
│ 分片规划实践 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 分片数量建议: │
│ • 单分片大小:10GB - 50GB │
│ • 每个节点分片数:≤ 堆内存(GB) × 20 │
│ │
│ 副本数量: │
│ • 生产环境:至少 1 个副本 │
│ • 高可用:2 个副本 │
│ • 离线分析:可以 0 副本 │
│ │
│ 示例配置: │
│ PUT /my_index │
│ { │
│ "settings": { │
│ "number_of_shards": 5, │
│ "number_of_replicas": 1 │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────┘
索引模板
// 索引模板示例
PUT /_index_template/logs_template
{
"index_patterns": ["logs-*"],
"priority": 100,
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"index.lifecycle.name": "logs_policy",
"index.lifecycle.rollover_alias": "logs"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" },
"level": { "type": "keyword" },
"service": { "type": "keyword" }
}
}
}
}
查询优化
查询优化技巧
┌─────────────────────────────────────────────────────────────────────────┐
│ 查询优化技巧 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 使用 Filter 代替 Query │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // 精确匹配使用 Filter(可缓存) │ │
│ │ { │ │
│ │ "query": { │ │
│ │ "bool": { │ │
│ │ "must": [ │ │
│ │ { "match": { "title": "search" } } │ │
│ │ ], │ │
│ │ "filter": [ │ │
│ │ { "term": { "status": "published" } }, │ │
│ │ { "range": { "date": { "gte": "2023-01-01" } } } │ │
│ │ ] │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 限制返回字段 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // 只返回需要的字段 │ │
│ │ { │ │
│ │ "_source": ["title", "author", "date"], │ │
│ │ "query": { ... } │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 避免深度分页 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // 使用 search_after 代替 from/size │ │
│ │ { │ │
│ │ "size": 100, │ │
│ │ "query": { ... }, │ │
│ │ "sort": [ │ │
│ │ { "date": "desc" }, │ │
│ │ { "_id": "asc" } │ │
│ │ ], │ │
│ │ "search_after": ["2023-08-30", "doc_123"] │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 4. 使用批量操作 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // 使用 _bulk 批量写入 │ │
│ │ POST /_bulk │ │
│ │ {"index": {"_index": "my_index", "_id": "1"}} │ │
│ │ {"title": "Document 1"} │ │
│ │ {"index": {"_index": "my_index", "_id": "2"}} │ │
│ │ {"title": "Document 2"} │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
性能监控
关键监控指标
┌─────────────────────────────────────────────────────────────────────────┐
│ 关键监控指标 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 集群级别: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ GET /_cluster/health │ │
│ │ • status: green/yellow/red │ │
│ │ • number_of_nodes │ │
│ │ • number_of_data_nodes │ │
│ │ • active_shards_percent │ │
│ │ • unassigned_shards │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 节点级别: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ GET /_nodes/stats │ │
│ │ • jvm.mem.heap_used_percent < 75% │ │
│ │ • jvm.gc收集时间 Young < 50ms, Full < 1s │ │
│ │ • os.cpu.percent < 80% │ │
│ │ • fs.disk_reads/writes │ │
│ │ • thread_pool 队列积压 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 索引级别: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ GET /_stats │ │
│ │ • indexing.index_total │ │
│ │ • indexing.index_time_in_millis │ │
│ │ • search.query_total │ │
│ │ • search.query_time_in_millis │ │
│ │ • segments.count │ │
│ │ • segments.memory_in_bytes │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
常用监控命令
# 集群健康
GET /_cluster/health?pretty
# 节点信息
GET /_cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m,node.role
# 分片分配
GET /_cat/allocation?v
# 索引状态
GET /_cat/indices?v&health=yellow
# 慢查询
GET /_cat/thread_pool?v&h=node_name,name,active,queue,rejected
# 任务状态
GET /_tasks?detailed=true&actions=*search
常见问题排查
高 CPU 使用率
┌─────────────────────────────────────────────────────────────────────────┐
│ 高 CPU 排查 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 可能原因: │
│ 1. 复杂查询(深度聚合、脚本查询) │
│ 2. 频繁 GC │
│ 3. 大量索引操作 │
│ 4. Segment 合并 │
│ │
│ 排查步骤: │
│ 1. 查看热点线程:GET /_nodes/hot_threads │
│ 2. 检查慢查询日志 │
│ 3. 检查 GC 日志 │
│ 4. 检查索引写入速率 │
│ │
│ 解决方案: │
│ • 优化查询语句 │
│ • 增加缓存 │
│ • 调整线程池 │
│ • 增加节点 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
高内存使用
┌─────────────────────────────────────────────────────────────────────────┐
│ 高内存排查 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 可能原因: │
│ 1. 堆内存不足 │
│ 2. Field Data 过大 │
│ 3. Segment 内存占用 │
│ 4. 缓存配置过大 │
│ │
│ 排查步骤: │
│ 1. GET /_nodes/stats?filter_path=nodes.*.jvm │
│ 2. GET /_nodes/stats?filter_path=nodes.*.indices.segments │
│ 3. GET /_nodes/stats?filter_path=nodes.*.indices.fielddata │
│ │
│ 解决方案: │
│ • 增加堆内存(但不超过 31GB) │
│ • 使用 doc_values 代替 fielddata │
│ • 减少 Segment 数量(force merge) │
│ • 调整缓存大小 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
查询慢
┌─────────────────────────────────────────────────────────────────────────┐
│ 查询慢排查 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 可能原因: │
│ 1. 查询语句效率低 │
│ 2. 索引设计不合理 │
│ 3. 分片分布不均 │
│ 4. 缓存未命中 │
│ │
│ 排查步骤: │
│ 1. 使用 Profile API 分析查询 │
│ GET /my_index/_search?profile=true │
│ │
│ 2. 检查查询缓存命中率 │
│ GET /_stats?filter_path=indices.*.total.query_cache │
│ │
│ 3. 检查 Segment 数量 │
│ GET /_cat/segments?v │
│ │
│ 解决方案: │
│ • 优化查询语句(使用 Filter、限制范围) │
│ • 添加合适的索引和映射 │
│ • 调整分片数量 │
│ • 增加缓存大小 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
系列总结
核心知识点回顾
┌─────────────────────────────────────────────────────────────────────────┐
│ Elasticsearch 核心原理总结 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 架构层次: │
│ Cluster → Node → Index → Shard → Segment → Lucene Index │
│ │
│ 核心机制: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 写入流程:Buffer → Translog → Refresh → Segment → Flush │ │
│ │ 查询流程:Query (找ID) → Fetch (取文档) │ │
│ │ 评分算法:BM25(TF 饱和 + 文档长度归一化) │ │
│ │ 存储结构:倒排索引 + Doc Values(列式) + Stored Fields │ │
│ │ 分布式:分片路由 + Master 选举 + 副本同步 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 性能优化要点: │
│ • 硬件:SSD、充足内存、合理 CPU │
│ • JVM:堆内存 ≤ 31GB、禁用 swap │
│ • 索引:合理分片、优化 Mapping、使用模板 │
│ • 查询:Filter 优先、限制返回、避免深度分页 │
│ • 监控:集群健康、GC、慢查询 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
面试高频问题
| 问题 | 核心答案 |
|---|---|
| 为什么 ES 搜索快? | 倒排索引 + FST + 多分片并行 |
| 写入流程是什么? | Buffer → Refresh(1s) → Segment → Flush |
| 如何避免脑裂? | 奇数个 Master 节点 + minimum_master_nodes |
| 分片数如何规划? | 单分片 10-50GB,每节点 ≤ 20分片/GB堆内存 |
| Query vs Filter? | Filter 不计算分数、可缓存、更快 |
参考资料
本系列完。感谢阅读!
相关文章
Elasticsearch 底层原理系列(八):分布式一致性
2020-12-20·5 分钟阅读
深入解析 Elasticsearch 分布式一致性机制,包括 Master 选举、脑裂问题、故障检测与恢复、以及数据一致性保证。
Elasticsearch 底层原理系列(七):分片与路由机制
2020-12-13·5 分钟阅读
深入解析 Elasticsearch 分片路由机制,包括路由算法、自定义路由、分片数量规划、以及分片重平衡策略。
Elasticsearch 底层原理系列(六):集群架构与节点角色
2020-11-29·6 分钟阅读
深入解析 Elasticsearch 集群架构,包括 Master、Data、Coordinating 等节点角色的职责、集群状态管理、以及节点角色配置最佳实践。