Fork me on GitHub

Elasticsearch性能调优总结

目录

[TOC]

背景

Elasticseach是一个基于Apache Lucene的开源检索引擎,允许用户近实时的存储、检索和分析数据。虽然 Elasticsearch 专为快速查询而设计,但其性能在很大程度上取决于应用使用的场景、索引的数据量以及应用用户查询数据的速度。本文主要参考Elasticseach官方文档和社区在集群调优上的经验,根据个人经验主要分为:集群的性能调优、集群稳定性调优、业务使用调优。

##第一部分 性能调优

###1.1 操作系统侧基线调优建议

####1.1.1 关闭交换分区(或降低Swappiness值)

swap空间是一块物理磁盘空间,操作系统使用这块空间保存从内存中换出的不常用内存数据,这样可以分配出更多的内存给当前使用的应用进程。

然而技术都是有适用场景的。相对于内存,磁盘的读写较慢(内存读写速度为纳秒级、磁盘的速度只能达到毫秒级),当系统发生大量的内存数据交换时,将影响系统运行性能。而elasticsearch对读写有较高的性能要求,如果进程使用的内存数据被写入swap磁盘,接着又换出读入内存,将极大影响性能,所以通常建议关闭或减少swap分区。

关于Linux系统swap交换分区的介绍可以参考下面的文章:

https://www.linux.com/news/all-about-linux-swap-space

  • 临时禁用swap(操作系统重启失效):
1
swapoff -a
  • 永久禁用swap:

    将/etc/fstab 文件中包含swap的行注释掉

1
sed -i '/swap/s/^/#/' /etc/fstabswapoff -a

对于应用混用部署环境中,如果不能关闭swap分区的场景,Linux提供swappiness参数(0-100)控制。swappiness=0的时候表示最大限度使用物理内存,swappiness=100表示积极使用swap分区,及时将不用的内存数据搬运到swap中。操作系统默认值为60,即物理内存使用率操作40%(100-60),开始使用swap分区。这个值决定操作系统交换内存的频率。可以预防正常情况下发生交换,但仍允许操作系统在紧急情况下发生交换。

  • 临时调整
1
sysctl vm.swappiness=10
  • 永久调整
1
2
3
4
5
# root用户
vi /etc/sysctl.conf
# 添加 vm.swappiness = 10
# 生效
sysctl -p

另外ElasticSearch 自身也提供相关优化参数。打开配置文件中的mlockall开关,它的作用就是运行JVM虚拟机时锁住内存,禁止操作系统将其内存数据交换出去。在elasticsearch.yml配置中修改如下:

1
bootstrap.mlockall: true

ElasticSearch 提供下面的API可以查询集群是否配置该参数:

1
2
curl -XGET localhost:9200/_nodes?filter_path=**.mlockall
##回显:{"nodes":{"VyKDGurkQiygV-of4B1ZAQ":{"process":{"mlockall":true}}}}

####1.1.2 其他操作系统资源限制调整

调整操作系统部分资源限制配置,提高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 修改系统资源限制
# 单用户可以打开的最大文件数量,可以设置为官方推荐的65536或更大些
echo "* - nofile 655360" >>/etc/security/limits.conf
# 单用户内存地址空间
echo "* - as unlimited" >>/etc/security/limits.conf
# 单用户线程数
echo "* - nproc 2056474" >>/etc/security/limits.conf
# 单用户文件大小
echo "* - fsize unlimited" >>/etc/security/limits.conf
# 单用户锁定内存
echo "* - memlock unlimited" >>/etc/security/limits.conf
# 单进程可以使用的最大map内存区域数量
echo "vm.max_map_count = 655300" >>/etc/sysctl.conf
# TCP全连接队列参数设置, 这样设置的目的是防止节点数较多(比如超过100)的ES集群中,节点异常重启时全连接队列在启动瞬间打满,造成节点hang住,整个集群响应迟滞的情况
echo "net.ipv4.tcp_abort_on_overflow = 1" >>/etc/sysctl.confecho "net.core.somaxconn = 2048" >>/etc/sysctl.conf
# 降低tcp alive time,防止无效链接占用链接数
echo 300 >/proc/sys/net/ipv4/tcp_keepalive_time

