2. 堆内存被什么数据占用了?
我们对线上集群的堆内存分布情况做统计分析后,发现绝大部分堆内存主要被 FST( Finite State Transducer )占用了:
- FST 内存占用量占分堆内存总量的 50% ~ 70%
- FST 与 磁盘数据量成正比: 10TB 磁盘数据量,其对应的 FST 内存占用量在 10GB ~ 15GB 左右。
因此,我们的目标就是就是通过内核层的优化,降低 FST 的堆内存占用量。
方案:降低 FST 堆内存占用量
什么是 FST ?
在介绍具体的方案前,先来了解下 FST 到底是什么。
如上图所示,ES 底层存储采用 Lucene(搜索引擎),写入时会根据原始数据的内容,分词,然后生成倒排索引。查询时,先通过查询倒排索引找到数据地址(DocID)),再读取原始数据(行存数据、列存数据)。但由于 Lucene 会为原始数据中的每个词都生成倒排索引,数据量较大。所以倒排索引对应的倒排表被存放在磁盘上。这样如果每次查询都直接读取磁盘上的倒排表,再查询目标关键词,会有很多次磁盘 IO,严重影响查询性能。为了解磁盘 IO 问题,Lucene 引入排索引的二级索引 FST [Finite State Transducer] 。原理上可以理解为前缀树,加速查询。
其原理如下:
-
将原本的分词表,拆分成多个 Block ,每个 Block 会包含 25 ~ 48 个词(Term)。图中做了简单示意,Allen 和 After 组成一个 Block 。
-
将每个 Block 中所有词的公共前缀抽取出来,如 Allen 和 After 的公共前缀是 A 。
-
将各个 Block 的公共前缀,按照类似前缀树的逻辑组合成 FST,其叶子节点携带对应 Block 的首地址 。(实际 FST 结构更为复杂,前缀后缀都有压缩,来降低内存占用量)
-
为了加速查询,FST 永驻堆内内存,无法被 GC 回收。
-
用户查询时,先通过关键词(Term)查询内存中的 FST ,找到该 Term 对应的 Block 首地址。再读磁盘上的分词表,将该 Block 加载到内存,遍历该 Block ,查找到目标 Term 对应的 DocID。再按照一定的排序规则,生成 DocID 的优先级队列,再按该队列的顺序读取磁盘中的原始数据(行存或列存)。
由此可知,FST 常驻堆内内存,无法被 GC 回收 , 长期占用 50% ~ 70% 的堆内存 !