腾讯万亿级 Elasticsearch 内存效率提升技术解密

267 阅读2分钟

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] 。原理上可以理解为前缀树,加速查询。

其原理如下:

  1. 将原本的分词表,拆分成多个 Block ,每个 Block 会包含 25 ~ 48 个词(Term)。图中做了简单示意,Allen 和 After 组成一个 Block 。

  2. 将每个 Block 中所有词的公共前缀抽取出来,如 Allen 和 After 的公共前缀是 A 。

  3. 将各个 Block 的公共前缀,按照类似前缀树的逻辑组合成 FST,其叶子节点携带对应 Block 的首地址 。(实际 FST 结构更为复杂,前缀后缀都有压缩,来降低内存占用量)

  4. 为了加速查询,FST 永驻堆内内存,无法被 GC 回收。

  5. 用户查询时,先通过关键词(Term)查询内存中的 FST ,找到该 Term 对应的 Block 首地址。再读磁盘上的分词表,将该 Block 加载到内存,遍历该 Block ,查找到目标 Term 对应的 DocID。再按照一定的排序规则,生成 DocID 的优先级队列,再按该队列的顺序读取磁盘中的原始数据(行存或列存)。

由此可知,FST 常驻堆内内存,无法被 GC 回收 , 长期占用 50% ~ 70% 的堆内存 !