开发者
鲲鹏服务器上 Elasticsearch 堆外内存泄漏排查
鲲鹏服务器上 Elasticsearch 堆外内存泄漏排查
原创
发表于05/09
190

现象

在鲲鹏 920 服务器(128 核 / 512GB 内存)部署 Elasticsearch 7.17 集群,运行 3 天后节点被 OOM Killer 杀掉。free -h 显示物理内存充足,但 dmesg 中有:

[123456.789012] Out of memory: Kill process 12345 (java) score 985 or sacrifice child
[123456.789123] Killed process 12345 (java) total-vm:520000000kB, anon-rss:480000000kB

进程虚拟内存 520GB,远超堆内存设置(-Xmx31g)。

定位

检查堆外内存使用:

# 查看 JVM 内存区域
jcmd $(pgrep -f elasticsearch) VM.native_memory summary

# 关键输出:
# Total: reserved=480123MB, committed=450234MB
# - Java Heap: reserved=31744MB, committed=31744MB
# - Class: reserved=1056MB, committed=512MB
# - Thread: reserved=2048MB, committed=2048MB
# - Code: reserved=512MB, committed=256MB
# - Internal: reserved=1024MB, committed=512MB
# - Native Memory: reserved=443739MB, committed=414162MB  <-- 异常!
# - (mmap'd files): reserved=440000MB, committed=410000MB   <-- Lucene 段文件映射

Lucene 的 MMapDirectory 映射了大量段文件到虚拟地址空间。鲲鹏 ARM64 的页表层级与 x86 不同,每个 mmap 区域的页表开销更大。

根因

  • 页表开销:鲲鹏使用 4 级或 5 级页表,大内存映射时页表项数量远超 x86
  • MMap 累积:默认 index.store.preload 预加载了过多段文件
  • Circuit Breaker 失效:堆外内存估算在 ARM 上不够准确,限流未触发

修复

1. 限制 MMap 预加载

# elasticsearch.yml
index.store.preload: ["nvd", "dvd"]  # 仅预加载 norms 和 doc values
indices.memory.index_buffer_size: 10%  # 降低索引缓冲区
indices.queries.cache.size: 5%         # 降低查询缓存

2. 启用内存锁定

# 系统配置
ulimit -l unlimited
echo "vm.swappiness = 1" >> /etc/sysctl.conf

# elasticsearch.yml
bootstrap.memory_lock: true

3. 调整 Circuit Breaker

# elasticsearch.yml
indices.breaker.total.limit: 70%       # 总断路器阈值
indices.breaker.fielddata.limit: 40%   # fielddata 限制
indices.breaker.request.limit: 60%     # 请求断路器

验证

指标修复前修复后
虚拟内存占用520GB85GB
mmap'd files440GB60GB
运行稳定性3 天 OOM30 天+ 稳定
查询 P99 延迟45ms12ms

教训

鲲鹏上跑 ES,MMap 不是免费的。页表开销在 ARM 上更显著,需要严格控制预加载范围,启用 memory_lock,并调整断路器阈值。建议用 jcmd VM.native_memory 定期监控堆外内存增长趋势。

收藏举报
Level 1
0
帖子
0
粉丝
0
获赞