JVM 内存使用率过高会降低集群性能,并可能触发熔断器错误。为避免这些问题,如果节点的 JVM 内存使用率持续超过 85%,建议采取措施降低内存压力。
诊断 JVM 内存压力过高
检查 JVM 内存压力
Elasticsearch 的 JVM 使用 G1GC 垃圾回收器。因此,堆使用量会随时间呈现锯齿状变化,详见 理解 JVM 堆内存。报告的堆百分比是瞬时测量值,会随回收活动波动。监控时应重点关注 JVM 内存压力,它基于老年代垃圾回收的滚动平均值,更能反映节点持续的 JVM 响应能力。
使用节点统计 API 计算每个节点当前的 JVM 内存压力:
GET _nodes/stats?filter_path=nodes.*.name,nodes.*.jvm.mem.pools.old
根据输出,将 used_in_bytes 除以 max_in_bytes,即可得到内存压力。例如,将输出保存为 nodes_stats.json,再使用第三方 JQ 工具处理:
cat nodes_stats.json | jq -rc '.nodes[]|.name as $n|.jvm.mem.pools.old|{name:$n, memory_pressure:(100*.used_in_bytes/.max_in_bytes|round) }'
Elastic Cloud Hosted 和 Elastic Cloud Enterprise 的部署概览页面,也为集群中的每个节点提供 JVM 内存压力指示器。当内存压力达到 75% 时,指示器会变红。更多信息见 内存压力监控。
检查垃圾回收日志
内存使用量越高,垃圾回收通常越频繁、耗时越长。可以在 elasticsearch.log 中跟踪垃圾回收事件的频率与持续时间。例如,下面的事件表明,Elasticsearch 在最近 40 秒中有 21 秒用于垃圾回收,占比超过 50%。
[timestamp_short_interval_from_last][INFO ][o.e.m.j.JvmGcMonitorService] [node_id] [gc][number] overhead, spent [21s] collecting in the last [40s]
垃圾回收活动也可能出现在 nodes hot threads API 输出的 OTHER_CPU 类别中,相关说明见 排查高 CPU 使用率。
为了达到理想的 JVM 性能,垃圾回收应满足:
| GC 类型 | 完成时间 | 频率 |
|---|---|---|
| 年轻代 GC | 小于 50 毫秒 | 约每 10 秒一次 |
| 老年代 GC | 小于 1 秒 | 每 10 分钟不超过一次 |
捕获 JVM 堆转储
要确定高 JVM 内存压力的确切原因,可在内存使用量高时捕获并分析 JVM 堆转储。
如果有 Elastic 订阅,可以请求 Elastic 协助分析。联系支持时,请遵循以下要求:
- 在支持工单中书面授权 Elastic 查看上传的堆转储。
- 转储可能包含私密信息,必须取得所需的业务审批后才能分享。文件按照 Elastic 的隐私声明处理。
- 通过安全的 Support Portal 分享。文件过大无法上传时,可以在工单中请求安全上传 URL。
- 同时提供覆盖相同时间段的垃圾回收日志。
监控 JVM 内存压力
使用 AutoOps 检测和解决问题:AutoOps 实时检测 Elasticsearch 集群的问题,并提供解决办法与性能优化建议。详见 AutoOps 介绍。
根据部署类型,选择以下方式持续跟踪 JVM 内存压力。
对于提供部署日志、指标与性能页面的云部署:
- 推荐启用 AutoOps。
- 启用日志和指标。之后可在 Kibana 的“Stack Monitoring”页面查看监控信息,也可启用 JVM 内存阈值告警,通过电子邮件获知潜在问题。
- 在部署菜单中打开“Performance”页面,查看内存压力故障排查图表。
对于使用 Elasticsearch 监控的部署:
- 推荐启用 AutoOps。
- 启用 Elasticsearch 监控。日志和指标启用后,在 Kibana 的“Stack Monitoring”页面查看信息,也可以启用 JVM 内存阈值邮件告警。
降低 JVM 内存压力
下面列出一些常见的缓解建议。
常见配置问题
有些配置问题即使在没有明显负载时,也会让内存压力持续偏高,或者在性能问题出现时产生非线性反应。
禁用交换
Elasticsearch 的 JVM 自行管理可执行内容,操作系统交换可能使性能严重下降,因此建议禁用 swap。
Elasticsearch 建议在操作系统层面完全禁用交换。Elasticsearch 层面的设置只能尽力避免交换,但交换一旦发生就可能严重影响性能。通过节点统计 API 查看是否有节点正在交换:
GET _nodes/stats?filter_path=**.swap,nodes.*.name
例如,将输出保存为 nodes_stats.json,再用 JQ 处理:
cat nodes_stats.json | jq -rc '.nodes[]|{name:.name, swap_used:.os.swap.used_in_bytes}' | sort
如果在 Elasticsearch 层面尝试禁用后,仍发现节点发生交换,就需要进一步在操作系统层面禁用,以避免性能影响。
启用压缩普通对象指针
JVM 性能很大程度上依赖压缩普通对象指针(Compressed OOPs)是否启用。可以使用压缩指针的最大堆阈值取决于操作系统,通常约为 30GB。使用节点信息 API 查看:
GET _nodes?filter_path=nodes.*.name,nodes.*.jvm.using_compressed_ordinary_object_pointers
例如,将输出保存为 nodes.json,再用 JQ 处理:
cat nodes.json | jq -rc '.nodes[]|{node:.name, compressed:.jvm.using_compressed_ordinary_object_pointers}'
将堆限制在总内存的一半以内
Elasticsearch 默认自行管理 JVM 堆大小。如果手动覆盖,Xms 与 Xmx 应相等,并且不超过操作系统总 RAM 的一半。详细建议见 设置 JVM 堆大小。
通过节点信息 API 查看堆设置:
GET _nodes?filter_path=nodes.*.name,nodes.*.jvm.mem
例如,将输出保存为 nodes.json,再用 JQ 处理:
cat nodes.json | jq -rc '.nodes[]|.name as $n|.jvm.mem|{name:$n, heap_min:.heap_init, heap_max:.heap_max}'
减少分片数量
每个分片都消耗内存。通常,少量大分片比大量小分片使用更少资源。减少分片数量的建议见 规划分片大小。
常见流量问题
以下建议针对流量模式引起的 JVM 内存压力。
避免高成本搜索
高成本搜索可能消耗大量内存。启用慢日志可以更好地跟踪集群中的此类搜索。
高成本搜索可能具有很大的 size 参数、包含大量桶的聚合,或者使用昂贵的查询。可以考虑:
- 通过索引设置
index.max_result_window降低size上限。 - 通过集群设置
search.max_buckets减少允许的聚合桶数量。 - 通过
search.allow_expensive_queries禁用高成本查询。 - 通过
search.default_search_timeout设置默认搜索超时。
PUT _settings
{
"index.max_result_window": 5000
}
PUT _cluster/settings
{
"persistent": {
"search.max_buckets": 20000,
"search.allow_expensive_queries": false,
"search.default_search_timeout": "1m"
}
}
提示:高效编写 ES|QL 查询的专门建议,见 优化 ES|QL 查询性能。
防止映射膨胀
定义过多字段,或字段嵌套过深,可能导致消耗大量内存的映射膨胀。使用映射限制设置,约束字段映射数量。
还可以配置 Kibana 高级设置 data_views:fields_excluded_data_tiers,阻止 Kibana 从指定数据层获取字段数据,以改善性能。例如,要排除通常用于可搜索快照的 cold 与 frozen 层,可设置为 data_cold,data_frozen。这样有助于 Discover 更快载入字段,详见 Kibana Discover 加载的六个常见问题排查指南。
分散批量请求
批量索引和多搜索请求虽然比单独请求更高效,但过大的批量请求仍会造成高 JVM 内存压力。如果可以,提交较小的请求,并拉长请求之间的间隔。
增加节点内存
高强度索引与搜索负载可能造成高 JVM 内存压力。升级节点、增加内存容量,可以更好地承受繁重工作负载。
减少字段数据使用
字段数据与全局序数的计算可能消耗大量 CPU。默认情况下,全局序数在搜索时计算,也可以启用预加载,让它在摄入后计算。
字段数据载入 JVM 堆缓存,并根据使用频率保留。它能占用的堆内存上限,是字段数据缓存设置与字段数据熔断器限制中的较小者。熔断器错误会表现为请求被拒绝。将 indices.fielddata.cache.size 设得过低,会造成抖动与频繁驱逐。
使用 cat nodes API 查看字段数据驱逐情况,并确定字段数据是否占用了大量 JVM 内存:
GET _cat/nodes?v=true&h=name,heap.*,fielddata.*
如果输出表明字段数据是主要内存消耗来源,使用 cat fielddata API 确定哪些字段使用了字段数据,以及各节点的使用量:
GET _cat/fielddata?v=true&s=size:desc
可以使用 clear cache API 清除字段数据缓存,临时降低内存使用量。例如,仅清除 my-index-000001 中 fieldname1 与 fieldname2 的缓存:
POST my-index-000001/_cache/clear?fielddata=true&fields=fieldname1,fieldname2
清除所有索引的字段数据缓存:
POST */_cache/clear?fielddata=true&expand_wildcards=all&ignore_unavailable=true
字段数据内存使用量高的常见原因包括:
- 在 text 字段上启用
fielddata。应改用多字段,并针对 keyword 字段搜索。 - 对已经计算全局序数的高基数字段进行聚合或排序。例如,Kibana 为非 text 字段提供自动补全或加载可视化时可能发生。相关建议见 避免全局序数加载。
原文:High JVM memory pressure。作者/维护者:Elastic 文档团队。本文为原文的中文译文;代码保留原文内容。











暂无评论内容