从一次排查 ES 线上问题得出的总结——熔断机制
线上问题概述
节点频繁退出集群
问题复现:线上集群本身有正在recovery的分片,同时存在force merge行为。两者并发对内存压力很大,导致报CircuitBreakingException异常:
大致意思是transport request的请求过大,导致内存实际使用量超过了最大限制。从ChildMemoryCircuitBreaker这个类中找到这块异常代码:
看方法的注释是说此方法用于给parent(此处的parent指parent task)上的circuitBreaker决定是否触发breaker,也就是熔断。而从这个方法往上追溯,是以下方法在调用:
注意到parent.checkParentLimit((long) (bytes * overheadConstant), label);这一行可以看出是HierarchyCircuitBreakerService调用了checkParentLimit,HierarchyCircuitBreakerService类中可以看到很多关于索引级别的熔断参数,
其中分为fieldData,requests,accounting_requests,inflight_requests的限制。
checkParentLimit,在这里抛出了问题中的异常。
至于为什么节点会频繁退出集群,是因为ES集群会在请求中一直有异常的节点进行心跳检测,如果多个请求都频繁抛出异常,则将该节点标记为dead,从master node list中删除。这样真正的master就会检测不到该node,从而将其从集群中真正下线。待内存恢复低负载后才重新加入。
背景介绍
受CircuitBreaker real-memory limit影响,recoveries不能正常进行且节点频繁退出集群。临时将indices.breaker.total.use_real_memory设置为false并重启集群解决。在官方的release note可以看出该参数是V7.0发布的时候带上的:https://www.elastic.co/guide/en/elasticsearch/reference/7.0/breaking-changes-7.0.html
问题的详细解释:
从以下几个Github上的issue可以看出和circuitBreaker相关的解释和开发过程:
https://github.com/elastic/elasticsearch/issues/44484
https://github.com/elastic/elasticsearch/pull/55566
https://github.com/elastic/elasticsearch/issues/56327
https://github.com/elastic/elasticsearch/pull/55353
原因分析
主要原因在于indices.breaker.total.use_real_memory默认true,这个在7.x之前版本没有这个参数,而由于G1GC的特性内存使用率提升导致往往会超过breaker的limit,从而触发熔断导致节点断开连接。另外ES有着自动扩展的内存大小也会长时间持有额外的内存。
比较彻底得解决方式是关闭real_memory,如果不关闭real_memory计算的话需要调整G1GC的参数(但是可能解决得不彻底,一些case还会报):
7.6.2已经在release中加入这两行参数,不过仅仅jdk14以上才强制要求
参考:
https://discuss.elastic.co/t/circuitbreakingexception-parent-data-too-large-in-es-7-x/192801/7
https://github.com/elastic/elasticsearch/pull/46169
解决思路
We have already done some enhancements for high-pressure search/write caused nodes overload, if the overloaded nodes' parent breaker has been triggered, we clean related memory and fail the request as soon as possible, this would make nodes always could be garbage cleaned instead of get stuck and disconnect from cluster finally.
可以理解为导致节点超过负荷的search或者write请求已经做了一些优化,但是一旦过载的节点的parent breaker被触发,将会清除相应内存占用并对该请求采用fail fast,这将导致节点的GC而不是假死或是从集群中失联。
分别从cancel request或者retry request入手,解决recoveries报error问题
可以看到社区中member提交的代码改动:
server/src/main/java/org/elasticsearch/tasks/TaskManager.java
先检测channel中的pending task:
然后进行原子操作后将pending task放入tracker中:
最后取消事件注册:
思路很清晰。
另外论坛中也有member提出如果内存压力持续过大,可以考虑拆分集群,这也是常见的解决线上集群负载过重的一个比较好的途径。
延展
从改进CircuitBreakerService入手:https://github.com/elastic/elasticsearch/pull/55695
ZGC解决这个问题的可能性以及优劣势分析(下一篇详细介绍)
版权声明: 本文为 InfoQ 作者【罗琦】的原创文章。
原文链接:【http://xie.infoq.cn/article/830fb57ff0c4794b5a7e008b3】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论