####1.1.3 多个path.data路径的设置

使用多个IO设备(设置多个path.data)存储shards,能够增加总的存储空间并提升IO性能。另外查询时shards并行检索,IO会分散负载到各个磁盘,提高查询效率。

ElasticSearch 2.0之前的版本,可以配置多个path.data路径,但是其相当于RAID 0,每个分片(shards)的数据会分布在所有的磁盘上。如果集群中一个节点上有一块盘坏了损坏,该节点上所有的shards都会损坏了。需要恢复该节点上的所有shards。
2.0.0版本之后,这个功能得到了优化:每个shards所有的数据只会在一块磁盘上面。这样即使一个节点的一块磁盘损坏了,也只是损失了该磁盘上的shards,其它磁盘上的shards安然无事。只需要恢复该块盘上的shards即可。提高了数据分布式存储的高可用。

集群升级到2.0.0版本时,旧版本一个shard分布到所有磁盘上的数据,会调整拷贝到一块盘上。

参考官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/master/path-settings.html

####1.1.4 磁盘挂载选项

服务器挂载磁盘时,可以做如下性能参数调优:

  • noatime:禁止记录访问时间戳,提高文件系统读写性能
  • data=writeback: 不记录data journal,提高文件系统写入性能
  • barrier=0:barrier保证journal先于data刷到磁盘,上面关闭了journal,这里的barrier也就没必要开启了
  • nobh:关闭buffer_head,防止内核打断大块数据的IO操作
1
mount -o noatime,data=writeback,barrier=0,nobh /dev/sda /es_data_dir

####1.1.5 磁盘设备优化

elasticsearch对读写性能要求较高,当然磁盘性能越高越好,生产建议使用固态SSD。读写速度媲美于内存,另外SSD磁盘采用电梯调度算法,提供了更智能的请求调度算法,不需要内核去做多余的调整 。

使用SSD(PCI-E接口SSD卡/SATA接口SSD盘)通常比机械硬盘(SATA盘/SAS盘)查询速度快5~10倍。但是写入性能提升不明显。

当然一分钱一分货,SSD比SAS贵不少。

###1.2 elasticsearch 配置文件优化

####1.2.1 增加写入buffer和bulk队列长度

elasticsearch在处理索引的时候会存在一个索引缓冲buffer,可以为其设置需要分配的缓冲buffer内存大小,这是一个全局设置。

1
2
3
# 这意味着分配给一个节点的总存储器的10%将被用作索引的缓冲区大小
indices.memory.index_buffer_size: 10%
thread_pool.bulk.queue_size: 1024

####1.2.2 计算disk使用量时,不考虑正在搬迁的shard

在规模比较大的集群中,可以防止新建shard时扫描所有的shard的元数据,提升shard的分配速度。

1
cluster.routing.allocation.disk.include_relocations: false

##第二部分 集群稳定性调优

###2.1 操作系统参数调优

主要为资源限制配置,参考1.1.2章节。

###2.2 Elasticsearch 配置

####2.2.1 JVM虚拟机参数

Elasticsearch由java语言编写开发。在 Java 中,所有的对象都分配在堆上,并通过一个指针进行引用。 普通对象指针(OOP)指向这些对象,通常为 CPU 字长的大小:32 位或 64 位,取决于你的处理器。指针引用的就是这个 OOP 值的字节位置。

对于 32 位的系统,意味着堆内存大小最大为 4 GB。对于 64 位的系统, 可以使用更大的内存,但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。更大的指针在主内存和各级缓存(例如 LLC,L1 等)之间移动数据的时候,会占用更多的带宽.

配置文件默认堆内存的大小为1G,官方建议将堆大小保持在接近32 GB(最佳建议30.5 GB阈值)。超过32G,jvm会禁用内存对象指针压缩技术,造成内存浪费。

关于堆大小的设置,elasticsearh官网有比较详细的说明:

https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html

-Xms和-Xmx设置为相同的值,推荐设置为:min(机器内存的一半左右,30.5G)。剩余留给系统cache使用。

另外jvm堆内存建议不要低于2G,否则有可能因为内存不足导致elasticsearch进程无法正常启动或内存溢出(OOM)。

