ElasticSearch 批量导入的优化实践_w1346561235 的博客 - CSDN 博客

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

最近在做 es 跨集群批量导入的优化工作,将自己 get 到的点总结一下。

跨集群导入有三种方法:

1,应用编程,从源集群读取,导入目标集群;这种方法的优点在于,整个导入过程的各个环节是自己编码控制的,每个环节都能做到可控,哪里有问题就可以针对性修改代码进行调优。正所谓哪里有问题点哪里。并且导出和导入是同时进行的,可以将导入程序部署在第三台机器上,充分利用各节点机器资源。但同时优点也是缺点,就是各个环节都要自己编码,需要对 es 有较深入的理解。

2,跨集群_reindex api;这种方法主要的优点在于简单,一个脚本搞定。其内部的实现是 es 自己提供的,应该说代码质量还是有保证的,导入过程中不会遇到各种奇奇怪怪的问题。省心。缺点在于,跨集群 reindex 不能使用多线程 slice,只能单线程操作,这对扩展性是个问题。这个单线程就是整个导入性能的瓶颈。既使你的 es 集群后面的硬件再牛逼,对不起,用不上。

3,snapshot、restore;通过导出导入的方式进行。优点也是简单,相对较快。但是 snapshot 与 restore 两个环节是割裂的,必须串行执行,整个的导入时间需要  备份时间 + 还原时间。如果两个 es 集群之间没有共享存储的话,还要加上文件 copy 的时间。

我的测试是 5000w 的数据导入,primary 分片总大小 175G,通过 enable=false 去掉各种字段索引之后,97G 大小。在我的测试中,导入速度由快到慢为 snapshot/restore 快于 应用编程 快于 跨集群_reindex api。这里虽然 snapshot/restore 排在了第一位,但是我还是抛弃它选择了应用编程。一个原因是 snapshot/restore 限制较多,不够灵活,另一个原因是两者速度相差不是特别大。

下面着重介绍应用编程的优化实践:

1,使用 slice scroll 从源进行查询。slice scroll 并行度不宜太大,以源集群的分片数为宜。当然,如果如果你的机器内存够大,也可以使用分片数的整数倍的并行。因为 es 要在分片上针对文档 id 进行 hash 以确定文档能通过哪个 slice 线程查出来,而不会重复。据官方文档的说法,这个过程在初始化的阶段会消耗大量内存,但是在运行一段时间后,会将其路由逻辑缓存起来,内存消耗会降低。slice scroll 的并行度还要考虑目标 es 的处理能力。通常简单的实现中,一个 slice 会对一个一个线程往目标 es 中插入,es 称之为 worker。如果 worker 太多,es 处理不过来,会有 TOO_MANY_REQUEST 的错误。当然,也可以实现为 slice -> queue  ->bulk worker 这样的结构,slice 往队列中写,worker 从队列中读,worker 与 slice 数量不一定要相等。

2,使用 bulk api 进行批量导入。bulk  size 的大小需要调试,因为如果 bulk size 太大,es 处理不过来,会报出 TOO_MANY_REQUEST 的错误,对应索引的文档会插入失败。

3,去掉不必要的索引,附加属性。es 能做到检索速度飞快,与它的大量索引密不可分。大量索引带来插入过程中额外的 cpu 处理,以及内存,磁盘消耗。磁盘 io 往往成为整个导入的瓶颈。

4,去掉副本。设置 number_of_replicas 为 0。同样在导入结束后恢复。

5,控制 refresh。尽量不要刷新,我最喜欢在导入过程中将 refresh_interval 设置为 - 1,导入结束后恢复。

6,translog 的设置,           “flush_threshold_size”: “1024mb”,  这个值还可以更大,设置为 10G 也没关系。           “sync_interval”: “30s”,           “durability”: “async”

7,index_buffer 的设置,保证每个分片有 512MB 大小的 index_buffer。太大的 index_buffer 作用不大。

8,merge 段大小的设置:max_merged_segment":“1G”。es 会合并小于这个大小的 segment。默认为 5G 大小。意思是什么呢?假如你的 segment 中包括了 2G 大小、2G 大小的 segment,并且 segment 数量满足 merge 条件,那么这 2 个 2G 的 segment 会从磁盘中读出来到内存中进行 merge 成一个大的 segment,然后再写回磁盘。这一个过程就多出了 4G 的磁盘 io。对于正在批量导入的你无疑是一次重大打击——离更快的导入又远了一步。

9,“index.merge.scheduler.max_thread_count”: 1  设置 merge 线程数,思路还是控制导入过程中的 merge。

10,index.store.type 通常这个默认设置都是基于当前系统的最优设置,并不需要调整。

11,最后一个不得不考虑的问题是限流的问题。es 有这样一个考虑,即,它不希望后台的 merge 操作影响到数据检索的性能,因此对 merge 的磁盘 io 有限速。有这样一个作用链:索引插入操作生成许多 segment,es 在后台对这些小 segemnt 进行 merge 以生成大 segment,merge 操作是一个磁盘读写密集的操作,因此 merge 操作消耗许多磁盘 io 资源,es 为了它最初的考虑,对 merge 操作的磁盘 io 速度进行了限流 indices.store.throttle.max_bytes_per_sec,merge 进度因为限流措施远远落后于索引插入速度,因此生成了太多的 segment,es 为了避免 segment explosion 问题,进一步的限制了索引插入的速度。当你在日志中看到了 “now throttling indexing” 这样的信息时,就表示索引插入速度被限制了。这个时候,就要做权衡,如果导入速度优先,就去掉 merge 的磁盘限流:indices.store.throttle.type 设置为 none,或者增大 indices.store.throttle.max_bytes_per_sec 值调高限流阈值;如果检索性能优先,那就需要观察磁盘使用率是否达到最大,如果并没有用满,那也可以增大 indices.store.throttle.max_bytes_per_sec 到合适的大小或者保持原状。

其中,第 5、6、7、8 需要结合起来看,总的思路就是,在第一阶段 insert 数据到内存的过程中,就能生成较大大小的 segment,避免生成大量小 size 的 segment,从而造成不必要的 merge;在第二阶段避免不了的 merge 过程中,降低导入过程中进行 merge 操作的数量,将 merge 操作推后到导入完成后进行,但是在此过程中需要观察是否会因为 merge 进度落后导致了索引速度限流。

整个的导入过程,可以通过 kibana 的 monitor 功能观察整个的导入情况。包括内存,segment,cpu,插入速度等情况。做到完全掌控,心中有数。也能看出来是哪个地方的瓶颈,进行针对性优化。

在我一顿操作猛如虎之下,175G 的数据从最初的 5 小时,跨入了 1 小时内的境界,没办法,机器就是这么渣。当然这可能不算啥,你应该会做的更好。

但是还有一个尾巴,在我的导入测试中,会出现两次磁盘读高峰。使 cpu 的 iowait 指标猛增到 60% 以上,持续时间几乎占到了导入时间的一半。拖慢了我整个的导入速度。但是无论从实时的 hot_threads 还是 kibana 的 merges 指标都看不到 merge 操作的身影。谜一样的存在。