问题:
某天kibana无法正常使用,查看es容器log,发现GC无法正常回收JVM内存
{"type": "server", "timestamp": "2021-05-14T02:16:17,936Z", "level": "INFO", "component": "o.e.i.b.HierarchyCircuitBreakerService", "cluster.name": "docker-cluster", "node.name": "es0", "message": "GC did bring memory usage down, before [23301315824], after [23252356736], allocations [1], duration [20]", "cluster.uuid": "Bs2Sbp4wQEiPmaKiJmtsfg", "node.id": "4Jop7UNfQgOYJ8-b-_tXCA" }
解决办法:
优化ES内存配置
1.配置断路器
- indices.breaker.fielddata.limit
fielddata 断路器默认设置堆的 60% 作为 fielddata 大小的上限。
PUT /_cluster/settings { "persistent": { "indices.breaker.fielddata.limit": "60%" } }
- indices.breaker.request.limit
request 断路器估算需要完成其他请求部分的结构大小,例如创建一个聚合桶,默认限制是堆内存的 40%。
PUT /_cluster/settings { "persistent": { "indices.breaker.request.limit": "40%" } }
- indices.breaker.total.limit
total 揉合 request 和 fielddata 断路器保证两者组合起来不会使用超过堆内存的 70%。
PUT /_cluster/settings { "persistent": { "indices.breaker.total.limit": "70%" } }
当前fieldData缓存区大小 < indices.fielddata.cache.size
当前fieldData缓存区大小 + 下一个查询加载进来的fieldData < indices.breaker.fielddata.limit
indices.breaker.request.limit + indices.breaker.fielddata.limit < indices.breaker.total.limit
- indices.fielddata.cache.size
缓存回收大小,无默认值, 有了这个设置,最久未使用(LRU)的 fielddata 会被回收为新数据腾出空间;这个参数无法动态配置,需要修改elasticsearch.yml文件,并且重启节点
indices.fielddata.cache.size: 40%
ES相关文档说明:
字段数据
字段数据(fielddata),在 Lucene 中又叫 uninverted index。我们都知道,搜索引擎会使用倒排索引(inverted index)来映射单词到文档的 ID 号。而同时,为了提供对文档内容的聚合,Lucene 还可以在运行时将每个字段的单词以字典序排成另一个 uninverted index,可以大大加速计算性能。
作为一个加速性能的方式,fielddata 当然是被全部加载在内存的时候最为有效。这也是 ES 默认的运行设置。但是,内存是有限的,所以 ES 同时也需要提供对 fielddata 内存的限额方式:
- indices.fielddata.cache.size 节点用于 fielddata 的最大内存,如果 fielddata 达到该阈值,就会把旧数据交换出去。该参数可以设置百分比或者绝对值。默认设置是不限制,所以强烈建议设置该值,比如
10%
。 - indices.fielddata.cache.expire 进入 fielddata 内存中的数据多久自动过期。注意,因为 ES 的 fielddata 本身是一种数据结构,而不是简单的缓存,所以过期删除 fielddata 是一个非常消耗资源的操作。ES 官方在文档中特意说明,这个参数绝对绝对不要设置!
Circuit Breaker
Elasticsearch 在 total,fielddata,request 三个层面上都设计有 circuit breaker 以保护进程不至于发生 OOM 事件。在 fielddata 层面,其设置为:
- indices.breaker.fielddata.limit 默认是 JVM 堆内存大小的 60%。注意,为了让设置正常发挥作用,如果之前设置过
indices.fielddata.cache.size
的,一定要确保indices.breaker.fielddata.limit
的值大于indices.fielddata.cache.size
的值。否则的话,fielddata 大小一到 limit 阈值就报错,就永远道不了 size 阈值,无法触发对旧数据的交换任务了。
ES内存分配规划
类目 | 默认占比 | 是否常驻 | 淘汰策略(在控制大小情况下) | 控制参数 |
---|---|---|---|---|
query cache | 10% | 是 | LRU | indices.queries.cache.size |
request cache | 1% | 是 | LRU | indices.requests.cache.size |
fielddata cache | 无限制 | 是 | LRU | indices.fielddata.cache.size |
segment memory | 无限制 | 是 | 无 | 不能通过参数控制 |
common space | 70% | 否 | GC | 通过熔断器 indices.breaker.total.limit 限制 |