假设你是土豪,机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。 也就是说不超过 64 GB 内存给 elasticsearch的堆内存,剩下的超过 64 GB 的内存给 Lucene。

####2.2.2 节点配置参数

设置内存熔断参数,防止写入或查询压力过高导致OOM,具体数值可根据使用场景调整。

1
2
3
indices.breaker.total.limit: 30% 
indices.breaker.request.limit: 6%
indices.breaker.fielddata.limit: 3%

减少查询使用的cache,避免cache占用过多的jvm内存,具体数值可根据使用场景调整。

1
2
indices.queries.cache.count: 500 
indices.queries.cache.size: 5%

单机多节点时,主从shard分配以ip为依据,分配到不同的机器上,避免单机挂掉导致数据丢失。

1
2
cluster.routing.allocation.awareness.attributes: ip 
node.attr.ip: 1.1.1.1

###2.3 集群使用方式

####2.3.1 设置专用master节点
Elasticsearch集群的元数据管理、Index的新建、删除操作、节点的加入和隔离、定期将整个集群的健康状态信息广播给各节点等管理任务,均由master节点角色负责。当集群的节点数较大时,集群的管理任务需要消耗大量资源。建议设置专用master节点,master只负责集群管理工作,不负责存储数据(无数据读写压力),提高集群管理的稳定性。

1
2
3
4
5
6
7
8
9
# 专有master节点的配置文件(elasticsearch.yml)增加如下属性:
node.master: true
node.data: false
node.ingest: false

# 数据节点的配置文件增加如下属性(与上面的属性相反):
node.master: false
node.data: true
node.ingest: true

2.3.2 大内存物理机多实例部署

之前章节也提到,假如你的机器有128G内存,可以创建两个Data Node实例,使用32G内存。也就是说64G内存给ES的堆内存,剩下的64G给Lucene。如果你选择第二种,你需要配置(默认是false):

1
cluster.routing.allocation.same_shard.host:true

这会防止同一个shard的主副本存在同一个物理机上(因为如果都在一个机器上,副本的高可用性就没有了)

####2.3.3 控制集群中index(索引)和shard(分片)总量

集群中master节点负责集群元数据管理,定期会同步给各节点(集群每个节点都会存储一份)。

Elasticsearch元数据存储在clusterstate中。例如所有节点元信息(indices、节点各种统计参数)、所有index/shard的元信息(mapping, location, size)、元数据ingest等。

elasticsearch在创建新shard时,要根据现有的分片分布情况指定分片分配策略,从而使各个节点上的分片数基本一致,此过程中就需要深入遍历clusterstate。当集群中的index/shard过多时,clusterstate结构会变得过于复杂,导致遍历clusterstate效率低下,集群响应迟滞。

当index/shard数量过多时,可以考虑从以下几方面优化:

  • 降低数据量较小的index的shard数量
  • 把一些有关联的index合并成一个index
  • 数据按某个维度做拆分,写入多个集群

####2.3.4 Segment Memory优化

elasticsearch底层使用Lucene实现存储,Lucene的一个index由若干segment组成,每个segment都会建立自己的倒排索引用于数据查询。Lucene为了加速查询,为每个segment的倒排做了一层前缀索引,这个索引在Lucene4.0以后采用的数据结构是FST (Finite State Transducer)。Lucene加载segment的时候将其全量装载到内存中,加快查询速度。

这部分内存被称为Segment Memory, 常驻内存,占用heap,无法被GC。

前面提到,为利用JVM的对象指针压缩技术来节约内存,通常建议JVM内存分配不要超过32G。当集群的数据量过大时,Segment Memory会吃掉大量的堆内存,而JVM内存空间有限,此时就需要想办法降低Segment Memory的使用量了。

常用方法有:

  • 定期清理删除不使用的index

  • 对于不常访问的index,可以通过close接口将其关闭,用到时再打开(此时数据仍然保存在磁盘,只是释放掉了内存,无法检索,需要时可以重启open)

  • 通过force_merge api接口强制合并segment,降低segment数量,从而减少Segment Memory

    1
    2
    > curl -XPOST "http://localhost:9200/library/_forcemerge?max_num_segments=1
    >

