Elasticsearch( ES )是一款功能强大的开源分布式实时搜索引擎,在日志分析(主要应用场景)、企业级搜索、时序分析等领域有广泛应用,几乎是各大公司搜索分析引擎的开源首选方案。
Tencent ES 是内核级深度优化的 ES 分支,持续地进行高可用、高性能、低成本等全方位优化,已支撑的单集群规模达到千级节点、万亿级吞吐。Tencent ES 已在公司内部开源,同时也积极贡献开源社区,截止目前已向社区提交 PR 25+。
腾讯联合 Elastic 官方在腾讯云上提供了内核增强版 ES 云服务,支撑公司内部云、外部云、专有云达 60PB+ 的数据存储,服务 蘑菇街、知乎、B 站、凤凰网等业内头部客户。
本文主要介绍 Tencent ES 的主要优化点之一:零拷贝 内存 Off Heap,提升内存使用效率,降低存储成本。最终达到,在读写性能与源生逻辑一致的前提下,堆内存使用率降低 80%,单节点存储量从 5TB 提升至 50TB 的效果。
上节提到,日志分析是 ES 的主要应用场景(占比60%),而日志数据的特点显著:
我们对线上售卖的集群做硬件成本分析后,发现成本主要在磁盘和内存。
为了降低磁盘成本,我们采取冷热分离、Rollup、备份归档、数据裁剪等多种方式降成本。在冷热分离的集群,我们通过大容量的冷存储机型,来存储历史数据,使得磁盘成本下降 60% 左右。
问题也随之而来:如上图所示,大容量的冷机型,存在磁盘使用率过低的问题( 40 % 以下),原因是堆内存使用率过高了( 70 % 左右),制约磁盘使用率无法提升。(其中单节点磁盘使用率 40%,约 13TB 左右,这已经是 Tencent ES 优化后的效果,源生只能支持到5 TB 左右)
所以,为了提升低成本的冷机型磁盘使用率,同时也为了降低内存成本,我们需要降低 ES 的堆内存使用率。
ES 是通过 JAVA 语言编写的,在介绍如何降低堆内存使用率之前,先了解下 JAVA 的堆内存:
所以,简单的运营侧策略调整无法解决堆内存使用率过高的问题。那么我们就需要确认 ES 的堆内存是被什么数据占用了,能否优化。
我们对线上集群的堆内存分布情况做统计分析后,发现绝大部分堆内存主要被 FST( Finite State Transducer )占用了:
因此,我们的目标就是就是通过内核层的优化,降低 FST 的堆内存占用量。
在介绍具体的方案前,先来了解下 FST 到底是什么。
如上图所示,ES 底层存储采用 Lucene(搜索引擎),写入时会根据原始数据的内容,分词,然后生成倒排索引。
查询时,先通过查询倒排索引找到数据地址(DocID)),再读取原始数据(行存数据、列存数据)。
但由于 Lucene 会为原始数据中的每个词都生成倒排索引,数据量较大。所以倒排索引对应的倒排表被存放在磁盘上。这样如果每次查询都直接读取磁盘上的倒排表,再查询目标关键词,会有很多次磁盘 IO,严重影响查询性能。
为了解磁盘 IO 问题,Lucene 引入排索引的二级索引 FST Finite State Transducer 。原理上可以理解为前缀树,加速查询。
其原理如下:
由此可知,FST常驻堆内内存,无法被 GC 回收 , 长期占用 50% ~ 70% 的堆内存 !
既然 FST 是常驻堆内内存,导致堆内存使用率过高,那么解决问题的思路有两种:
在 Tencent ES 成立前期,我们采用过这种方案。具体的做法是,将 FST 对应的 Block 大小,从 25 ~ 48,放大一倍至 49 ~ 96 。这样,在 关键词 Term 数相同的情况下,Block 数量降低了一倍,对应的 FST 内存理论上也会下降一倍。
由此我们可以看出,简单的降低 FST 的堆内存使用量,并不是一个普适性的方案,需要更为通用、彻底限制住 FST 总大小的方案。
将 FST 从堆内存(OnHeap)移到堆外内存(OffHeap),几乎可以完全释放 FST 在堆内存占据的使用空间,这也是 JAVA 实践方向上一个普遍使用的方案。对于 JAVA 的堆内存不足,将部分内存移到堆外内存(OffHeap)的问题,ES 社区 和 其他 JAVA 系产品都有相应的解决方案。
1.ES 社区方案:
该方案是将 FST 从堆内存中剔除, 直接交由 MMAP 管理。FST 在磁盘上也是有对应的持久化文件的,Lucene 的 .tip 文件,该方案每次查询时直接通过 MMap 读取 .tip 文件,通过文件系统缓存 FST 数据。
2.HBase 方案
HBase的方案是,在堆外搭建一个Cache,将其一部分堆内存(Bucket Cache,Data Block 缓存)移到堆外,释放堆内内存。
3.Tencent ES 方案
我们的方案总体上接近HBase的方案,相比之下:
下面通过将由浅入深地向大家介绍我们实现 FST OffHeap 的过程,及其中碰见的问题和解决方案。
在实现 OffHeap 方案的初期,我们的架构如上图所示。
先来看下源生逻辑是怎样访问 FST 的:
再来看下优化后的 Tencent ES 是怎样访问 FST 的:
将两种方案做个对比,如下表所示:
那么可以总结出 Tencent ES 优化后的 FST 访问逻辑的优势和劣势:
所以,我们要对 OffHeap LRU Cache 的读写路径做优化,减少 Copy 次数,提升读写性能。
具体的实现方案是全链路零拷贝 OffHeap FST 访问逻辑。
ES 源生逻辑访问 FST 只支持堆内的操作,怎样做到让它能直接访问堆外的数据呢?
为此,我们做了两方面优化:
经过上述优化,把 FST 的数据访问由 1 次 Copy 优化为 0 Copy,实现了全链路零拷贝 OffHeap FST 访问逻辑。同时也将 FST 的数据写入从 2 次 Copy 优化为 1 次 Copy。读写性能损耗从 20%+ 下降至 7%。
虽然这样性能影响已经比较小了,但我们还是想挑战下自己,能否将性能优化到极致呢?
要进一步优化性能,需要搞清楚一个问题:7% 的性能损耗在哪里?
Perf分析后发现,Hot 堆栈是 OffHeap Cache 计算Hash、校验 Key 等逻辑。为什么会有频繁读 Cache 的操作呢?我们分析 Lucene 的源码发现,在高并发读写时,一次读写入上千条数据,则会有读 Cache 上千次。例如,一个 bulk update 写入 3000条数据,3 分片,每个分片大概有 1000 条数据 update 操作,那么就有 1000 次读 Cache 的逻辑。而这 1000 次读 Cache,基本上是读的同一个 Key (_id 对应的 Key),能否做到让这 1000 次查询的 Key,稍微缓存一会,防止那么多次读 Cache 的操作呢?
我们的优化方案是:OnHeap + OffHeap 的两级 Cache 架构,降低 OffHeap Cache 访问频率。而堆内的 Cache 一定要轻量,最少的占用 OnHeap 内存,否则就违背了我们要将 FST 从 OnHeap 移出去的初衷。所以,我们最终选用堆内的弱引用机制( WeakRefrence )来缓存 OffHeap FST 的指针,作为 OnHeap 的轻量级 Cache,利用 JVM 的 GC 自动释放无效的弱引用,同时堆外内存。
相比于直接设置一个 OnHeap Cache,弱引用有占用内存小,避免拷贝等优势。
这样我们访问 FST 的逻辑,会先查询堆内的各个查询共享的 WeakRefrence,当其已经被释放时,才会访问 OffHeap Cache。这样就大大降低了 OffHeap Cache 的访问频率。
这里的挑战是,两级Cache,key的关联释放问题。JVM GC时会销毁 WeakRefrence 对应的 OnHeap 对象,但 Java 无析构函数,无法自动释放堆外指针。而我们期望,在堆内的 WeakRefrence 释放时,同时释放堆外指针,从而对 OffHeap Cache 的 Key 的引用计数减一,使其可以根据 LRU 规则自动回收无效的 FST 数据。
深入分析 JAVA 垃圾回收、弱引用机制后,发现可以通过注册一个 WeakRefrence Queue,在 WeakRefrence 释放前,可以捕获到它。进而改造 WeakRefrence的数据结构,使其在被捕获后,对 OffHeap Cahe 的 Key 引用计数减一,然后才被回收。
经过上述 OnHeap WeakRefrence + OffHeap LRU Cache 两级 Cache 架构的优化后,高并发读写性能基本与源生逻辑持平。
除了上述的性能优化外,Tencent ES 的 FST OffHeap 还做了一些其他优化:
最后来看下我们层层优化后,最终的效果:
通过 ES 官方 Benchmark 工具 ES Rally 压测,结果如下:
根据线上集群的实际运行效果看,当开启OffHeap功能后,集群整体平均JVM的内存使用率从 70%+ 下降 至 30% 左右。
Tencent ES 将持续地进行高可用、高性能、低成本等全方位优化:可用性方面,将提升 ES 的故障自愈能力、故障自动分析诊断,达到零接触运维的目标;高性能方面,将进一步提升 ES 的海量数据实时分析能力;低成本方面,将提供存储与计算分离的能力,基于腾讯自研的共享文件系统 CFS,进一步缩减成本。
最后,欢迎各位对 ES 内核技术有兴趣的同学扫描下方的二维码与我们展开交流,同时也欢迎大家在腾讯云体验 CES 云服务。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。