ElasticSearch索引、查询和写入性能调优

注: 部分概念介绍来源于网络

一、Elasticsearch部署建议
1.选择合理的硬件配置
尽可能使用SSD
Elasticsearch最大的瓶颈往往是磁盘读写性能,尤其是随机读取性能。使用SSD(PCI-E接口SSD卡/SATA接口SSD盘)通常比机械硬盘(SATA盘/SAS盘)查询速度快5~10倍,写入性能提升不明显。
对于文档检索类查询性能要求较高的场景,建议考虑SSD作为存储,同时按照1:10的比例配置内存和硬盘。对于日志分析类查询并发要求较低的场景,可以考虑采用机械硬盘作为存储,同时按照1:50的比例配置内存和硬盘。单节点存储数据建议在2TB以内,不要超过5TB,避免查询速度慢、系统不稳定。
2.给JVM配置机器一半的内存
但是不建议超过32G
修改conf/jvm.options配置,-Xms和-Xmx设置为相同的值,推荐设置为机器内存的一半左右,剩余一半留给操作系统缓存使用。jvm内存建议不要低于2G,否则有可能因为内存不足导致ES无法正常启动或内存溢出,jvm建议不要超过32G,否则jvm会禁用内存对象指针压缩技术,造成内存浪费。机器内存大于64G内存时,推荐配置-Xms30g -Xmx30g 。JVM堆内存较大时,内存垃圾回收暂停时间比较长,建议配置ZGC或G1垃圾回收算法。
3.规模较大的集群配置专有主节点
避免脑裂问题
Elasticsearch主节点(master节点)负责集群元信息管理、index的增删操作、节点的加入剔除,定期将最新的集群状态广播至各个节点。在集群规模较大时,建议配置专有主节点只负责集群管理,不存储数据,不承担数据读写压力。
# 专有主节点配置(conf/elasticsearch.yml):
node.master:true
node.data: false
node.ingest:false
# 数据节点配置(conf/elasticsearch.yml):
node.master:false
node.data:true
node.ingest:true
Elasticsearch默认每个节点既是候选主节点,又是数据节点。最小主节点数量参数minimum_master_nodes推荐配置为候选主节点数量一半以上,该配置告诉Elasticsearch当没有足够的master候选节点的时候,不进行master节点选举,等master节点足够了才进行选举。
例如对于3节点集群,最小主节点数量从默认值1改为2。
# 最小主节点数量配置(conf/elasticsearch.yml):
discovery.zen.minimum_master_nodes: 2
4.Linux操作系统调优
关闭交换分区,防止内存置换降低性能。
将/etc/fstab 文件中包含swap的行注释掉
sed -i '/swap/s/^/#/' /etc/fstab
swapoff -a
单用户可以打开的最大文件数量,可以设置为官方推荐的65536或更大些
echo "* - nofile 655360" >> /etc/security/limits.conf
单用户线程数调大
echo "* - nproc 131072" >> /etc/security/limits.conf
单进程可以使用的最大map内存区域数量
echo "vm.max_map_count = 655360" >> /etc/sysctl.conf
参数修改立即生效
sysctl -p
5.索引性能调优建议
1)设置合理的索引分片数和副本数
索引分片数建议设置为集群节点的整数倍,初始数据导入时副本数设置为0,生产环境副本数建议设置为1(设置1个副本,集群任意1个节点宕机数据不会丢失;设置更多副本会占用更多存储空间,操作系统缓存命中率会下降,检索性能不一定提升)。单节点索引分片数建议不要超过3个,每个索引分片推荐10-40GB大小。索引分片数设置后不可以修改,副本数设置后可以修改。
Elasticsearch6.X及之前的版本默认索引分片数为5、副本数为1,从Elasticsearch7.0开始调整为默认索引分片数为1、副本数为1。
2)使用批量请求
使用批量请求将产生比单文档索引请求好得多的性能。写入数据时调用批量提交接口,推荐每批量提交5~15MB数据。例如单条记录1KB大小,每批次提交10000条左右记录写入性能较优;单条记录5KB大小,每批次提交2000条左右记录写入性能较优。
3)通过多进程/线程发送数据
单线程批量写入数据往往不能充分利用服务器CPU资源,可以尝试调整写入线程数或者在多个客户端上同时向Elasticsearch服务器提交写入请求。与批量调整大小请求类似,只有测试才能确定最佳的worker数量。可以通过逐渐增加工作任务数量来测试,直到集群上的I / O或CPU饱和。
4)调大refresh interval
在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。
并不是所有的情况都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日志文件,你可能想优化索引速度而不是近实时搜索,可以通过设置 refresh_interval,降低每个索引的刷新频率。
5)配置事务日志参数
事务日志translog用于防止节点失败时的数据丢失。它的设计目的是帮助shard恢复操作,否则数据可能会从内存flush到磁盘时发生意外而丢失。事务日志translog的落盘(fsync)是ES在后台自动执行的,默认每5秒钟提交到磁盘上,或者当translog文件大小大于512MB提交,或者在每个成功的索引、删除、更新或批量请求时提交。
索引创建时,可以调整默认日志刷新间隔5秒,例如改为60秒,index.translog.sync_interval: "60s"。创建索引后,可以动态调整translog参数,"index.translog.durability":"async"相当于关闭了index、bulk等操作的同步flush translog操作,仅使用默认的定时刷新、文件大小阈值刷新的机制。
6)设计mapping配置合适的字段类型
Elasticsearch在写入文档时,如果请求中指定的索引名不存在,会自动创建新索引,并根据文档内容猜测可能的字段类型。但这往往不是最高效的,我们可以根据应用场景来设计合理的字段类型。