###2.4 温热数据分离架构

当Elasticsearch集群用于大量实时数据分析的场景时,elasticsearch官方推荐使用基于时间的索引,并且集群使用三种不同类型的节点:Master、Hot-Node、Warm-Node,进行结构分层。即所谓的“Hot-Warm”集群架构。

参考官方文章:

https://www.elastic.co/blog/hot-warm-architecture-in-elasticsearch-5-x

第三部分 业务使用调优

###3.1 控制字段的存储选项

elasticsearch底层使用Lucene存储数据,主要包括行存(StoreFiled)、列存(DocValues)和倒排索引(InvertIndex)三部分。 大多数使用场景中,没有必要同时存储这三个部分,可以通过下面的参数来做适当调整:

  • StoreFiled: 行存,其中占比最大的是source字段,它控制doc原始数据的存储。在写入数据时,elasticsearch把doc原始数据的整个json结构体当做一个string,存储为source字段。查询时,可以通过source字段拿到当初写入时的整个json结构体。 所以,如果没有取出整个原始json结构体的需求,可以通过下面的命令,在mapping中关闭source字段或者只在source中存储部分字段,数据查询时仍可通过elasticsearch的docvaluefields获取所有字段的值。

    注意:关闭source后, update, updatebyquery, reindex等接口将无法正常使用,所以有update等需求的index不能关闭source。

1
2
3
4
# 关闭 _source
PUT my_index {"mappings": {"my_type": {"_source": {"enabled": false}}}}
# _source只存储部分字段,通过includes指定要存储的字段或者通过excludes滤除不需要的字段,例如
PUT my_index{"mappings": {"_doc": {"_source": {"includes": ["*.count","meta.*"], "excludes": ["meta.description","meta.other.*"]}}}}
  • docvalues:控制列存。 ES主要使用列存来支持sorting, aggregations和scripts功能,对于没有上述需求的字段,可以通过下面的命令关闭docvalues,降低存储成本。
1
PUT my_index{"mappings": {"my_type": {"properties": {"session_id": {"type": "keyword",   "doc_values": false}}}}}
  • index:控制倒排索引。 ES默认对于所有字段都开启了倒排索引,用于查询。对于没有查询需求的字段,可以通过下面的命令关闭倒排索引。
1
PUT my_index{"mappings": {"my_type": {"properties": {"session_id": {"type": "keyword",   "index": false}}}}}
  • all:ES的一个特殊的字段,ES把用户写入json的所有字段值拼接成一个字符串后,做分词,然后保存倒排索引,用于支持整个json的全文检索。 这种需求适用的场景较少,可以通过下面的命令将all字段关闭,节约存储成本和cpu开销。(ES 6.0+以上的版本不再支持_all字段,不需要设置)
1
PUT /my_index{"mapping": {"my_type": {"_all": {"enabled": false}}}}
  • fieldnames:该字段用于exists查询,来确认某个doc里面有无一个字段存在。若没有这种需求,可以将其关闭。
1
PUT /my_index{"mapping": {"my_type": {"_field_names": {"enabled": false}}}}

###3.2 开启最佳压缩

对于打开了上述_source字段的index,可以通过下面的命令来把Lucene适用的压缩算法替换成 DEFLATE,提高数据压缩率。

1
2
3
4
PUT /my_index/_settings
{
"index.codec": "best_compression"
}

###3.3 bulk批量写入

在向elasticsearch集群写入数据时,建议尽量使用bulk接口批量写入,提高写入效率。每个bulk请求的doc数量设定区间推荐为1k~1w,具体可根据业务场景选取一个适当的数量。

例如Logstash向elasticsearch集群写数时,其中batch size是一个重要的调优参数(优化的大小需要根据doc的大小和服务器性能来确定)。如果写入过程中遇到大量EsRejectedExecutionException说明elasticsearch集群索引性能已经达到极限了。

1
2
3
4
5
POST _bulk
{ "index" : { "_index" : "test", "_type" : "type1" } }
{ "field1" : "value1" }
{ "index" : { "_index" : "test", "_type" : "type1" } }
{ "field1" : "value2" }

###3.4 调整translog同步策略

