在当今世界,各行各业每天都有海量数据产生,为了从这些海量数据中获取想要的分析结果,需要对数据进行提取、转换,存储,维护,管理和分析。 这已然远远超出了普通处理工具、数据库等的实现能力,只有基于的分布式架构和并行处理机制的大数据工具所才能实现这些功能。 Elasticsearch是响应如前所述大多数用例的最热门的开源数据存储引擎之一。
Elasticsearch是一种分布式数据存储和搜索引擎,具有容错和高可用性特点。 为了充分利用其搜索功能,需要正确配置Elasticsearch。
简单的默认配置不适合每个实际业务场景。实战开发运维中,个性化实现贴合自己业务场景的集群配置是优化集群性能的必经之路。 本文集合实战业务场景,重点介绍搜索密集型Elasticsearch集群的提升性能的干货配置。
1、索引层面优化配置
默认情况下,6.x及之前的版本中Elasticsearch索引有5个主分片和1个副本,7.X及之后版本1主1副。 这种配置并不适用于所有业务场景。 需要正确设置分片配置,以便维持索引的稳定性和有效性。
1.1、分片大小
分片大小对于搜索查询非常重要。
- 一方面, 如果分配给索引的分片太多,则Lucene分段会很小,从而导致开销增加。 当同时进行多个查询时,许多小分片也会降低查询吞吐量。
- 另一方面,太大的分片会导致搜索性能下降和故障恢复时间更长。
Elasticsearch官方建议一个分片的大小应该在20到40 GB左右。
例如,如果您计算出索引将存储300 GB的数据,则可以为该索引分配9到15个主分片。
根据集群大小,假设群集中有10个节点,您可以选择为此索引分配10个主分片,以便在集群节点之间均匀分配分片。
深入原理推荐:
- Elasticsearch之如何合理分配索引分片? https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster
- Elasticsearch究竟要设置多少分片数? https://qbox.io/blog/optimizing-elasticsearch-how-many-shards-per-index
1.2、数据动态持续写入场景
如果存在连续写入到Elasticsearch集群的数据流,如:实时爬虫互联网数据写入ES集群。则应使用基于时间的索引以便更轻松地维护索引。
如果写入数据流的吞吐量随时间而变化,则需要适当地改变下一个索引的配置才能实现数据的动态扩展。
那么,如何查询分散到不同的基于时间索引的所有文档?答案是别名。可以将多个索引放入别名中,并且对该别名进行搜索会使查询就像在单个索引上一样。
当然,需要保持好平衡。注意思考:将多少数据写入别名?别名上写入太多小索引会对性能产生负面影响。
例如,是以周还是以月为单位为单位建立索引是需要结合业务场景平衡考虑的问题?
如果以月为单位建议索引性能最优,那么相同数据以周为单位建立索引势必会因为索引太多导致负面的性能问题。
深入原理推荐:Elasticsearch索引管理利器——Curator深入详解
1.3、Index Sorting
注意:索引排序机制是6.X版本才有的特性。
在Elasticsearch中创建新索引时,可以配置每个分片中的分段的排序方式。 默认情况下,Lucene不会应用任何排序。 index.sort.* 定义应使用哪些字段对每个Segment内的文档进行排序。
使用举例:
curl -X PUT "localhost:9200/twitter" -H 'Content-Type: application/json' -d'
{
"settings" : {
"index" : {
"sort.field" : "date",
"sort.order" : "desc"
}
},
"mappings": {
"properties": {
"date": {
"type": "date"
}
}
}
}
12345678910111213141516
目的:index sorting是优化Elasticsearch检索性能的非常重要的方式之一。
**大白话:**index sorting机制通过写入的时候指定了某一个或者多个字段的排序方式,会极大提升检索的性能。
更深原理:推荐阅读:https://www.elastic.co/cn/blog/index-sorting-elasticsearch-6-0
2、分片层面优化配置
分片是底层基本的读写单元,分片的目的是分割巨大索引,让读写并行执行。写入过程先写入主分片,主分片写入成功后再写入副本分片。
副本分片的出现,提升了集群的高可用性和读取吞吐率。
在优化分片时,分片的大小、节点中有多少分片是主要考虑因素。副本分片对于扩展搜索吞吐量很重要,如果硬件条件允许,则可以小心增加副本分片的数量。
容量规划的一个很好的启动点是分配分片,“《深入理解Elasticsearch》强调:最理想的分片数量应该依赖于节点的数量。”其数量是节点数量的1.5到3倍。
分配副本分片数的公式:max(max_failures,ceil(num_nodes /) num_primaries) - 1)。
原理:如果您的群集具有num_nodes节点,总共有num_primaries主分片,如果您希望最多能够同时处理max_failures节点故障,那么适合您的副本数量为如上公式值。
公式来源:https://www.elastic.co/guide/en/elasticsearch/reference/master/tune-for-search-speed.html
总的来说:节点数和分片数、副本数的简单计算公式如下:
所需做大节点数=分片数*(副本数+1)。
3、Elasticsearch整体层面配置
配置Elasticsearch集群时,最主要的考虑因素之一是确保至少有一半的可用内存进入文件系统缓存,以便Elasticsearch可以将索引的hot regions保留在物理内存中。
在设计集群时还应考虑物理可用堆空间。 Elasticsearch建议基于可用堆空间的分片分配最多应为20个分片/ GB,这是一个很好的经验法则。
例如,具有30 GB堆的节点最多应有600个分片,以保持集群的良好状态。
一个节点上的存储可以表述如下:节点可以支持的磁盘空间= 20 (堆大小单位:GB)(以GB为单位的分片大小)
由于在高效集群中通常会看到大小在20到40 GB之间的分片,
因此最大存储空间可以支持16 GB可用堆空间的节点,最多可达12 TB的磁盘空间(201640=12.8TB)。
边界意识有助于为更好的设计和未来的扩展操作做好准备。
可以在运行时以及初始阶段进行许多配置设置。
在构建Elasticsearch索引和集群本身以获得更好的搜索性能时,了解在运行时哪些配置可以修改以及哪些配不可以修改是至关重要的。
3.1 动态设置
1、设置历史数据索引为只读状态。
基于时间的动态索引的执行阶段,如果存放历史数据的索引没有写操作,可以将月度索引设置为只读模式,以提高对这些索引的搜索性能。
6.X之后的只读索引实战设置方式:
PUT /twitter/_settings
{
"index.blocks.read_only_allow_delete": null
}
1234
2、对只读状态索引,进行段合并。
当索引设置为只读时,可以通过强制段合并操作以减少段的数量。
优化段合并将导致更好的搜索性能,因为每个分片的开销取决于段的计数和大小。
- 注意1:不要将段合并用于读写索引,因为它将导致产生非常大的段(每段> 5Gb)。
- 注意2:此操作应在非高峰时间进行,因为这是一项非常耗资源的操作。
段合并操作实战方式:
curl -X POST "localhost:9200/kimchy/_forcemerge?only_expunge_deletes=false&max_num_segments=100&flush=true"
1
3、使用preference优化缓存利用率
有多个缓存可以帮助提高搜索性能,例如文件系统缓存,请求缓存或查询缓存。
然而,所有这些缓存都维护在节点级别,这意味着如果您在拥有1个或更多副本且基于默认路由算法集群上连续两次运行相同的请求,这两个请求将转到不同的分片副本上 ,阻止节点级缓存帮助。
由于搜索应用程序的用户一个接一个地运行类似的请求是常见的,例如为了检索分析索引的部分较窄子集,使用preference标识当前用户或会话的偏好值可以帮助优化高速缓存的使用。
preference实战举例:
GET /_search?preference=xyzabc123
{
"query": {
"match": {
"title": "elasticsearch"
}
}
12345678
4、禁止交换
可以在每个节点上禁用交换以确保稳定性,并且应该不惜一切代价避免交换。它可能导致垃圾收集持续数分钟而不是毫秒,并且可能导致节点响应缓慢甚至断开与集群的连接。
在Elasticsearch分布式系统中,让操作系统终止节点更有效。可以通过将bootstrap.memory_lock设置为True来禁用它。
Linux系统级配置:
sudo swapoff -a
1
Elasticsearch配置文件elasticsearch.yml配置:
bootstrap.memory_lock: true
1
5、增加刷新间隔 refresh_interval
默认刷新间隔为1秒。这迫使Elasticsearch每秒创建一个分段。实际业务中,应该根据使用情况增加刷新间隔,举例:增加到30秒。
这样之后,30s产生一个大的段,较每秒刷新大大减少未来的段合并压力。最终会提升写入性能并使搜索查询更加稳定。
更新刷新间隔实战:
PUT /twitter/_settings
{
"index" : {
"refresh_interval" : "1s"
}
}
123456
6、设置index.merge.scheduler.max_thread_count
index.merge.scheduler.max_thread_count默认设置为Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))。
但这适用于SSD配置。对于HDD,应将其设置为1。
实战:
curl -XPUT 'localhost:9200/_settings' -d '{
"index.merge.scheduler.max_thread_count" : 1
}
123
7、禁止动态分配分片
有时,Elasticsearch将重新平衡集群中的分片。此操作可能会降低检索的性能。
在生产模式下,需要时,可以通过cluster.routing.rebalance.enable设置将重新平衡设置为none。
PUT /_cluster/settings
{
"transient" : {
"cluster.routing.allocation.enable" : "none"
}
}
123456
其中典型的应用场景之包括:
- 集群中临时重启、剔除一个节点;
- 集群逐个升级节点;当您关闭节点时,分配过程将立即尝试将该节点上的分片复制到集群中的其他节点,从而导致大量浪费的IO. 在关闭节点之前禁用分配可以避免这种情况。
更多实践,推荐阅读:https://www.elastic.co/guide/en/elasticsearch/reference/5.5/restart-upgrade.html
8、充分利用近似日期缓存效果
现在使用的日期字段上的查询通常不可缓存,因为匹配的范围一直在变化。
然而,就用户体验而言,切换到近似日期通常是可接受的,并且能更好地使用查询高速缓存带来的益处。
实战如下:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
}
}
}
}
123456789101112131415
这里可能不大好理解,推荐深入阅读:https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html
3.2 初始设置
1、合并多字段提升检索性能
query_string或multi_match查询所针对的字段越多,检索越慢。
提高多个字段的搜索速度的常用技术是在索引时将其值复制到单个字段中。
对于经常查询的某些字段,请使用Elasticsearch的copy-to功能。
例如,汽车的品牌名称,发动机版本,型号名称和颜色字段可以与复制到指令合并。它将改善在这些字段上进行的搜索查询性能。
PUT movies
{
"mappings": {
"properties": {
"cars_infos": {
"type": "text"
},
"brand_name": {
"type": "text",
"copy_to": "cars_infos"
},
"engine_version": {
"type": "text",
"copy_to": "cars_infos"
},
"model ": {
"type": "text",
"copy_to": "cars_infos"
},
"color": {
"type": "text",
"copy_to": "cars_infos"
}
}
}
}
1234567891011121314151617181920212223242526
2、设置分片分配到指定节点
实战业务中经常遇到的业务场景问题:如何将分片设置非均衡分配,有新节点配置极高,能否多分片点过去?
某个 shard 分配在哪个节点上,一般来说,是由 ES 自动决定的。以下几种情况会触发分配动作:
- 1)新索引生成
- 2)索引的删除
- 3)新增副本分片
- 4)节点增减引发的数据均衡
ES 提供了一系列参数详细控制这部分逻辑,其中之一是:在异构集群的情为具有更好硬件的节点的分片分配分配权重。
为了分配权重,需要设置cluster.routing.allocation.balance.shard值,默认值为0.45f。
数值越大越倾向于在节点层面均衡分片。
实战:
PUT _cluster/settings
{
“transient” : {
“cluster.routing.allocation.balance.shard” : 0.60
}
}
123456
3、调整熔断内存比例大小
查询本身也会对响应的延迟产生重大影响。为了在查询时不触发熔断并导致Elasticsearch集群处于不稳定状态,
可以根据查询的复杂性将indices.breaker.total.limit设置为适合您的JVM堆大小。此设置的默认值是JVM堆的70%。
PUT /_cluster/settings
{
"persistent" : {
"indices.breaker.fielddata.limit" : "60%"
}
}
123456
最好为断路器设置一个相对保守点的值。更深原理推荐阅读:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_limiting_memory_usage.html。
《Elastic源码分析》作者张超指出:“Elasticsearch 7.0 增加了 indices.breaker.total.use_real_memory 配置项,可以更加精准的分析当前的内存情况,及时防止 OOM 出现。虽然该配置会增加一点性能损耗,但是可以提高 JVM 的内存使用率,增强了节点的保护机制。”
4、特定搜索场景,增加搜索线程池配置
默认情况下,Elasticsearch将主要用例是搜索。在需要增加检索并发性的情况下,可以增加用于搜索设置的线程池,与此同时,可以根据节点上的CPU中的核心数量多少斟酌减少用于索引的线程池。
举例:更改配置文件elasticsearch.yml增加如下内容:
thread_pool.search.queue_size: 500
#queue_size允许控制没有线程执行它们的挂起请求队列的初始大小。
12
官方:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-threadpool.html
5、打开自适应副本选择
应打开自适应副本选择。该请求将被重定向到响应最快的节点。
当存在多个数据副本时,elasticsearch可以使用一组称为自适应副本选择的标准,根据包含每个分片副本的节点的响应时间,服务时间和队列大小来选择数据的最佳副本。
这样可以提高查询吞吐量并减少搜索量大的应用程序的延迟。
这个配置默认是关闭的,实战打开方法:
PUT /_cluster/settings
{
"transient": {
"cluster.routing.use_adaptive_replica_selection": true
}
}
123456
存储设备的性能优化
磁盘在现代服务器上通常都是瓶颈。Elasticsearch 重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。这里有一些优化磁盘 I/O 的技巧:
使用 SSD。就像其他地方提过的, 他们比机械磁盘优秀多了。
使用 RAID 0。条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。
另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。
不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。
如果你用的是 EC2,当心 EBS。即便是基于 SSD 的 EBS,通常也比本地实例的存储要慢。
内部索引优化
Elasticsearch为了能快速找到某个term,先将所有的term排个序,然后根据二分法查找term,时间复杂度为logN,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库通过B-Tree的方式类似。
但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
在内存中用FST方式压缩term index,FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源。演示地址:Build your own FST
对于存储在磁盘上的倒排表同样也采用了压缩技术减少存储所占用的空间,更多可以阅读 Frame of Reference and Roaring Bitmaps。
调整配置参数
给每个文档指定有序的具有压缩良好的序列模式ID,避免随机的UUID-4 这样的 ID,这样的ID压缩比很低,会明显拖慢 Lucene。
对于那些不需要聚合和排序的索引字段禁用Doc values。Doc Values是有序的基于document => field value的映射列表;
不需要做模糊检索的字段使用 keyword类型代替 text 类型,这样可以避免在建立索引前对这些文本进行分词。
如果你的搜索结果不需要近实时的准确度,考虑把每个索引的 index.refreshinterval 改到 30s 。如果你是在做大批量导入,导入期间你可以通过设置这个值为 -1 关掉刷新,还可以通过设置 index.numberof_replicas: 0关闭副本。别忘记在完工的时候重新开启它。
避免深度分页查询建议使用Scroll进行分页查询。普通分页查询时,会创建一个from + size的空优先队列,每个分片会返回from + size 条数据,默认只包含文档id和得分score给协调节点,如果有n个分片,则协调节点再对(from + size)× n 条数据进行二次排序,然后选择需要被取回的文档。当from很大时,排序过程会变得很沉重占用CPU资源严重。
减少映射字段,只提供需要检索,聚合或排序的字段。其他字段可存在其他存储设备上,例如Hbase,在ES中得到结果后再去Hbase查询这些字段。
创建索引和查询时指定路由routing值,这样可以精确到具体的分片查询,提升查询效率。路由的选择需要注意数据的分布均衡。
JVM调优
确保堆内存最小值( Xms )与最大值( Xmx )的大小是相同的,防止程序在运行时改变堆内存大小。Elasticsearch 默认安装后设置的堆内存是 1 GB。可通过 …/config/jvm.option文件进行配置,但是最好不要超过物理内存的50%和超过32GB。
GC 默认采用CMS的方式,并发但是有STW的问题,可以考虑使用G1收集器。
重新开启它。
避免深度分页查询建议使用Scroll进行分页查询。普通分页查询时,会创建一个from + size的空优先队列,每个分片会返回from + size 条数据,默认只包含文档id和得分score给协调节点,如果有n个分片,则协调节点再对(from + size)× n 条数据进行二次排序,然后选择需要被取回的文档。当from很大时,排序过程会变得很沉重占用CPU资源严重。
减少映射字段,只提供需要检索,聚合或排序的字段。其他字段可存在其他存储设备上,例如Hbase,在ES中得到结果后再去Hbase查询这些字段。
创建索引和查询时指定路由routing值,这样可以精确到具体的分片查询,提升查询效率。路由的选择需要注意数据的分布均衡。
Elasticsearch优化
- 超出限制,清除内存已有的fielddata
elasticsearch.yml中添加 indices.fielddata.cache.size: 20% - 提升jvm内存
在jvm.options中修改内存分配-Xms8g -Xmx8g - 修改max_file_descriptors
修改/etc/security/limits.conf 值改为102400
4. ES锁住内存不进行swapping
bootstrap.mlockall: true
修改/etc/security/limits.conf添加
-
hard memlock unlimited
-
soft memlock unlimited
4、小结
- Elasticsearch集群有许多配置设置可以减少响应延迟,提升检索性能。 以上只是冰山一角。更多实践配置推荐阅读官方文档:
鼻祖级优化指南:https://www.elastic.co/guide/en/elasticsearch/reference/6.7/tune-for-search-speed.html - 本文授权翻译至国外Burak Altaş的博文,结合实战并仔细核对官网最新7.2版本的配置,进行了细节的调整和改动,实操性更强。
- 希望对你有帮助。
- 欢迎分享交流你在性能优化上的经验和教训。
推荐阅读:
[1] Elasticsearch设置必读更新清单:https://nickcanzoneri.com/elasticsearch-settings/
[2] 英文原文:https://medium.com/@burak.altas/how-to-optimize-elasticsearch-for-better-search-performance-283492e475f8