二、Elasticsearch提升写入速度
1.加大translog flush间隔,目的是降低iops(iops指的是每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一)、writeblock。
2.加大index refresh间隔,除了降低I/O,更重要的是降低了segment merge频率。
3.调整bulk请求。
4.优化磁盘间的任务均匀情况,将shard尽量均匀分布到物理主机的 各个磁盘。
5.优化节点间的任务分布,将任务尽量均匀地发到各节点。
6.优化Lucene层建立索引的过程,目的是降低CPU占用率及I/O,例如,禁用_all字段
为了更好理解上述优化策略,需要再复习一下几个概念:
flush:是指触发lucene commit,也就是将缓存中的数据写入到磁盘,并清空translog日志文件。
refresh: 是指从内存到文件系统缓存的过程,这样该文档就可以被搜索到,但是该文档还没有存储到磁盘上,如果机器宕机了,数据就会丢失。

1.translog flush间隔调整
1.1 index.translog.durability
在默认设置下,translog的持久化策略为:每个请求 都“flush”。
对应配置项如下:
index.translog.durability: request 
这是影响 ES 写入速度的最大因素。但是只有这样,写操作才有可 能是可靠的。如果系统可以接受一定概率的数据丢失(例如,数据写入 主分片成功,尚未复制到副分片时,主机断电。由于数据既没有刷到 Lucene,translog也没有刷盘,恢复时translog中没有这个数据,数据丢失,则调整translog持久化策略为周期性和一定大小的时候“flush”,例 如:
index.translog.durability: async 
设置为async表示translog的刷盘策略按sync_interval配置指定的时间周期进行。
index.translog.sync_interval: 120s 
加大translog刷盘间隔时间。默认为5s,不可低于100ms。
1.2 index.translog.flush_threshold_size
index.translog.flush_threshold_size: 1024mb 
超过这个大小会导致refresh操作,产生新的Lucene分段。默认值为 512MB。

2.索引刷新间隔refresh_interval
默认情况下索引的refresh_interval为1秒,这意味着数据写1秒后就 可以被搜索到,每次索引的refresh会产生一个新的Lucene段,Lucene段即为segment,segment在复合一定条件后,会自动合并,因此这会导致频繁的segment merge行为(),如果不需要这么高的搜索实时性,应该降低 索引refresh周期,例如:
index.refresh_interval: 120s

3.段合并优化
segment merge操作对系统I/O和内存占用都比较高,从ES 2.0开始, merge行为不再由ES控制,而是由Lucene控制,调整开关:
index.merge.scheduler.max_thread_count 
index.merge.policy.* 
最大线程数max_thread_count的默认值如下:
Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors()/2)) 
以上是一个比较理想的值,如果只有一块硬盘并且非 SSD,则应该把它设置为1,因为在旋转存储介质上并发写,由于寻址的原因,只会降低写入速度。
merge策略index.merge.policy有三种:
tiered(默认策略); 
log_byete_size; 
log_doc。 
目前我们使用默认策略,但是对策略的参数进行了一些调整。 索引创建时合并策略就已确定 ,不能更改,但是可以动态更新策略参数,可以不做此项调整。如果堆栈经常有很多merge,则可以尝试调整以下策略配置:
index.merge.policy.segments_per_tier 
该属性指定了每层分段的数量,取值越小则最终segment越少,因此需要merge的操作更多,可以考虑适当增加此值。默认为10,,其应该 大于等于index.merge.policy.max_merge_at_once。
index.merge.policy.max_merge_at_once。     
表示默认一次最多归并segment的个数,默认也为 10个 segment。
index.merge.policy.max_merged_segment
指定了单个segment的最大容量,默认为5GB,可以考虑适当降低此值。
4.indexing buffer
indexing buffer在为doc建立索引时使用,当缓冲满时会刷入磁盘, 生成一个新的segment,这是除refresh_interval刷新索引外,另一个生成 新segment的机会。
每个shard有自己的indexing buffer,下面的这个buffer 大小的配置需要除以这个节点上所有shard的数量:
indices.memory.index_buffer_size 默认为整个堆空间的10%。 
indices.memory.min_index_buffer_size 默认为48MB。 
indices.memory.max_index_buffer_size 默认为无限制。 
在执行大量的索引操作时,indices.memory.index_buffer_size的默认 设置可能不够,这和可用堆内存、单节点上的shard数量相关,可以考虑适当增大该值,增大该值,减少segment,就会减少merge。
5.使用bulk请求
批量写比一个索引请求只写单个文档的效率高得多,但是要注意 bulk请求的整体字节数不要太大,太大的请求可能会给集群带来内存压 力,因此每个请求最好避免超过几十兆字节,即使较大的请求看上去执 行得更好。
5.1 bulk线程池和队列
建立索引的过程属于计算密集型任务,应该使用固定大小的线程池 配置,来不及处理的任务放入队列。线程池最大线程数量应配置为CPU 核心数+1,这也是bulk线程池的默认设置,可以避免过多的上下文切 换。队列大小可以适当增加,但一定要严格控制大小,过大的队列导致 较高的GC压力,并可能导致FGC频繁发生。
5.2 并发执行bulk请求
bulk写请求是个长任务,为了给系统增加足够的写入压力,写入过程应该多个客户端、多线程地并行执行,如果要验证系统的极限写入能 力,那么目标就是把CPU压满。磁盘util、内存等一般都不是瓶颈。如果CPU没有压满,则应该提高写入端的并发数量。
但是要注意bulk线 程池队列的reject情况,出现reject代表ES的bulk队列已满,客户端收到429错误(TOO_MANY_REQUESTS)。不可忽略这个异常,否则写入系 统的数据会少于预期。即使客户端正确处理了429错误,我们仍然应该尽量避免产生reject。
因此,在评估极限的写入能力时,客户端的极限写入并发量应该控制在不产生reject前提下的最大值为宜。
6.磁盘间的任务均衡
如果部署方案是为path.data配置多个路径来使用多块磁盘,则ES在 分配shard时,落到各磁盘上的 shard 可能并不均匀,这种不均匀可能会导致某些磁盘繁忙,利用率在较长时间内持续达到100%。这种不均匀 达到一定程度会对写入性能产生负面影响。
ES在处理多路径时,优先将shard分配到可用空间百分比最多的磁盘上,因此短时间内创建的shard可能被集中分配到这个磁盘上,即使可用空间是99%和98%的差别。如果单一的机制不能解决所有的场景,那么至少应该为不同场景准备多种选择。
为此,我们为ES增加了两种策略。
简单轮询:在系统初始阶段,简单轮询的效果是最均匀的。
基于可用空间的动态加权轮询:以可用空间作为权重,在磁盘之间加权轮询。
7.节点间的任务均衡
为了节点间的任务尽量均衡,数据写入客户端应该把bulk请求轮询 发送到各个节点。 当使用Java API或REST API的bulk接口发送数据时,客户端将会轮 询发送到集群节点,节点列表取决于:
使用Java API时,当设置client.transport.sniff为true(默认为false) 时,列表为所有数据节点,否则节点列表为构建客户端对象时传入的节 点列表。
使用REST API时,列表为构建对象时添加进去的节点。 
在此建议使用REST API,Java API会在未来的版本中废弃,REST API有良好的版本兼容性好。理论上,Java API在序列化上有性能优 势,但是只有在吞吐量非常大时才值得考虑序列化的开销带来的影响, 通常搜索并不是高吞吐量的业务。
要观察bulk请求在不同节点间的均衡性,可以通过cat接口观察bulk 线程池和队列情况: _cat/thread_pool