默认情况下,translog的持久化策略是,对于每个写入请求都做一次flush,刷新translog数据到磁盘上。这种频繁的磁盘IO操作是严重影响写入性能的,如果可以接受一定概率的数据丢失(这种硬件故障的概率很小),可以通过下面的命令调整 translog 持久化策略为异步周期性执行,并适当调整translog的刷盘周期。

1
2
3
4
5
6
7
8
9
10
11
PUT my_index
{
"settings": {
"index": {
"translog": {
"sync_interval": "5s",
"durability": "async"
}
}
}
}

###3.5 适当增加refresh时间间隔
elasticsearch为了提高索引性能,数据的写入采用延迟写入机制。数据首先写入内存,当操作一定时间间隔(index.refresh_interval)会统一发起一次写入磁盘的操作。即将内存中的segment数据写入磁盘,也就是这时候我们才能对数据进行访问。所以严格的说elasticsearch提供的近实时检索功能,这个延时大小就是index.refresh_interval参数(默认为1秒)决定的。

默认情况下,elasticsearch每一秒会refresh一次,产生一个新的segment,这样会导致产生的segment较多,从而segment merge较为频繁,系统开销较大。如果对数据的实时检索要求较低,可以提高refresh的时间间隔,降低系统开销。

1
2
3
4
5
6
7
8
PUT my_index
{
"settings": {
"index": {
"refresh_interval" : "30s"
}
}
}

###3.6 merge并发控制

elasticsearch的一个index由多个shard组成,而一个shard其实就是一个Lucene的index,它又由多个segment组成,且Lucene会不断地把一些小的segment合并成一个大的segment,这个过程被称为merge。

默认值是Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2)),当节点配置的cpu核数较高时,merge占用的资源可能会偏高,影响集群的性能,可以通过下面的命令调整某个index的merge过程的并发度:

1
2
3
4
PUT /my_index/_settings
{
"index.merge.scheduler.max_thread_count": 2
}

###3.7 写入数据不指定_id,让elasticsearch自动产生

文档唯一标识由四个元数据字段组成:_id:文档的字符串 ID;_type:文档的类型名;_index:文档所在的索引

_uid:_type 和 _id 连接成的 type#id。

当用户指定_id写入数据时,elasticsearch会先发起查询来确定index中是否已经有相同id的doc存在,若有则先删除原有doc再写入新doc。这样每次写入时,都会耗费一定的资源做查询。如果用户写入数据时不指定id,则通过内部算法产生一个随机的id,并且保证id的唯一性,这样就可以跳过前面查询id的步骤,提高写入效率。 所以,在不需要通过id字段去重、update的使用场景中,写入不指定id可以提升写入速率。

自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突。

