Elasticsearch默认的设置和参数配置下,是综合考虑了数据的可靠性、搜索实时性、写入速度等因素。但在某些场景中,业务对数据的可靠性和搜索实时性要求并不高,反而对写入速度要求高,可以通过调整一些策略,优化写入速度。
综合来说,提升写入速度可以从以下几个方面入手:
1) 加大index refresh间隔,除了降低IO,更重要的是降低了segment merge的频率;
2) 加大translog refresh间隔,目的是降低iops、writeblock;
3) 调整bulk请求,控制每次bulk请求提交的量;
4) 优化磁盘间的任务均匀情况,将shard尽量均匀分布到机器的各个磁盘;
5) 优化节点间的任务分布,将任务尽量均匀地发布到各节点,避免单点阻塞问题;
6) 优化Lucene层建立索引的过程,目的是降低CPU占用率及IO,例如,禁用_all字段。
默认情况下索引的refresh_interval为1秒,这意味着数据写1秒后就可以被搜索到,每次索引的refresh会产生一个新的lucene段,这会导致频繁的segment merge行为,如果你不需要这么高的搜索实时性,应该降低索引refresh周期,如:
index.refresh_interval: 120s
默认设置下,translog的持久化策略为:每个请求都flush。对应配置项为:
index.translog.durability: request
这是影响Elasticsearch写入速度的最大因素。但是只有这样,写操作才有可能是可靠的,如果系统可以接受一定几率的数据丢失,调整translog持久化策略为周期性和一定大小的时候flush:
index.translog.durability: async index.translog.sync_interval: 120s
index.translog.flush_threshold_size: 3gb
index.translog.flush_threshold_period: 120m
segment merge操作对系统CPU和IO占用都比较高,从es 2.0开始,merge行为不再由es控制,而是转由lucene控制。有以下调整开关:
index.merge.scheduler.max_thread_count:最大线程数
index.merge.policy.*
最大线程数的默认值为:
Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))
是一个比较理想的值,如果你只有一块硬盘并且非SSD,应该把他设置为1,因为在旋转存储介质上并发写,由于寻址的原因,不会提升,只会降低写入速度。
index.merge.policy.floor_segment
该属性用于阻止segment的频繁flush,小于此值将考虑优先合并,默认为2M,可考虑适当降低此值,如1M。
index.merge.policy.segments_per_tier
该属性指定了每层分段的数量,取值越小最终segment越少,因此需要merge的操作更多,可以考虑适当增加此值。默认为10,他应该大于等于 index.merge.policy.max_merge_at_once
“merge”:{
“scheduler”:{
“max_thread_count” : “1”
},
“policy”:{
“segments_per_tier” : “20”,
“max_merge_at_once”: “20”,
“floor_segment” : “1m”,
“max_merged_segment” : “5g”
}
}
indexing buffer在为doc建立索引时使用,当缓冲满时会刷入磁盘,生成一个新的 segment,这是除refresh_interval外另外一个刷新索引,生成新segment的机会。每个 shard有自己的indexing buffer,下面的关于这个buffer大小的配置需要除以这个节点上所有的shard数量。
indices.memory.index_buffer_size
默认为整个堆的10%,如30GB堆内存,约占300M,在大量的索引操作时,可以考虑适当增大,但不要超过20%。
1) 使用批量请求,设置合理的数据条数:使用bulk命令进行批量索引数据时,每批次提交的数据大小为5~15MB;比如每条数据大小为1k,那么建议批量提交的数据条数为5000条;当前集群的最佳批量请求大小,可以从5MB开始测试,缓慢增加这个大小,直到写入性能不能提升为止。
2) 每个批量请求中只处理一个索引的数据:一个bulk请求只写入一个索引的数据,不建议一个bulk请求同时写入多个索引的数据,不同索引的数据分多个bulk请求提交。
3) 建立索引的过程偏计算密集型任务,应该使用固定大小的线程池配置,来不及处理的放入队列,线程数量配置为CPU核心数+1,避免过多的上下文切换。队列大小可以适当增加,但一定要严格控制大小,过大的队列导致较高的GC压力,并可能导致FGC频繁发生。
如果你的部署方案是为path.data配置多个路径来使用多块磁盘, es在分配shard 时,落到各磁盘上的shard可能并不均匀,这种不均匀可能会导致某些磁盘繁忙,利用率达到100%,这种不均匀达到一定程度可能会对写入性能产生负面影响。
所以建议每个数据实例挂载单块磁盘。
为了在节点间任务尽量均衡,数据写入客户端应该把bulk请求轮询发送到各个节点。
当使用java api ,或者rest api的bulk接口发送数据时,客户端将会轮询的发送的集群节点,节点列表取决于:
当client.transport.sniff为true,(默认为 false),列表为所有数据节点。
否则,列表为初始化客户端对象时添加进去的节点。
java api的TransportClient和rest api的RestClient都是线程安全的,当写入程序自己创建线程池控制并发,应该使用同一个Client对象。在此建议使用rest api,兼容性好,只有吞吐量非常大才值得考虑序列化的开销,显然搜索并不是高吞吐量的业务。
观察bulk请求在不同节点上的处理情况,通过cat接口观察bulk线程池和队列情况,是否存在不均:
_cat/thread_pool/bulk?v
另外,也可以搭建NGINX服务来达到负载均衡。
配置total_shards_per_node参数,让分片更加均匀的分布在各个实例上。表示限制每个实例上分布该该索引的分片最大个数,如2,即表示每个实例上最多分布2个该索引的分片。
说明:total_shards_per_node参数值=索引总分片数/数据实例数(向上取整)。
自动生成doc ID
分析es写入流程可以看到,写入doc时如果是外部指定了id,es会先尝试读取原来doc的版本号,判断是否需要更新,使用自动生成doc id可以避免这个环节,从而避免出现在磁盘IOPS能力不足的情况下,磁盘IO被读IO大量占用,导致写入速度受限于磁盘IO。
调整字段 Mappings
1) 字段的index属性设置为: not_analyzed,或者no。对字段不分词,或者不索引,可以节省很多运算,降低CPU占用。尤其是binary类型,默认情况下占用CPU非常高,而这种类型根本不需要进行分词做索引。
2) 调整_source字段:_source字段用于存储doc原始数据,对于部分不需要存储的字段,可以通过includes excludes来过滤,或者将_source 禁用,一般用于索引和数据分离。
3) 禁用_all字段:_all字段默认是开启的,其中包含所有字段分词后的关键词,作用是可以在搜索的时候不指定特定字段,从所有字段中检索。如果你不需要这个特性,可以禁用_all,可以小幅的降低CPU 压力,对速度影响并不明显。
over