8.索引过程调整和优化
8.1 自动生成doc ID
通过ES写入流程可以看出,写入doc时如果外部指定了id,则ES会先尝试读取原来doc的版本号,以判断是否需要更新。这会涉及一次读取磁盘的操作,通过自动生成doc ID可以避免这个环节。
8.2 调整字段Mappings
减少字段数量,对于不需要建立索引的字段,不写入ES。
将不需要建立索引的字段index属性设置为not_analyzed或no。
对字段不分词,或者不索引,可以减少很多运算操作,降低CPU占用。 尤其是binary类型,默认情况下占用CPU非常高,而这种类型进行分词通常没有什么意义。
减少字段内容长度,如果原始数据的大段内容无须全部建立 索引,则可以尽量减少不必要的内容。
使用不同的分析器(analyzer),不同的分析器在索引过程中 运算复杂度也有较大的差异。
8.3 调整_source字段
_source 字段用于存储 doc 原始数据,对于部分不需要存储的字 段,可以通过 includes excludes过滤,或者将_source禁用,一般用于索 引和数据分离。 这样可以降低 I/O 的压力,不过实际场景中大多不会禁用_source,而即使过滤掉某些字段,对于写入速度的提升作用也不大,满负荷写入情况下,基本是 CPU先跑满了,瓶颈在于CPU。
8.4 禁用_all字段
从ES 6.0开始,_all字段默认为不启用,而在此前的版本中,_all字 段默认是开启的。_all字段中包含所有字段分词后的关键词,作用是可以在搜索的时候不指定特定字段,从所有字段中检索。ES 6.0默认禁用 _all字段主要有以下几点原因:
由于需要从其他的全部字段复制所有字段值,导致_all字段占用非常大的空间。
_all 字段有自己的分析器,在进行某些查询时(例如,同义词),结果不符合预期,因为没有匹配同一个分析器。
由于数据重复引起的额外建立索引的开销。
想要调试时,其内容不容易检查。
有些用户甚至不知道存在这个字段,导致了查询混乱。
禁用_all字段可以明显降低对CPU和I/O的压力。
8.5 对Analyzed的字段禁用Norms
Norms用于在搜索时计算doc的评分,如果不需要评分,则可以将其 禁用:
"title":
         {
             "type": "string",
             "norms": 
             {
                 "enabled": false
             }
         }
8.6 index_options 设置
index_options用于控制在建立倒排索引过程中,哪些内容会被添加到倒排索引,例如,doc数量、词频、positions、offsets等信息,优化这些设置可以一定程度降低索引过程中的运算任务,节省CPU占用率。 不过在实际场景中,通常很难确定业务将来会不会用到这些信息, 除非一开始方案就明确是这样设计的。

三、Elasticsearch数据写入流程
(1)数据写入buffer缓冲和translog日志文件
(2)每隔一秒钟,buffer中的数据被写入新的segment file,并进入os cache,此时segment被打开并供search使用
(3)buffer被清空
(4)重复1~3,新的segment不断添加,buffer不断被清空,而translog中的数据不断累加
(5)当translog长度达到一定程度的时候,commit操作发生
(5-1)buffer中的所有数据写入一个新的segment,并写入os cache,打开供使用
(5-2)buffer被清空
(5-3)一个commit ponit被写入磁盘,标明了所有的index segment
(5-4)filesystem cache中的所有index segment file缓存数据,被fsync强行刷到磁盘上
(5-5)现有的translog被清空,创建一个新的translog

你可能感兴趣的:(ElasticSearch,elasticsearch)