1
2
3
4
5
6
7
8
# 写入时指定_id,这里_id=1
POST _bulk
{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
{ "field1" : "value1" }
# 写入时不指定_id
POST _bulk
{ "index" : { "_index" : "test", "_type" : "type1" } }
{ "field1" : "value1" }

###3.8 使用routing

Elasticsearch的路由机制与其分片机制直接相关。路由即通过哈希算法,将具有相同哈希值的文档放置到同一个主分片中。这里机制原理和通过哈希算法实现数据库负载均衡原理是相同的。

Elasticsearch有一个默认的路由算法:它会将文档的ID值作为依据将其哈希到相应的主分片上,这种算法基本上会保持所有数据在所有分片上的一个平均分布,而不会产生数据热点。

默认的Routing模式在很多情况下都是能满足需求的。但是在我们更深入地理解我们的数据的特征之后,使用自定义的Routing模式可能会带来更好的性能。

所有的文档API(get,index,delete,update和mget)都能接收一个routing参数,可以用来形成个性化文档分片映射。一个个性化的routing值可以确保相关的文档存储到同样的分片上。

参考官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-routing-field.html

对于数据量较大的index,一般会配置多个shard来分摊压力。这种场景下,一个查询会同时搜索所有的shard,然后再将各个shard的结果合并后,返回给用户。具体过程如下:

  1. 查询的请求会被发送到一个节点

  2. 接收到这个请求的节点,将这个查询广播到这个索引的每个分片上(可能是主分片,也可能是复制分片)

  3. 每个分片执行这个搜索查询并返回结果

  4. 结果在通道节点上合并、排序并返回给用户

对于高并发的小查询场景,每个分片通常仅抓取极少量数据,此时查询过程中的调度开销远大于实际读取数据的开销,且查询速度取决于最慢的一个分片。

开启routing功能后,会将routing相同的数据写入到同一个分片中(也可以是多个,由index.routingpartitionsize参数控制)。如果查询时指定routing,那么只会查询routing指向的那个分片,可显著降低调度开销,提升查询效率。

routing的使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 写入
PUT my_index/my_type/1?routing=user1
{
"title": "This is a document"
}

# 查询
GET my_index/_search?routing=user1,user2
{
"query": {
"match": {
"title": "document"
}
}
}

###3.9 为string类型的字段选取合适的存储方式

  • 存为text类型的字段(string字段默认类型为text): 做分词后存储倒排索引,支持全文检索,可以通过下面几个参数优化其存储方式:
    • norms:用于在搜索时计算该doc的_score(代表这条数据与搜索条件的相关度),如果不需要评分,可以将其关闭。
    • indexoptions:控制倒排索引中包括哪些信息(docs、freqs、positions、offsets)。对于不太注重score/highlighting的使用场景,可以设为 docs来降低内存/磁盘资源消耗。
    • fields: 用于添加子字段。对于有sort和聚合查询需求的场景,可以添加一个keyword子字段以支持这两种功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"title": {
"type": "text",
"norms": false,
"index_options": "docs",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}
}
  • 存为keyword类型的字段: 不做分词,不支持全文检索。text分词消耗CPU资源,冗余存储keyword子字段占用存储空间。如果没有全文索引需求,只是要通过整个字段做搜索,可以设置该字段的类型为keyword,提升写入速率,降低存储成本。 设置字段类型的方法有两种:一是创建一个具体的index时,指定字段的类型;二是通过创建template,控制某一类index的字段类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 1. 通过mapping指定 tags 字段为keyword类型
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"tags": {
"type": "keyword"
}
}
}
}
}

# 2. 通过template,指定my_index*类的index,其所有string字段默认为keyword类型
PUT _template/my_template
{
"order": 0,
"template": "my_index*",
"mappings": {
"_default_": {
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword",
"ignore_above": 256
}
}
}
]
}
},
"aliases": {}
}

###3.10 查询时,使用query-bool-filter组合取代普通query

默认情况下,ES通过一定的算法计算返回的每条数据与查询语句的相关度,并通过score字段来表征。但对于非全文索引的使用场景,用户并不care查询结果与查询条件的相关度,只是想精确的查找目标数据。此时,可以通过query-bool-filter组合来让ES不计算score,并且尽可能的缓存filter的结果集,供后续包含相同filter的查询使用,提高查询效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 普通查询
POST my_index/_search
{
"query": {
"term" : { "user" : "Kimchy" }
}
}

# query-bool-filter 加速查询
POST my_index/_search
{
"query": {
"bool": {
"filter": {
"term": { "user": "Kimchy" }
}
}
}
}

###3.11 index命名按日期滚动

写入elasticsearch的数据最好通过某种方式分割,存入不同的index。常见的做法是将数据按模块、功能分类,写入不同的index,然后按照时间去滚动生成index。这样做的好处是各种数据分开管理不会混淆,也易于提高查询效率。同时index按时间滚动,数据过期时删除整个index,要比一条条删除数据效率高很多,因为删除整个index是直接删除底层文件,而delete by query是查询-标记-删除。

举例说明,假如有两个source1和source2两个数据生产者产生的数据,那么index规划可以是这样的:一类index名称是source1 + {日期},另一类index名称是source2+ {日期}。

对于名字中的日期,可以在写入数据时自己指定精确的日期,也可以通过elasticsearch的ingest pipeline中的index-name-processor实现(会有写入性能损耗)。参考官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/5.6/date-index-name-processor.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# module_a 类index
- 创建index:
PUT module_a@2018_01_01
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}
PUT module_a@2018_01_02
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}
...

- 查询数据:
GET module_a@*/_search

