查询 Elasticsearch 进程 pid
ps -ef|grep elastic
获取二进制 heap dump 文件
jmap -dump:format=b,file=/root/es_heap.bin <pid>
以M为单位显示文件大小
ls -l --block-size=M
压缩 dump 文件大小
tar zcf your-file-name.tar.gz your-file-namec
开启文件下载端口 ,访问端口下载文件
python -m SimpleHTTPServer 8178
这里给出几个 JVM 堆内存分析软件
JProfiler 商业级分析工具,可试用,收费 https://www.ej-technologies.com/index.html
YourKit 商业级分析工具,可试用,收费 https://www.yourkit.com/java/profiler/download/
VisualVM 免费 JDK 8 及之前自带,之后版本需要单独下载 http://visualvm.github.io/
打开单个快照,选择下载好的 dump 文件。
https://www.ej-technologies.com/resources/jprofiler/v/12.0/help_zh_CN/doc/heapWalker/memoryLeaks.html
内存泄漏排查
5859MB byte[]
GC root static DEFAULT of class io.netty.buffer.PooledByteBufAllocator (0x2120)
stap1: 帮助文档 https://www.yourkit.com/docs/java/help/
可以看到当前堆内存中有 5.7G 的 int 类型占用 且 无 可达 GC roots 。
关于 Shallow Size 和 Retained Size https://www.yourkit.com/docs/java/help/sizes.jsp
这部分很奇怪,实际在 full gc 时也没有回收掉,且在某些时刻 int[] 会暴涨直至 OOM。
83% 的 int[] 占用全部内存 5.7G 的 99%。无可达 GC roots
<Objects unreachable from GC roots> 240451 5722290584
stap2: 这边线索断了,于是考虑从版本角度到官网中找是否有相关的 issues 和版本的修复。
可能有关的 bug issues
这个是 Slow loggers 导致的 Log4j loggers 会随时间内存泄漏。该问题出现于 创建新索引和删除索引后。反复创建和删除索引应该可以复现该问题。
Slow loggers can cause Log4j loggers to leak over time. When a new index is created, a new Log4j logger is associated with it. However, when an index is deleted, Log4j keeps an internal reference to its loggers that results in a memory leak (issue: #56171)
This issue is fixed in Elasticsearch 6.8.10 and 7.7.1.
这里考虑到使用场景并没有频繁创建删除索引,应该不是该问题所导致。
7.6.0 Fix memory leak in DLS bitset cache #50635 (issue: #49261)
Elasticsearch 各版本的改动和问题修复 :
https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-7.6.0.html
stap3: 在搜寻了较长一段时间的官网文档和 issues 后我意识到,这个分析的方向是错误的,又走了岔路。
从新回到问题本身。 这 5.7G 的 int[] 在创建之初一定是有 GC roots 的,那么问题变为,创建他们的是谁?一个很简单但也很难搞的问题……
stap4: 5.7G 的 int[] 究竟是谁创建了他们??
这里尝试在测试环境复现一下,看看是否可以进行定位。
查看当前堆内存使用情况并记录 导出当前堆
jmap -dump:format=b,file=/root/es_heap_before_full_gc.bin
jmap -histo:live
手动进行一次 full gc 导出当前堆 dump
jmap -dump:format=b,file=/root/es_heap_after_full_gc.bin
压缩 dump 文件并下载分析
前后对比 gc 后的 int[] 比 gc 前的多,推测 gc 后可能会产生少量 int[] 数组
stap5:
查看 ES 相关的查询日志
晕答案有时候没有想象的那么复杂 , 长文本在搜索没有限制, 分词器 standard ,导致查询时瞬间 load 大量 倒排索引到内存 以及对应的聚合 产生大量 int[],同时疯狂 GC 。
如搜索:
如果数学不是体育老师教的话,还是比较容易看出来这种压缩技巧的。
未压缩之前,6个整数,每个整数用4个字节压缩,一共需要24个字节;通过将数据增量编码,将73,300,302...,变成73, 227, 2,没有丢掉数据信息的情况下,将数据减小。标识位8,本身需要一个字节空间,表示73,227,2每个数都用8bit = 1byte来存储,需要1byte * 3 = 3bytes内存空间来存储,标识位5,本身需要1个字节空间存储,表示30,11,29都用5bit来存储,5bit * 3 = 15bit,但是需要16bit = 2bytes来存储,那么8,73,227,2,5,30,11,29,一共需要1 + 3 + 1 + 2 = 7bytes空间即可。
原理就是通过增量,将原来的大数变成小数仅存储增量值,再精打细算按bit排好队,最后通过字节存储,而不是大大咧咧的尽管是2也是用int(4个字节)来存储。
Roaring bitmaps
说到Roaring bitmaps,就必须先从bitmap说起。Bitmap是一种数据结构,假设有某个posting list:
对应的bitmap就是:
非常直观,用0/1表示某个值是否存在,比如10这个值就对应第10位,对应的bit值是1,这样用一个字节就可以代表8个文档id,旧版本(5.0之前)的Lucene就是用这样的方式来压缩的,但这样的压缩方式仍然不够高效,如果有1亿个文档,那么需要12.5MB的存储空间,这仅仅是对应一个索引字段(我们往往会有很多个索引字段)。于是有人想出了Roaring bitmaps这样更高效的数据结构。
Bitmap的缺点是存储空间随着文档个数线性增长,Roaring bitmaps需要打破这个魔咒就一定要用到某些指数特性:
将posting list按照65535为界限分块,比如第一块所包含的文档id范围在0~65535之间,第二块的id范围是65536~131071,以此类推。再用<商,余数>的组合表示每一组id,这样每组里的id范围都在0~65535内了,剩下的就好办了,既然每组id不会变得无限大,那么我们就可以通过最有效的方式对这里的id存储。
32 核 64G 的机器 集群 3 台瞬间打挂……
有时候问题就是这么明显且尽在眼前。排查问题一定要找准找对方向。单点突破。
希望本篇文章对你工作和学习有所帮助。
我是 dying 搁浅 我看了一场 99 小时的电影也没能等来你的点赞、关注、收藏,我想这不是你不够喜欢我,而是我看的电影不够长……