管理 Elasticsearch 内存并进行故障排查
大家好啊!随着 Elastic 扩展我们的 Elasticsearch Service Cloud 产品和自动上线流程,我们已经将 Elastic Stack 的受众范围从运营团队全员扩展到了数据工程师、安全团队和顾问。作为一名 Elastic 支持工程师,我很喜欢与更多具有不同背景、用例甚至更广的用户进行互动。
随着服务对象的增多,我接触到了更多关于管理资源分配方面的问题,特别是晦涩难懂的分片堆比和避免触发熔断器的问题,不免让人无从理解。我也是心有戚戚焉!在开始使用 Elastic Stack 时,我也遇到了同样的问题。这是我第一次介绍如何管理 Java 堆和时序数据库分片,以及如何扩展自己的基础架构。
当我加入 Elastic 团队时,我除了喜欢阅读文档之外,还喜欢看我们的博客和教程,以便让我自己很快上手。但我入职的第一个月过得并不轻松,因为我要将理论知识与用户通过工单接连发送给我的各种错误联系起来。最后,和其他支持工程师一样,我发现所报告的错误中有不少都属于分配问题的症状,通过相同的七个链接便可让用户快速提升成功管理资源分配的能力。
在以下部分中,我将以支持工程师的身份,介绍我们发送给用户的主要分配管理理论链接、我们看到的主要症状,以及我们指引用户去哪里更新自己的配置来解决资源分配问题。
理论
作为一款使用 Java 开发的应用程序,Elasticsearch 需要从系统的物理内存中分配部分逻辑内存(堆)。这部分内存最大不超过物理 RAM 的一半,上限为 32GB。设置较高的堆利用率通常是为了应对耗费资源的查询和较大的数据存储。父级熔断器默认在堆利用率为 95% 时触发,但我们建议在堆利用率持续达到 85% 时,就应该扩展一下资源了。
我强烈建议阅读以下由我们团队撰写的概述性文章,以了解更多信息:
配置
Elasticsearch 开箱即用的默认设置会根据节点角色和内存总量自动调整 JVM 堆的大小。但是,您也可以根据需要通过以下三种方式直接进行配置:
1. 直接在本地 Elasticsearch 文件的 config > jvm.options 文件中
## JVM 配置
################################################################
## 重要说明:JVM 堆大小
################################################################
…
#Xms 表示总堆空间的初始大小
#Xmx 表示总堆空间的最大大小
-Xms4g
-Xmx4g
2. 作为 docker-compose 中的 Elasticsearch 环境变量
version: '2.2'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
environment:
- node.name=es01
- cluster.name=es
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 9200:9200
3. 通过我们的 Elasticsearch Service >“Deployment”(部署)>“Edit”(编辑)视图。注意:滑动条分配的是物理内存,约有一半会分配给堆。
故障排查
如果您的集群当前遇到性能问题,则很可能是以下常见问题所致
- 配置问题:过度分片,无 ILM 策略
- 引入的高数据量:高请求速度/负载,叠加耗费资源的查询/写入
所有以下 cURL/API 请求都可以在 Elasticsearch Service >“API Console”(API 控制台)中作为 Elasticsearch API 的 cURL 发出,或者在 Kibana >“Dev Tools”(开发工具)下进行。
过度分片
数据索引存储在子分片中,这些子分片会在维护以及搜索/写入请求期间使用堆。分片大小的上限应设为 50GB,数量上限应设为通过以下公式确定的值:
shards = sum(nodes.max_heap) * 20
以上面的 Elasticsearch Service 为例,在两个区域中具有 8GB 的物理内存(将总共分配两个节点)
#node.max_heap
8GB of physical memory / 2 = 4GB of heap
#sum(nodes.max_heap)
4GB of heap * 2 nodes = 8GB
#max shards
8GB * 20
160
然后将这个结果与 _cat/allocation 进行交叉比较
GET /cn/_cat/allocation?v=true&h=shards,node
shards node
41 instance-0000000001
41 instance-0000000000
或者以 _cluster/health 作为比较对象
GET /cn/_cluster/health?filter_path=status,*_shards
{
"status": "green",
"unassigned_shards": 0,
"initializing_shards": 0,
"active_primary_shards": 41,
"relocating_shards": 0,
"active_shards": 82,
"delayed_unassigned_shards": 0
}
因此,这个部署有 82 个分片,而建议最多可有 160 个分片。如果计数高于建议值,则您可能会遇到接下来两个部分中介绍的症状(请见下文)。
如果有任何分片报告(active_shards 或 active_primary_shards 除外)数字大于 0,那么您就找到了导致性能问题的主要配置原因。
如果这方面报告了问题,最常见的情况将是 unassigned_shards>0。如果这些分片是主要分片,则集群将报告状态:红色;如果只是副本分片,它将报告状态:黄色。(这就是在索引上设置副本分片的重要之处,这样一来,如果集群遇到问题,它可以恢复数据而避免数据丢失。)
假设我们有一个状态:黄色,带有一个未分配的分片。为了进行调查,我们将通过 _cat/shards 来查看哪个索引分片出现问题
GET _cat/shards?v=true&s=state
index shard prirep state docs store ip node
logs 0 p STARTED 2 10.1kb 10.42.255.40 instance-0000000001
logs 0 r UNASSIGNED
kibana_sample_data_logs 0 p STARTED 14074 10.6mb 10.42.255.40 instance-0000000001
.kibana_1 0 p STARTED 2261 3.8mb 10.42.255.40 instance-0000000001
因此,这将会用到我们的非系统索引日志,因为这个日志有一个未分配的副本分片。让我们运行一下 _cluster/allocation/explain 来看看是什么导致了问题(专业提示:如果您向支持部门上报问题,我们做的就是这个操作)
GET _cluster/allocation/explain?pretty&filter_path=index,node_allocation_decisions.node_name,node_allocation_decisions.deciders.*
{ "index": "logs",
"node_allocation_decisions": [{
"node_name": "instance-0000000005",
"deciders": [{
"decider": "data_tier",
"decision": "NO",
"explanation": "node does not match any index setting [index.routing.allocation.include._tier] tier filters [data_hot]"
}]}]}
这个错误消息指向 data_hot,它是索引生命周期管理 (ILM) 策略的一部分,并指明我们的 ILM 策略与当前的索引设置不一致。在本例中,这个错误的原因是在没有指定热温节点的情况下设置了热温 ILM 策略。(我需要保证让某些操作出现失败,以便为大家强行展示错误示例。看看你们对我都做了些什么😂。)
顺便说一下:如果您在没有任何未分配分片的情况下运行这个命令,则会出现 400 错误,提示找不到任何未分配的分片进行解释,因为没有错误要报告。
如果您遇到非逻辑原因(例如,分配期间节点离开群集等临时网络错误),则可以使用 Elastic 方便好用的 _cluster/reroute。
POST /cn/_cluster/reroute
这个无需定制的请求可启动一个异步后台进程,以尝试分配所有当前状态为 UNASSIGNED 的分片。(千万不要像我一样,不等它完成就联系开发人员;我原以为它瞬间就可以完成,上报时他们正好可以说什么错误都没有,因为什么问题都消失了。)
熔断器
最大限度地增加堆分配可能会导致对集群的请求发生超时或出错,并且会经常导致集群出现熔断器异常。熔断器会导致类似以下的 elasticsearch.log 事件:
Caused by: org.elasticsearch.common.breaker.CircuitBreakingException: [parent] Data too large, data for [] would be [num/numGB], which is larger than the limit of [num/numGB], usages [request=0/0b, fielddata=num/numKB, in_flight_requests=num/numGB, accounting=num/numGB]
要进行调查,请查看您的 heap.percent,方法是查看 _cat/nodes
GET /cn/_cat/nodes?v=true&h=name,node*,heap*
#heap = JVM (logical memory reserved for heap)
#ram = physical memory
name node.role heap.current heap.percent heap.max
tiebreaker-0000000002 mv 119.8mb 23 508mb
instance-0000000001 himrst 1.8gb 48 3.9gb
instance-0000000000 himrst 2.8gb 73 3.9gb
或者,如果您先前已启用它,也可以通过导览至 Kibana >“Stack Monitoring”(堆栈监测)来进行调查。
如果您确认将会触发内存熔断器,则可以考虑临时增加堆容量,为自己腾出空间进行调查。在调查根本原因时,请通过集群代理日志或 elasticsearch.log 查找先前的连续事件。您要寻找的是
- 最耗费资源的查询,尤其是:
- 高存储桶聚合
- 当我发现搜索在运行查询之前会根据搜索大小或存储桶维度临时分配堆的某个端口时,我感到非常傻,所以设置 10,000,000 确实让我的运营团队感到非常沮丧。
- 非优化的映射
- 第二个让我感到愚蠢的原因是,我认为进行分层报告会比扁平化的数据搜索更好(其实不然)。
- 高存储桶聚合
- 请求量/速度:通常是批量或异步查询
进行扩展的时间
如果这不是您第一次触发熔断器,或者您怀疑这会是一个持续存在的问题(例如,堆利用率持续保持在 85%,那么就是考虑扩展资源的时候了);您需要仔细看一下作为长期堆指示器的 JVM 内存压力情况。您可以在 Elasticsearch Service >“Deployment”(部署)中进行查看
或者,您也可以根据 _nodes/stats 信息进行计算,
GET /cn/_nodes/stats?filter_path=nodes.*.jvm.mem.pools.old
{"nodes": { "node_id": { "jvm": { "mem": { "pools": { "old": {
"max_in_bytes": 532676608,
"peak_max_in_bytes": 532676608,
"peak_used_in_bytes": 104465408,
"used_in_bytes": 104465408
}}}}}}}
其中
JVM Memory Pressure = used_in_bytes / max_in_bytes
这种情况的一个潜在症状是:elasticsearch.log 中的垃圾收集器 (gc) 事件发生的频率高且持续时间长
[timestamp_short_interval_from_last][INFO ][o.e.m.j.JvmGcMonitorService] [node_id] [gc][number] overhead, spent [21s] collecting in the last [40s]
如果您确认是这种情况,就需要考虑一下,要么扩展集群,要么减少对集群的需求。您需要调查/考虑:
结论
哇!从我在 Elastic 支持中见到的情况来看,用户提交的工单中最常见问题包括:未分配的分片、不平衡的分片堆、熔断器、大量垃圾收集和分配错误。所有这些都是核心资源分配管理对话的症状。希望您现在也对相关理论和解决步骤有了一些了解。
不过,如果您现在仍无法解决问题,请随时与我们联系。我们将竭诚为您提供帮助!您可以通过 Elastic Discuss、Elastic 社区 Slack、咨询、培训和支持团队与我们联系。
为我们能够作为非运维人员(以及热爱运维的人)自行管理 Elastic Stack 资源分配欢呼!