###3.12 按需控制index的分片数和副本数

分片(shard):Elasticsearch中的一个索引(index)由多个分片(shard)组成,每个shard承载index的一部分数据。

副本(replica):index也可以设定副本数(numberofreplicas),也就是同一个shard有多少个备份。对于查询压力较大的index,可以考虑提高副本数(numberofreplicas),通过多个副本均摊查询压力。

Elasticsearch默认副本数量为3,能提高集群的业务检索性能,但是会影响索引的写入性能(写入过程需要等待所有副本写完,才算完成一次更新写入)。

shard数量设置过多或过低都会引发一些问题:

  • shard数量过多,则批量写入、查询请求被分割为过多的子写入、查询,导致该index的写入、查询拒绝率上升。
  • 对于数据量较大的index,当其shard数量过小时,无法充分利用节点资源,造成机器资源利用率不高或不均衡,影响写入、查询的效率。

对于每个index的shard数量,可以根据数据总量、写入压力、节点数量等综合考量后设定,然后根据数据增长状态定期检测下shard数量是否合理,推荐方案是:

  • 对于数据量较小(100GB以下)的index,往往写入压力查询压力相对较低,一般设置3~5个shard,副本设置为1(也就是一主一从) 。
  • 对于数据量较大(100GB以上)的index:
    • 一般把单个shard的数据量控制在(20GB~50GB)
    • 让index压力分摊至多个节点:可通过index.routing.allocation.totalshardsper_node参数,强制限定一个节点上该index的shard数量,让shard尽量分配到不同节点上
    • 综合考虑整个index的shard数量,如果shard数量(不包括副本)超过50个,就很可能引发拒绝率上升的问题,此时可考虑把该index拆分为多个独立的index,分摊数据量,同时配合routing使用,降低每个查询需要访问的shard数量。

所以有一种优化建议:在index创建写入过程中将副本数设为0,待index完成后将副本数按需量改回来,这样也可以提高效率。

3.13 增加负载均衡(查询)节点

负载均衡节点需要如下配置:

1
2
node.master: false
node.data: false

该节点服务器即不会被选作主节点,也不会存储任何索引数据,节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点主要作用是负载均衡。

在查询的时候,通常会涉及到从多个节点服务器上查询数据,并请求分发到多个指定的节点服务器,并对各个节点服务器返回的结果进行一个汇总处理,最终返回给客户端。

性能方面对该节点要求是内存越大越好。

###3.14 关闭data节点服务器中的http功能

针对ElasticSearch集群中的所有data节点,不用开启http服务。将其中的配置参数这样设置:

1
http.enabled: false

同时不安装head, marvel等监控插件,这样保证data节点服务器只需处理创建/更新/删除/查询索引数据等操作。
http功能可以在非数据节点服务器上开启,上述相关的监控插件也安装到这些服务器上,用于监控ElasticSearch集群状态等数据信息。这样做一来出于数据安全考虑,二来出于服务性能考虑。

###3.15 调整集群热数据分布

集群在分配数据分片的时候,如果不指定路由,分片是均匀分派到各个节点上。当节点上存在太多热点数据分片时候,我们可以通过API手动进行调整分片的存储位置。

下面的API中,indexname:需要移动的索引名称,shard:分片编号,from_node:分片的原归属节点名称,to_node:分片的移动目的节点名称。

1
2
3
4
5
6
curl -XPOST 'http://localhost:9200/_cluster/reroute' -d '{
"commands" : [
{"move" :{
"index" : "indexname", "shard" : 1,
"from_node" : "nodename", "to_node" : "nodename"}}]
}'

##总结

Elasticsearch 的性能取决于很多因素,包括文档结构、文档大小、索引设置 / 映射、请求率、数据集大小和查询命中次数等等。每个优化策略都自身的优化需求场景,没有技术银弹。因此,针对需求场景进行性能测试、收集数据,然后调优非常重要。

##参考文献

1、A Heap of Trouble: Managing Elasticsearch’s Managed Heap 链接:https://www.elastic.co/blog/a-heap-of-trouble

2、elasticsearch官方网站 链接:https://www.elastic.co/

3、《深入理解Elasticsearch》

0%