前言
这是一份基于 2C4G 5M 带宽服务器的 ES 调优实战经验,适用于中小规模生产环境。核心目标:稳定性优先,兼顾性能。
一、JVM 堆内存配置
基础原则
bash
ES_JAVA_OPTS="-Xms1g -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
为什么这样配置?
-
堆内存大小:1G
- 4G 总内存的服务器,分配 1G 给 JVM 堆
- 预留 50% 内存(2G+)给 Lucene 文件缓存和操作系统
- 错误示范 :
-Xmx2g会导致频繁 OOM 或 swap
-
Xms = Xmx
- 避免运行时动态调整堆大小的开销
- 减少内存碎片
-
G1GC 垃圾回收器
- 适合中小堆内存(< 4G)
- 更可预测的停顿时间
MaxGCPauseMillis=200目标停顿 200ms 以内
不同内存规格参考
| 服务器内存 | JVM 堆内存 | 系统预留 |
|---|---|---|
| 2G | 512M | 1.5G |
| 4G | 1G | 3G |
| 8G | 2-3G | 5-6G |
| 16G | 4-6G | 10-12G |
注意:JVM 堆内存不要超过 32G,否则失去指针压缩优化。
二、Swap 必须禁用
Swap 是什么?
Swap 是硬盘上的虚拟内存,当物理内存不足时,系统会把不常用的数据"换出"到硬盘。
为什么 ES 必须禁用 Swap?
性能差距:内存访问比硬盘快 100,000 倍
| 场景 | 内存访问 | Swap 访问 | 影响 |
|---|---|---|---|
| 查询倒排索引 | 纳秒级 | 毫秒级 | 查询超时 |
| GC 扫描堆内存 | 200ms | 几十秒 | 集群雪崩 |
| 分片恢复 | 秒级 | 分钟级 | 节点掉线 |
如何禁用?
bash
# 1. 立即禁用(当前会话生效)
sudo swapoff -a
# 2. 永久禁用(重启后生效)
sudo sed -i '/swap/s/^/#/' /etc/fstab
# 3. 验证
free -h
# Swap 行应该显示:0B 0B 0B
三、Docker 环境配置
Docker Compose 完整配置
yaml
services:
elasticsearch:
container_name: ${CONTAINER_NAME}
restart: always
image: elasticsearch:8.19.9
ports:
- "${PANEL_APP_PORT_HTTP}:9200"
volumes:
- "./data/data:/usr/share/elasticsearch/data"
- "./data/backup:/usr/share/elasticsearch/backup"
- "./data/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml"
# 内存限制(关键)
mem_limit: 2.5g # 容器最多用 2.5G(3.5G 服务器留 1G 给系统)
mem_reservation: 1.5g # 保证至少 1.5G 可用
mem_swappiness: 0 # 禁止容器使用 swap
# 文件描述符和内存锁定
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
environment:
- discovery.type=single-node
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- xpack.security.enabled=${ELASTIC_SECURITY}
- bootstrap.memory_lock=true # 锁定内存到物理 RAM
- ${P_ES_JAVA_OPTS}
networks:
- 1panel-network
networks:
1panel-network:
external: true
重要说明:
- 如果挂载了自定义
elasticsearch.yml,确保文件中也包含bootstrap.memory_lock: true mem_limit根据实际服务器内存调整(4G 服务器用 3g,3.5G 服务器用 2.5g)
环境变量配置(.env 文件)
bash
ES_JAVA_OPTS=-Xms1g -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
关键参数说明
-
mem_limit: 2.5g
- 根据实际服务器内存调整
- 3.5G 服务器用 2.5g,4G 服务器用 3g
- 必须留至少 1G 给系统
-
mem_swappiness: 0
- 告诉内核尽量不要 swap 这个容器
- 配合宿主机
swapoff -a双重保险
-
bootstrap.memory_lock: true
- 锁定 JVM 堆内存到物理 RAM
- 防止被操作系统 swap 出去
- 必须同时在 elasticsearch.yml 中配置
四、Elasticsearch 配置文件调优
elasticsearch.yml 核心配置
yaml
# 集群名称
cluster.name: my-application
# 节点名称
node.name: node-1
# 数据和日志路径
path.data: /usr/share/elasticsearch/data
path.logs: /usr/share/elasticsearch/logs
# 网络配置
network.host: 0.0.0.0
http.port: 9200
# 内存锁定(必须配置,配合 Docker ulimits)
bootstrap.memory_lock: true
# 索引缓冲区(默认 10%,可适当提高)
indices.memory.index_buffer_size: 20%
# 分片分配策略(单节点可忽略)
cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.low: 85%
cluster.routing.allocation.disk.watermark.high: 90%
注意:
- ES 8.x 已移除手动设置线程池队列大小的功能,系统会自动优化
- 如果使用 Docker 环境变量设置了
bootstrap.memory_lock=true,这里也必须配置,否则可能不生效
五、验证配置是否生效
1. 检查内存锁定
bash
docker exec <容器名> curl -s localhost:9200/_nodes?filter_path=**.mlockall
期望输出:
json
{"nodes":{"xxx":{"process":{"mlockall":true}}}}
2. 检查 JVM 堆内存
bash
curl localhost:9200/_cat/nodes?v&h=heap.percent,heap.current,heap.max,ram.percent
期望输出:
heap.percent heap.current heap.max ram.percent
45 450mb 1gb 60
3. 监控容器资源
bash
docker stats <容器名>
关注指标:
- MEM USAGE:应该在 2-2.5G 左右
- MEM %:不超过 75%
4. 检查 GC 情况
bash
curl localhost:9200/_nodes/stats/jvm?pretty
关注指标:
jvm.gc.collectors.young.collection_time_in_millis:年轻代 GC 时间jvm.gc.collectors.old.collection_count:老年代 GC 次数(应该很少)
六、性能监控与告警阈值
关键指标
| 指标 | 健康值 | 警告值 | 危险值 |
|---|---|---|---|
| JVM 堆使用率 | < 75% | 75-85% | > 85% |
| GC 停顿时间 | < 200ms | 200-500ms | > 500ms |
| 查询响应时间 | < 100ms | 100-500ms | > 500ms |
| 系统可用内存 | > 1G | 500M-1G | < 500M |
| CPU 使用率 | < 70% | 70-85% | > 85% |
监控命令
bash
# 实时监控堆内存
watch -n 1 'curl -s localhost:9200/_cat/nodes?v&h=heap.percent,ram.percent'
# 查看慢查询
curl localhost:9200/_cat/thread_pool?v&h=name,active,queue,rejected
# 查看索引统计
curl localhost:9200/_cat/indices?v&s=store.size:desc
七、常见问题排查
问题 1:频繁 Full GC
症状 :jvm.gc.collectors.old.collection_count 持续增长
原因:
- 堆内存设置过小
- 查询或聚合消耗大量内存
- 内存泄漏
解决:
bash
# 1. 查看哪些查询占用内存
curl localhost:9200/_nodes/stats/indices/fielddata?fields=*
# 2. 清理 fielddata 缓存
curl -X POST localhost:9200/_cache/clear?fielddata=true
# 3. 限制 fielddata 缓存大小(elasticsearch.yml)
indices.fielddata.cache.size: 20%
问题 2:查询超时
症状 :请求返回 timeout 错误
原因:
- 数据被 swap 到硬盘
- 分片数过多
- 查询未优化
解决:
bash
# 1. 检查是否使用了 swap
free -h
# 2. 查看分片状态
curl localhost:9200/_cat/shards?v
# 3. 合并小索引
curl -X POST localhost:9200/<索引名>/_forcemerge?max_num_segments=1
问题 3:容器 OOM 被杀
症状 :docker logs 显示 Killed 或 137 退出码
原因:
mem_limit设置过小- JVM 堆外内存超限
解决:
yaml
# 调整 mem_limit(至少是 JVM 堆的 2.5 倍)
mem_limit: 3g # JVM 堆 1G,留 2G 给堆外内存
八、不同场景的配置建议
场景 1:日志存储(写多读少)
yaml
# elasticsearch.yml
indices.memory.index_buffer_size: 30% # 提高写入缓冲
refresh_interval: 30s # 降低刷新频率
number_of_replicas: 0 # 单节点无需副本
场景 2:搜索服务(读多写少)
yaml
# elasticsearch.yml
indices.queries.cache.size: 15% # 提高查询缓存
indices.fielddata.cache.size: 30% # 提高字段数据缓存
场景 3:实时分析(读写均衡)
yaml
# elasticsearch.yml
indices.memory.index_buffer_size: 20%
refresh_interval: 5s # 准实时刷新
注意 :ES 8.x 不再支持手动设置 thread_pool.write.queue_size,系统会根据 CPU 核心数自动优化。
九、升级建议
当出现以下情况时,说明 2C4G 配置已不够用:
- JVM 堆使用率持续 > 85%
- 频繁 Full GC(每小时 > 10 次)
- 查询响应时间 > 500ms
- 数据量 > 10GB
推荐升级路径:
- 4C8G:适合 50GB 以内数据
- 8C16G:适合 200GB 以内数据
- 16C32G:适合 TB 级数据
十、总结
核心要点
- 内存分配:JVM 堆不超过物理内存的 50%
- 禁用 Swap:宿主机 + Docker 双重禁用
- 内存锁定 :
bootstrap.memory_lock=true - 容器限制 :
mem_limit防止 OOM - 持续监控:堆使用率、GC 时间、查询延迟
配置检查清单
- JVM 堆内存 = 物理内存 × 25-50%
- Xms = Xmx
- 宿主机执行
swapoff -a -
/etc/fstab注释 swap 行 - Docker Compose 添加
mem_limit和mem_swappiness -
bootstrap.memory_lock=true生效 -
ulimits.memlock设置为-1 - 验证
mlockall: true
最后的话
ES 调优是个持续过程,没有一劳永逸的配置。根据实际业务场景监控指标,逐步调整参数,才能达到最佳状态。
记住:稳定性永远比性能更重要,宁可查询慢 100ms,也不要让服务挂掉。
参考资料: