主页 > www.0449aa.com >
Elasticsearch 高并发写入优化的开源协同经历 技术头条
发布日期:2019-09-19 14:42   来源:未知   阅读:

  原标题:Elasticsearch 高并发写入优化的开源协同经历 技术头条

  在腾讯金融科技数据应用部的全民BI项目里,我们每天面对超过10亿级的数据写入,提高es写入性能迫在眉睫,在最近的一次优化中,有幸参与到了Elasticsearch开源社区中。

  为了更便捷地分析数据,腾讯金融科技数据应用部去年推出了全民BI的系统。这个系统通过Elasticsearch进行基础的统计,超过10亿级的数据量需要尽可能快速地导入到es系统中。即使经过多次的参数优化,我们依然需要几个小时才能完成导入,这是系统此前存在的一大瓶颈。

  我们准备了1000万的数据,并在原程序(Spark程序写入)上进行了几轮单机压测,得到了一些基本的性能数据。

  在往下进入深水区之前,我们需要先回顾一下es本身,es本身是基于Lucene基础上设计的分布式搜索系统,在写入方面主要提供了:

  要进行优化,首先得验证一个问题:Lucene的极限速率能到达多少,所以我在我的本机上构建了这样的一个测试。

  Macbook pro 15,6核12线万,每个document 400个字段,10个线程并发(考虑Mac CPU Turbo 4.5G ,服务器2.4G(24核),所以只采用10线分钟,意味着理论上是可行的。那剩下的就看如何接近这个极限。因为那说明一定是es本身的一些默认特性导致了写入速率无法提升。

  下面的介绍忽略了一些相对简单的参数调优,比如关闭docvalues,这个对于非text字段,es默认开启,对于不需要groupby的场景,是不必要的,这个可以减少不少性能。经过初步的参数优化写入耗时降低到了18分钟,这是后面继续往下优化的基础。

  写Lucene前面已经优化过,那么第一步的文档查找其实是在所有分段中进行查找,因为只提供了一个线程进行merge,如果merge不及时,导致分段过的,必然影响文档版本这一块的耗时。

  观察发现,分段的增长速度比预期的快很多。按照默认配置,index_buffer=10%,堆内存31G的情况,按lucene的写分段机制,平均到每个线M,分段产生的速度不应该那么快。而这个问题的根源就是flush_threshold_size默认值只有512M ,这个参数表示在当未提交的translog日志达到该阈值的时候进行一次刷盘操作。

  线程分析这时候就需要进行堆栈分析了,多次取样后,发现了下面的一个频繁出现的现象:

  发现很多线程都停在了获取锁的等待上,而writeLock被rollGeneration占用了。

  而在高flush_threshold_size的配置下,rollGeneration发生了300+次,每次平均耗时560ms,浪费了超过168s,而这个时间里写入线程都只能等待,小分段的优化被这个抵消了。

  因为我们在写入过程中使用的translog持久化策略是async,所以我很自然的想到了把写日志和刷盘异步化。

  一开始的方案则想引入disruptor,消除写线程之间的竞争问题,后面因为es的第三方组件检查禁止使用sun.misc.Unsafe (disruptor无锁机制基于Unsafe实现)而放弃。基于这个方案,测试结果终于出现了跨越:13分钟。

  没想到的是,在社区提交几次优化后,竟然吸引了大佬Simon Willnauer的加入。

  跟社区讨论后,Simon最后建议了一个折中的小技巧,就是在关闭原translog文件之前(writeLock之外),先执行一次刷盘操作。

  这个调整的效果可以让每次rollGeneration操作的耗时从平均570ms降低到280ms,在我的基准测试中(配置flush_threhold_size=30G,该参数仅用于单索引压测设计,不能在生产环境使用),耗时会从18分钟下降到15分钟。

  事实上,这并不是一个非常令人满意的解决方案,这里选择这个方案主要出于两点考虑:

  1. 未来新的版本将考虑不使用Translog进行副分片的recovery,translog的滚动策略会进行调整(具体方案elasitc未透露)

  最后根据讨论的最终结论,我们重新提交了PR,提交了这个改动,并合并到了主干中。

  下面是es写入中的影响关系和调用关系图,从图中可以看到各个因素直接的相互影响。

  最近提交的优化实时上只优化了rollGeneration,而实际上这里还有一些优化空间trimUnreferenceReader,这个也在跟社区沟通中,并需要足够的测试数据证明调整的效果,这个调整还在测试中。