基于版本: 2.x – 5.x
在 Es 的默认设置,是综合考虑数据可靠性,搜索实时性,写入速度等因素的,当你离开默认设置,追求极致的写入速度时,很多是以牺牲可靠性和搜索实时性为代价的。有时候,业务上对两者要求并不高,反而对写入速度要求很高。
从 es 2.x 开始,默认设置下,translog的持久化策略为:每个请求都flush
,这是影响 es写入速度的最大因素,但是只有这样,写操作才有可能是可靠的。
对应配置项为:index.translog.durability: request
如果系统可以接受一定几率的数据丢失,调整translog
持久化策略为周期性和一定大小的时候 flush
:
index.translog.durability: async
index.translog.sync_interval: 120s
index.translog.flush_threshold_size: 1024mb
index.translog.flush_threshold_period: 120m
默认情况下索引的refresh_interval
为1秒,这意味着数据写1秒后就可以被搜索到,每次索引的 refresh
会产生一个新的 lucene
段,这会导致频繁的 segment merge
行为,如果你不需要这么高的搜索实时性,应该降低索引refresh
周期。如:
index.refresh_interval: 120s
segment merge
操作对系统 CPU
和 IO
占用都比较高,从es 2.0开始,merge
行为不再由 Es
控制,而是转由 lucene
控制,因此以下配置已被删除:
indices.store.throttle.type
indices.store.throttle.max_bytes_per_sec
index.store.throttle.type
index.store.throttle.max_bytes_per_sec
改为以下调整开关:
index.merge.scheduler.max_thread_count
index.merge.policy.*
最大线程数的默认值为:
Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))
是一个比较理想的值,如果你只有一块硬盘并且非 SSD,应该把他设置为1,因为在旋转存储介质上并发写,由于寻址的原因,不会提升,只会降低写入速度。
merge 策略有三种:
默认情况下:index.merge.polcy.type: tiered
索引创建时合并策略就已确定,不能更改,但是可以动态更新策略参数,一般情况下,不需要调整。如果堆栈经常有很多merge,可以尝试调整以下配置:
index.merge.policy.floor_segment
该属性用于阻止segment 的频繁flush,小于此值将考虑优先合并,默认为2M,可考虑适当降低此值。
index.merge.policy.segments_per_tier
该属性指定了每层分段的数量,取值越小最终segment
越少,因此需要 merge
的操作更多,可以考虑适当增加此值。默认为10,他应该大于等于index.merge.policy.max_merge_at_once
。
index.merge.policy.max_merged_segment
指定了单个segment
的最大容量,默认为5GB,可以考虑适当降低此值。
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
数量相关,可以考虑适当增大。
建立索引的过程偏计算密集型任务,应该使用固定大小的线程池配置,来不及处理的放入队列,线程数量配置为 CPU 核心数+1,避免过多的上下文切换。队列大小可以适当增加。
如果你的部署方案是为path.data 配置多个路径来使用多块磁盘,es 在分配 shard 时,落到各磁盘上的 shard 可能并不均匀,这种不均匀可能会导致某些磁盘繁忙,利用率达到100%,这种不均匀达到一定程度可能会对写入性能产生负面影响。
Es 在处理多路径时,优先将 shard 分配到可用空间百分比最多的磁盘,因此短时间内创建的 shard 可能被集中分配到这个磁盘,即使可用空间是99%和98%的差别。后来 Es 在2.x 版本中开始解决这个问题的方式是:预估一下这个 shard 会使用的空间,从磁盘可用空间中减去这部分,直到现在6.x beta 版也是这种处理方式。但是实现也存在一些问题:这种机制只存在于一次索引创建的过程中,下一次的索引创建,磁盘可用空间并不是上次做完减法以后的结果,这也可以理解,毕竟预估是不准的,一直减下去很快就减没了。
但是最终的效果是,这种机制并没有从根本上解决问题,即使没有完美的解决方案,这种机制的效果也不够好。如果单一的机制不能解决所有的场景,至少应该为不同场景准备多种选择。为此,我们为 es 增加了两种策略:
为了在节点间任务尽量均衡,数据写入客户端应该把 bulk 请求轮询发送到各个节点。
当使用 java api
或者 rest api
的 bulk
接口发送数据时,客户端将会轮询的发送到集群节点,节点列表取决于:当client.transport.sniff
为 true(默认为 false),列表为所有数据节点。否则,列表为初始化客户端对象时添加进去的节点。
java api
的 TransportClient
和 rest api
的 RestClient
都是线程安全的,当写入程序自己创建线程池控制并发,应该使用同一个 Client
对象。在此建议使用 rest api
,兼容性好,只有吞吐量非常大才值得考虑序列化的开销,显然搜索并不是高吞吐量的业务。
观察bulk
请求在不同节点上的处理情况,通过cat
接口观察 bulk
线程池和队列情况,是否存在不均:
GET _cat/thread_pool
分析 Es 写入流程可以看到,写入 doc 时如果是外部指定了 id,Es 会先尝试读取原来doc的版本号, 判断是否需要更新,使用自动生成 doc id
可以避免这个环节。
对字段不分词或者不索引,可以节省很多运算,降低 CPU 占用。尤其是 binary 类型,默认情况下占用 CPU 非常高,而这种类型根本不需要进行分词做索引。
单个 doc 在建立索引时的运算复杂度,最大的因素不在于 doc 的字节数或者说某个字段 value 的长度,而是字段的数量。 例如在满负载的写入压力测试中,mapping 相同的情况下,一个有10个字段,200字节的 doc, 通过增加某些字段 value 的长度到500字节,写入 Es 时速度下降很少,而如果字段数增加到20,即使整个 doc 字节数没增加多少,写入速度也会降低一倍。
不同的分析器在索引过程中运算复杂度也有较大的差异
_source
字段用于存储 doc 原始数据,对于部分不需要存储的字段,可以通过 includes、excludes
来过滤,或者将_source
禁用,一般用于索引和数据分离。
这样可以降低 io 的压力,不过实际场景大多数情况不会禁用 _source
,而即使过滤掉某些字段,对于写入速度的提示效果也不大,满负荷写入情况下,基本是CPU 先跑满了,瓶颈在于 CPU。
_all
字段默认是开启的,其中包含所有字段分词后的关键词,作用是可以在搜索的时候不指定特定字段,从所有字段中检索。如果你不需要这个特性,可以禁用 _all
,可以小幅的降低CPU 压力,对速度影响并不明显。
Norms 用于在搜索时计算 doc 的评分,如果不需要评分,可以禁用他:
"title": {"type": "string","norms": {"enabled": false}}
对于 text
类型的字段而言,默认开启了norms
,而 keyword
类型的字段则默认关闭了norms
开启norms之后,每篇文档的每个字段需要一个字节存储norms。对于 text 类型的字段而言是默认开启norms的,因此对于不需要评分的 text 类型的字段,可以禁用norms,这算是一个调优点吧。
index_options
用于控制在建立倒排索引过程中,哪些内容会被添加到倒排,例如 doc数量、词频、positions、offsets等信息,优化这些设置可以一定程度降低索引过程中运算任务,节省 CPU 占用率。不过实际场景中,通常很难确定业务将来会不会用到这些信息,除非一开始方案就明确这样设计的
index_options:索引选项控制添加到倒排索引(Inverted Index)的信息,这些信息用于搜索(Search)和高亮显示:
docs:只索引文档编号(Doc Number)
freqs:索引文档编号和词频率(term frequency)
positions:索引文档编号,词频率和词位置(序号)
offsets:索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号)
默认情况下,被分析的字符串(analyzed string)字段使用positions,其他字段使用docs。
下面是笔者的线上环境使用的全局模板和配置文件的部分内容,省略掉了节点名称、节点列表等基础配置字段,仅列出与本文相关内容。
从ES 5.x开始,索引级设置需要写在模板中,或者在创建索引时指定,我们把各个索引通用的配置写到了模板中,这个模板匹配全部的索引,并且具有最低的优先级,让用户定义的模板有更高的优先级,以覆盖这个模板中的配置。
{
"template": "*",
"order" : 0,
"settings": {
"index.merge.policy.max_merged_segment" : "2gb",
"index.merge.policy.segments per_tier" : "24",
"index.number_of_replicas" : "1",
"index.number_of_shards" : "24",
"index.optimize_auto_generated_id" : "true",
"index.refresh_interval" : "120s",
"index.translog.durability" : "async",
"index.translog.flush_threshold_size" : "1000mb",
"index.translog. sync_ interval" : "120s",
"index.unassigned.node_left.delayed_timeout" : "5d"
}
}
elasticsearch.yml中的配置:
indices.memory.index_buffer_size: 30%
(1) 方法比结论重要。一个系统性问题往往是多种因素造成的,在处理集群的写入性能问题上,先将问题分解,在单台上进行压测,观察哪种系统资源达到极限,例如,CPU或磁盘利用率、I/O block、线程切换、堆栈状态等。然后分析并调整参数,优化单台上的能力,先解决局部问题,在此基础上解决整体问题会容易得多。
(2) 可以使用更好的CPU,或者使用SSD,对写入性能提升明显。在我们的测试中,在相同条件下,E5 2650V4比E5 2430v2的写入速度高60%左右。
(3) 在我们的压测环境中,写入速度稳定在平均单机每秒3万条以上,使用的测试数据:每个文档的字段数量为10个左右,文档大小约100字节,CPU使用E5 2430 v2。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。