Elasticsearch调优

背景
Elasticsearch(ES)作为NOSQL+搜索引擎的有机结合体,不仅有近实时的查询能力,还具有强大的聚合分析能力。因此在全文检索、日志分析、监控系
统、数据分析等领域ES均有广泛应用。而完整的Elastic Stack体系(Elasticsearch、Logstash、Kibana、Beats),更是提供了数据采集、清洗、存储、
可视化的整套解决方案。
本文从性能和稳定性两方面,从linux参数调优、ES节点配置和ES使用方式三个角度入手,介绍ES调优的基本方案。当然,ES的调优绝不能
一概而论,需要根据实际业务场景做适当的取舍和调整,文中的疏漏之处也随时欢迎批评指正。

性能调优
一 Linux参数调优
1. 关闭交换分区,防止内存置换降低性能。 将/etc/fstab 文件中包含swap的行注释掉
1. sed -i '/swap/s/^/#/' /etc/fstab
2. swapoff -a
2. 磁盘挂载选项
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
3. 对于SSD磁盘,采用电梯调度算法,因为SSD提供了更智能的请求调度算法,不需要内核去做多余的调整 (仅供参考)
1. echo noop > /sys/ block/sda/queue/scheduler
二 ES节点配置
conf/elasticsearch.yml文件:
1. 适当增大写入buffer和bulk队列长度,提高写入性能和稳定性 1. indices.memory.index_buffer_size: 15 %
2. thread_pool.bulk.queue_size: 1024
2. 计算disk使用量时,不考虑正在搬迁的shard
在规模比较大的集群中,可以防止新建shard时扫描所有shard的元数据,提升shard分配速度。
1. cluster.routing.allocation.disk.include_relocations: false
三 ES使用方式
1. 控制字段的存储选项
ES底层使用Lucene存储数据,主要包括行存(StoreFiled)、列存(DocValues)和倒排索引(InvertIndex)三部分。 大多数使用场景中,没有必要同时
存储这三个部分,可以通过下面的参数来做适当调整:
StoreFiled: 行存,其中占比最大的是source字段,它控制doc原始数据的存储。在写入数据时,ES把doc原始数据的整个json结构体当做一个
string,存储为source字段。查询时,可以通过source字段拿到当初写入时的整个json结构体。 所以,如果没有取出整个原始json结构体的需求,
可以通过下面的命令,在mapping中关闭source字段或者只在source中存储部分字段,数据查询时仍可通过ES的docvaluefields获取所有字段的
值。
注意: 关闭source后, update, updatebyquery, reindex等接口将无法正常使用,所以有update等需求的index不能关闭source。
1. # _source
2. PUT my_index
3. {
4. "mappings": {
5. "my_type": {
6. "_source": {
7. "enabled": false
8. }
9. }
10. }
11. }
12.
13. # _sourceincludesexcludes
14. PUT my_index
15. {
16. "mappings": {
17. "_doc": {
18. "_source": {
19. "includes": [
20. "*.count",
21. "meta.*"
22. ],
23. "excludes": [
24. "meta.description",
25. "meta.other.*"
26. ]
27. }
28. }
29. }
30. }

docvalues:控制列存。
ES主要使用列存来支持sorting, aggregations和scripts功能,对于没有上述需求的字段,可以通过下面的命令关闭docvalues,降低存储成本。
1. PUT my_index
2. {
3. "mappings": {
4. "my_type": {
5. "properties": {
6. "session_id": {
7. "type": "keyword",
8. "doc_values": false
9. }
10. 10. }
11. }
12. }
13. }

index:控制倒排索引。
ES默认对于所有字段都开启了倒排索引,用于查询。对于没有查询需求的字段,可以通过下面的命令关闭倒排索引。
1. PUT my_index
2. {
3. "mappings": {
4. "my_type": {
5. "properties": {
6. "session_id": {
7. "type": "keyword",
8. "index": false
9. }
10. }
11. }
12. }
13. }

all:ES的一个特殊的字段,ES把用户写入json的所有字段值拼接成一个字符串后,做分词,然后保存倒排索引,用于支持整个json的全文检索。
这种需求适用的场景较少,可以通过下面的命令将all字段关闭,节约存储成本和cpu开销。(ES 6.0+以上的版本不再支持_all字段,不需要设置)
1. PUT /my_index
2. {
3. "mapping": {
4. "my_type": {
5. "_all": {
6. "enabled": false
7. }
8. }
9. }
10. }

fieldnames:该字段用于exists查询,来确认某个doc里面有无一个字段存在。若没有这种需求,可以将其关闭。
1. PUT /my_index
2. {
3. "mapping": {
4. "my_type": {
5. "_field_names": {
6. "enabled": false
7. }
8. }
9. }
10. }

2. 开启最佳压缩
对于打开了上述_source字段的index,可以通过下面的命令来把lucene适用的压缩算法替换成 DEFLATE,提高数据压缩率。
1. PUT /my_index/_settings
2. {
3. "index.codec": "best_compression"
4. }

3. bulk批量写入
写入数据时尽量使用下面的bulk接口批量写入,提高写入效率。每个bulk请求的doc数量设定区间推荐为1k~1w,具体可根据业务场景选取一个适当的数
量。
1. POST _bulk
2. { "index" : { "_index" : "test", "_type" : "type1" } }
3. { "field1" : "value1" }
4. { "index" : { "_index" : "test", "_type" : "type1" } }
5. 5. { "field1" : "value2" }

4. 调整translog同步策略
默认情况下,translog的持久化策略是,对于每个写入请求都做一次flush,刷新translog数据到磁盘上。这种频繁的磁盘IO操作是严重影响写入性能的,如
果可以接受一定概率的数据丢失(这种硬件故障的概率很小),可以通过下面的命令调整 translog 持久化策略为异步周期性执行,并适当调整translog的刷
盘周期。
1. PUT my_index
2. {
3. "settings": {
4. "index": {
5. "translog": {
6. "sync_interval": "5s",
7. "durability": "async"
8. }
9. }
10. }
11. }

5. 调整refresh_interval
写入Lucene的数据,并不是实时可搜索的,ES必须通过refresh的过程把内存中的数据转换成Lucene的完整segment后,才可以被搜索。默认情况下,ES每
一秒会refresh一次,产生一个新的segment,这样会导致产生的segment较多,从而segment merge较为频繁,系统开销较大。如果对数据的实时可见性
要求较低,可以通过下面的命令提高refresh的时间间隔,降低系统开销。
1. PUT my_index
2. {
3. "settings": {
4. "index": {
5. "refresh_interval" : "30s"
6. }
7. }
8. }

6. merge并发控制
ES的一个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. PUT /my_index/_settings
2. {
3. "index.merge.scheduler.max_thread_count": 2
4. }

7. 写入数据不指定_id,让ES自动产生
当用户显示指定id写入数据时,ES会先发起查询来确定index中是否已经有相同id的doc存在,若有则先删除原有doc再写入新doc。这样每次写入时,ES都
会耗费一定的资源做查询。如果用户写入数据时不指定doc,ES则通过内部算法产生一个随机的id,并且保证id的唯一性,这样就可以跳过前面查询id的步
骤,提高写入效率。 所以,在不需要通过id字段去重、update的使用场景中,写入不指定id可以提升写入速率。基础架构部数据库团队的测试结果显示,无
id的数据写入性能可能比有_id的高出近一倍,实际损耗和具体测试场景相关。
1. # _id
2. POST _bulk
3. { "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
4. { "field1" : "value1" }
5.
6. # _id
7. POST _bulk
8. { "index" : { "_index" : "test", "_type" : "type1" } }
9. { "field1" : "value1" }

8. 使用routing
对于数据量较大的index,一般会配置多个shard来分摊压力。这种场景下,一个查询会同时搜索所有的shard,然后再将各个shard的结果合并后,返回给用
户。对于高并发的小查询场景,每个分片通常仅抓取极少量数据,此时查询过程中的调度开销远大于实际读取数据的开销,且查询速度取决于最慢的一个分
片。开启routing功能后,ES会将routing相同的数据写入到同一个分片中(也可以是多个,由index.routingpartitionsize参数控制)。如果查询时指定
routing,那么ES只会查询routing指向的那个分片,可显著降低调度开销,提升查询效率。 routing的使用方式如下:
1. #
2. PUT my_index/my_type/1?routing=user1
3. {
4. "title": "This is a document"
5. }
6.
7. #
8. GET my_index/_search?routing=user1,user2
9. {
10. "query": {
11. "match": {
12. "title": "document"
13. }
14. }
15. }

9. 为string类型的字段选取合适的存储方式
存为text类型的字段(string字段默认类型为text): 做分词后存储倒排索引,支持全文检索,可以通过下面几个参数优化其存储方式:
norms:用于在搜索时计算该doc的_score(代表这条数据与搜索条件的相关度),如果不需要评分,可以将其关闭。
indexoptions:控制倒排索引中包括哪些信息(docs、freqs、positions、offsets)。对于不太注重score/highlighting的使用场景,可
以设为 docs来降低内存/磁盘资源消耗。
fields: 用于添加子字段。对于有sort和聚合查询需求的场景,可以添加一个keyword子字段以支持这两种功能。
1. PUT my_index
2. {
3. "mappings": {
4. "my_type": {
5. "properties": {
6. "title": {
7. "type": "text",
8. "norms": false,
9. "index_options": "docs",
10. "fields": {
11. "raw": {
12. "type": "keyword"
13. }
14. }
15. }
16. }
17. }
18. }
19. }

存为keyword类型的字段: 不做分词,不支持全文检索。text分词消耗CPU资源,冗余存储keyword子字段占用存储空间。如果没有全文索引需
求,只是要通过整个字段做搜索,可以设置该字段的类型为keyword,提升写入速率,降低存储成本。 设置字段类型的方法有两种:一是创建一个
具体的index时,指定字段的类型;二是通过创建template,控制某一类index的字段类型。
1. # 1. mapping tags keyword
2. PUT my_index
3. {
4. "mappings": {
5. "my_type": {
6. "properties": {
7. "tags": {
8. "type": "keyword"
9. }
10. }
11. }
12. }
13. }
14.
15. 14.
15. # 2. templatemy_index*indexstringkeyword
16. PUT _template/my_template
17. {
18. "order": 0,
19. "template": "my_index*",
20. "mappings": {
21. "_default_": {
22. "dynamic_templates": [
23. {
24. "strings": {
25. "match_mapping_type": "string",
26. "mapping": {
27. "type": "keyword",
28. "ignore_above": 256
29. }
30. }
31. }
32. ]
33. }
34. },
35. "aliases": {}
36. }

10. 查询时,使用query-bool-filter组合取代普通query
默认情况下,ES通过一定的算法计算返回的每条数据与查询语句的相关度,并通过score字段来表征。但对于非全文索引的使用场景,用户并不care查询结果
与查询条件的相关度,只是想精确的查找目标数据。此时,可以通过query-bool-filter组合来让ES不计算score,并且尽可能的缓存filter的结果集,供后续
包含相同filter的查询使用,提高查询效率。
1. #
2. POST my_index/_search
3. {
4. "query": {
5. "term" : { "user" : "Kimchy" }
6. }
7. }
8.
9. # query-bool-filter
10. POST my_index/_search
11. {
12. "query": {
13. "bool": {
14. "filter": {
15. "term": { "user": "Kimchy" }
16. }
17. }
18. }
19. }

11. index按日期滚动,便于管理
写入ES的数据最好通过某种方式做分割,存入不同的index。常见的做法是将数据按模块/功能分类,写入不同的index,然后按照时间去滚动生成index。这
样做的好处是各种数据分开管理不会混淆,也易于提高查询效率。同时index按时间滚动,数据过期时删除整个index,要比一条条删除数据或
deletebyquery效率高很多,因为删除整个index是直接删除底层文件,而deletebyquery是查询-标记-删除。
举例说明,假如有[modulea,moduleb]两个模块产生的数据,那么index规划可以是这样的:一类index名称是modulea + {日期},另一类index名称是
module_b+ {日期}。对于名字中的日期,可以在写入数据时自己指定精确的日期,也可以通过ES的ingest pipeline中的 index-name-processor 实现(会有
写入性能损耗)。
1. # module_a index
2. - index
3. PUT module_a@2018_01_01
4. {
5. "settings" : {
6. "index" : {
7. "number_of_shards" : 3,
8. "number_of_replicas" : 2
9. }
10. 10. }
11. }
12. PUT module_a@2018_01_02
13. {
14. "settings" : {
15. "index" : {
16. "number_of_shards" : 3,
17. "number_of_replicas" : 2
18. }
19. }
20. }
21. ...
22.
23. -
24. GET module_a@*/_search
25.
26.
27. # module_b index
28.
29. - index
30. PUT module_b@2018_01_01
31. {
32. "settings" : {
33. "index" : {
34. "number_of_shards" : 3,
35. "number_of_replicas" : 2
36. }
37. }
38. }
39. PUT module_b@2018_01_02
40. {
41. "settings" : {
42. "index" : {
43. "number_of_shards" : 3,
44. "number_of_replicas" : 2
45. }
46. }
47. }
48. ...
49.
50. -

51. GET module_b@*/_search
12. 按需控制index的分片数和副本数
分片(shard):一个ES的index由多个shard组成,每个shard承载index的一部分数据。
副本(replica):index也可以设定副本数(numberofreplicas),也就是同一个shard有多少个备份。对于查询压力较大的index,可以考虑提高副本数
(numberofreplicas),通过多个副本均摊查询压力。
shard数量(numberofshards)设置过多或过低都会引发一些问题:shard数量过多,则批量写入/查询请求被分割为过多的子写入/查询,导致该index的写
入、查询拒绝率上升;对于数据量较大的inex,当其shard数量过小时,无法充分利用节点资源,造成机器资源利用率不高 或 不均衡,影响写入/查询的效
率。
对于每个index的shard数量,可以根据数据总量、写入压力、节点数量等综合考量后设定,然后根据数据增长状态定期检测下shard数量是否合理。基础架
构部数据库团队的推荐方案是:
对于数据量较小(100GB以下)的index,往往写入压力查询压力相对较低,一般设置3~5个shard,numberofreplicas设置为1即可(也就是一主
一从,共两副本) 。
对于数据量较大(100GB以上)的index:
一般把单个shard的数据量控制在(20GB~50GB)
让index压力分摊至多个节点:可通过index.routing.allocation.totalshardsper_node参数,强制限定一个节点上该index的shard数量,
让shard尽量分配到不同节点上
综合考虑整个index的shard数量,如果shard数量(不包括副本)超过50个,就很可能引发拒绝率上升的问题,此时可考虑把该index拆分
为多个独立的index,分摊数据量,同时配合routing使用,降低每个查询需要访问的shard数量。
稳定性调优 一 Linux参数调优
1. #
2. # 65536
3. echo "* - nofile 655360" >>/etc/security/limits.conf
4. #
5. echo "* - as unlimited" >>/etc/security/limits.conf
6. #
7. echo "* - nproc 2056474" >>/etc/security/limits.conf
8. #
9. echo "* - fsize unlimited" >>/etc/security/limits.conf
10. #
11. echo "* - memlock unlimited" >>/etc/security/limits.conf
12.
13. # map
14. echo "vm.max_map_count = 655300" >>/etc/sysctl.conf
15.
16. # TCP 100EShang
17. echo "net.ipv4.tcp_abort_on_overflow = 1" >>/etc/sysctl.conf
18. echo "net.core.somaxconn = 2048" >>/etc/sysctl.conf
19.
20. # tcp alive time
21. echo 300 >/proc/sys/net/ipv4/tcp_keepalive_time

二 ES节点配置
1. jvm.options
-Xms和-Xmx设置为相同的值,推荐设置为机器内存的一半左右,剩余一半留给系统cache使用。
jvm内存建议不要低于2G,否则有可能因为内存不足导致ES无法正常启动或OOM
jvm建议不要超过32G,否则jvm会禁用内存对象指针压缩技术,造成内存浪费
2. elasticsearch.yml
设置内存熔断参数,防止写入或查询压力过高导致OOM,具体数值可根据使用场景调整。 indices.breaker.total.limit: 30% indices.breaker.
request.limit: 6% indices.breaker.fielddata.limit: 3%
调小查询使用的cache,避免cache占用过多的jvm内存,具体数值可根据使用场景调整。 indices.queries.cache.count: 500 indices.queries.
cache.size: 5%
单机多节点时,主从shard分配以ip为依据,分配到不同的机器上,避免单机挂掉导致数据丢失。 cluster.routing.allocation.awareness.
attributes: ip node.attr.ip: 1.1.1.1
三 ES使用方式
1. 节点数较多的集群,增加专有master,提升集群稳定性
ES集群的元信息管理、index的增删操作、节点的加入剔除等集群管理的任务都是由master节点来负责的,master节点定期将最新的集群状态广播至各个
节点。所以,master的稳定性对于集群整体的稳定性是至关重要的。当集群的节点数量较大时(比如超过30个节点),集群的管理工作会变得复杂很多。
此时应该创建专有master节点,这些节点只负责集群管理,不存储数据,不承担数据读写压力;其他节点则仅负责数据读写,不负责集群管理的工作。 这样把集群管理和数据的写入/查询分离,互不影响,防止因读写压力过大造成集群整体不稳定。 将专有master节点和数据节点的分离,需要修改ES的配置
文件,然后滚动重启各个节点。
1. # masterconf/elasticsearch.yml
2. node.master: true
3. node.data: false
4. node.ingest: false
5.
6. #
7. node.master: false
8. node.data: true
9. node.ingest: true

2. 控制index、shard总数量
上面提到,ES的元信息由master节点管理,定期同步给各个节点,也就是每个节点都会存储一份。这个元信息主要存储在clusterstate中,如所有node元信
息(indices、节点各种统计参数)、所有index/shard的元信息(mapping, location, size)、元数据ingest等。
ES在创建新分片时,要根据现有的分片分布情况指定分片分配策略,从而使各个节点上的分片数基本一致,此过程中就需要深入遍历clusterstate。当集群中
的index/shard过多时,clusterstate结构会变得过于复杂,导致遍历clusterstate效率低下,集群响应迟滞。基础架构部数据库团队曾经在一个20个节点的
集群里,创建了4w+个shard,导致新建一个index需要60s+才能完成。 当index/shard数量过多时,可以考虑从以下几方面改进:
降低数据量较小的index的shard数量
把一些有关联的index合并成一个index
数据按某个维度做拆分,写入多个集群
3. Segment Memory优化
前面提到,ES底层采用Lucene做存储,而Lucene的一个index又由若干segment组成,每个segment都会建立自己的倒排索引用于数据查询。Lucene为了
加速查询,为每个segment的倒排做了一层前缀索引,这个索引在Lucene4.0以后采用的数据结构是FST (Finite State Transducer)。Lucene加载segment
的时候将其全量装载到内存中,加快查询速度。这部分内存被称为SegmentMemory, heapGC
前面提到,为利用JVM的对象指针压缩技术来节约内存,通常建议JVM内存分配不要超过32G。当集群的数据量过大时,SegmentMemory会吃掉大量的堆
内存,而JVM内存空间又有限,此时就需要想办法降低SegmentMemory的使用量了,常用方法有下面几个:
定期删除不使用的index
对于不常访问的index,可以通过close接口将其关闭,用到时再打开
通过force_merge接口强制合并segment,降低segment数量
基础架构部数据库团队在此基础上,对FST部分进行了优化,释放高达40%的Segment Memory内存空间。

二、数据写入过程

Lucene 把每次生成的倒排索引,叫做一个段(segment)。然后另外使用一个 commit 文件,记录索引内所有的 segment。而生成 segment 的数据来源,则是内存中的 buffer。

1、数据写入 --> 进入ES内存 buffer (同时记录到translog)--> 生成倒排索引分片(segment)

2、将 buffer 中的 segment 先同步到文件系统缓存中,然后再刷写到磁盘

Elasticsearch 的flush操作主要通过以下几个参数控制:

默认设置为:每 30 分钟主动进行一次 flush,或者当 translog 文件大小大于 512MB 时主动触发flush。

index.translog.flush_threshold_period  每隔多长时间执行一次flush(默认30m)

index.translog.flush_threshold_size  当事务日志大小到达此预设值,则执行flush。(默认512mb)

index.translog.flush_threshold_ops  当事务日志累积到多少条数据后flush一次。

三、segment merge 对写入性能的影响

ES 会不断在后台运行任务,主动将这些零散的 segment 做数据归并,尽量让索引内只保有少量的,每个都比较大的,segment 文件。这个过程是有独立的线程来进行的,并不影响新 segment 的产生。

当归并完成,较大的这个 segment 刷到磁盘后,commit 文件做出相应变更,删除之前几个小 segment,改成新的大 segment。等检索请求都从小 segment 转到大 segment 上以后,删除没用的小 segment。这时候,索引里 segment 数量就下降了

segment 归并的过程,需要先读取 segment,归并计算,再写一遍 segment,最后还要保证刷到磁盘。可以说,这是一个非常消耗磁盘 IO 和 CPU 的任务。所以,ES 提供了对归并线程的限速机制,确保这个任务不会过分影响到其他任务。

用于控制归并线程的数目,推荐设置为cpu核心数的一半。 如果觉得自己磁盘性能跟不上,可以降低配置,免得IO情况瓶颈。

index.merge.scheduler.max_thread_count

归并策略:

归并线程是按照一定的运行策略来挑选 segment 进行归并的。主要有以下几条:

index.merge.policy.floor_segment  默认 2MB,小于这个大小的 segment,优先被归并。

index.merge.policy.max_merge_at_once  默认一次最多归并 10 个 segment

index.merge.policy.max_merge_at_once_explicit  默认 optimize 时一次最多归并 30 个 segment。

index.merge.policy.max_merged_segment  默认 5 GB,大于这个大小的 segment,不用参与归并。optimize 除外。

optimize 接口

既然默认的最大 segment 大小是 5GB。那么一个比较庞大的数据索引,就必然会有为数不少的 segment 永远存在,这对文件句柄,内存等资源都是极大的浪费。

但是由于归并任务太消耗资源,所以一般不太选择加大 index.merge.policy.max_merged_segment 配置,而是在负载较低的时间段,通过 optimize 接口,强制归并 segment。

curl -XPOST http://127.0.0.1:9200/logstash-2015-06.10/_optimize?max_num_segments=1

由于 optimize 线程对资源的消耗比普通的归并线程大得多,所以,绝对不建议对还在写入数据的热索引执行这个操作。

四、副本分片的存储过程

默认情况下ES通过对每个数据的id值进行哈希计算,对索引的主分片取余,就是数据实际应该存储的分片ID。

由于取余这个计算,完全依赖于分母,所以导致 ES 索引有一个限制,索引的主分片数,不可以随意修改。因为一旦主分片数不一样,所以数据的存储位置计算结果都会发生改变,索引数据就完全不可读了。

有副本配置情况下,ES的写入流程

1、客户端请求发送给Node1节点,图中的Node1是Master节点,实际环境中也可以不是(通常Master节点和Data_Node部署在不同的服务器)。

2、Node 1 用数据的 _id 取余计算得到应该讲数据存储到 P0 上。通过 cluster state 信息发现 P0 的主分片已经分配到了 Node 3 上。Node 1 转发请求数据给 Node 3。

3、Node3 完成请求数据的索引过程,存入主分片 P0。然后并行转发数据给分配有 P0 的副本分片(R0)的 Node1 和 Node2。当收到任一节点汇报副本分片数据写入成功,Node 3 即返回给初始的接收节点 Node 1,宣布数据写入成功。Node 1 返回成功响应给客户端。

副本配置和分片配置不一样,是可以随时调整的。有些较大的索引,甚至可以在做 optimize 前,先把副本全部取消掉,等 optimize 完后,再重新开启副本,节约单个 segment 的重复归并消耗。

curl -XPUT http://127.0.0.1:9200/logstash-mweibo-2015.05.02/_settings -d ‘{

    "index": { "number_of_replicas" : 0 }

}‘

五、fielddata

indices.fielddata.cache.size 节点用于 fielddata 的最大内存,如果 fielddata 达到该阈值,就会把旧数据交换出去。该参数可以设置百分比或者绝对值。默认设置是不限制,所以强烈建议设置该值,比如 10%。

indices.fielddata.cache.expire  这个参数绝对绝对不要设置!

indices.breaker.fielddata.limit 默认值是JVM堆内存的60%,注意为了让设置正常生效,一定要确保 indices.breaker.fielddata.limit 的值大于 indices.fielddata.cache.size 的值。否则的话,fielddata 大小一到 limit 阈值就报错,就永远道不了 size 阈值,无法触发对旧数据的交换任务了。

六、全文搜索

ES 对搜索请求,有简易语法和完整语法两种方式。简易语法作为以后在 Kibana 上最常用的方式。

# 命令行示例:

curl -XGET http://127.0.0.1:9200/logstash-2015.06.21/log/_search?q=first

# curl指令 -请求方式 http://服务器IP:端口/索引库名称/_type(索引类型)/_search?q=querystring 语法

?q=后面跟的是querystring 语法,这种语法在Kibana上是通用的

querystring 语法解析:

全文检索:直接写搜索的单词,如 q=Shanghai

单字段的全文检索:比如知道想检索的信息可能出现在某字段中,可以在搜索单词之前加上字段名和冒号,如:q=name:tuchao

单字段的精确检索:在搜索单词前后加双引号,比如 clientip:"192.168.12.1"

多个检索条件的组合:可以使用 NOT, AND 和 OR 来组合检索,注意必须是大写。比如

http://127.0.0.1:9200/logstash-nginxacclog-2016.09.23/_search?q=status:>400 AND size:168

字段是否存在:_exists_:user 表示要求 user 字段存在,_missing_:user 表示要求 user 字段不存在;

通配符:用 ? 表示单字母,* 表示任意个字母。比如 fir?t mess*

正则: 不建议使用

近似搜索:用 ~ 表示搜索单词可能有一两个字母写的不对,请 ES 按照相似度返回结果。比如 frist~;

七、映射的定制

Elasticsearch 是一个 schema-less 的系统,会尽量根据 JSON 源数据的基础类型猜测你想要的字段类型映射。

如果你对这种动态生成的映射关系不满意,或者想要使用一些更高级的映射设置,那么就需要使用自定义映射。

ES 可以随时根据数据中的新字段来创建新的映射关系。我们也可以在还没有正式数据写入之前,先创建一个基础的映射。等后续数据有其他字段时,ES 也一样会自动处理。

映射的创建方式如下:

curl -XPUT http://127.0.0.1:9200/logstash-2015.06.20/_mapping -d ‘ {   "mappings": {     "syslog" : {       "properties" : {         "@timestamp" : {           "type" : "date"         },         "message" : {           "type" : "string"         },         "pid" : {           "type" : "long"         }       }     }   } }‘

注意:对于已存在的映射,ES 的自动处理仅限于新字段出现。已经生成的字段映射,是不可变更的。 如果确实需要,可以参考reindex接口

而如果是新增一个字段映射的更新,那还是可以通过 /_mapping 接口直接完成的:

curl -XPUT http://127.0.0.1:9200/logstash-2015.06.21/_mapping/syslog -d ‘ {   "properties" : {     "syslogtag" : {       "type" :    "string",       "index":    "not_analyzed"     }   } }‘

这里只需要单独写这个新字段的内容就够了。ES 会自动合并进去。

删除映射

删除数据并不代表会删除数据的映射。比如:

curl -XDELETE http://127.0.0.1:9200/logstash-2015.06.21/syslog

删除了索引下 syslog 的全部数据,但是 syslog 的映射还在。删除映射(同时也就删掉了数据)的命令是:

curl -XDELETE http://127.0.0.1:9200/logstash-2015.06.21/_mapping/syslog

当然,如果删除整个索引,那映射也是同时被清除的。

查看已有数据的映射

我们用 logstash 写入 ES 的数据,都会根据 logstash 自带的 template,生成一个很有学习意义的映射:

curl -XGET http://127.0.0.1:9200/logstash-nginxacclog-2016.09.20/_mapping/

特殊字段

ES有一些默认的特殊字段,这些字段统一以_下划线开头。如_index,_type,_id。默认不开启的还有 _ttl,_timestamp,_size,_parent 等;这里介绍两个对我们索引和检索性能都有较大影响的:

_all

_all 里存储了各字段的数据内容。其作用是,在检索的时候,如果无法或者未指明具体搜索哪个字段的数据,那么 ES 默认就会是从 _all 里去查找。

对于日志场景,如果你的日志划分出来的字段比较少且数目固定。那么,完全可以关闭掉 _all 功能,节省这部分 IO 和 CPU。

"_all" : {

    "enabled" : false

}

_source

_source 里存储了该条记录的 JSON 源数据内容。这部分内容只是按照 ES 接收到的内容原样存储下来,并不经过索引过程。对于 ES 的请求过程来说,它不参与 Query 阶段,而只用于 Fetch 阶段。我们在 GET 或者 /_search 时看到的数据内容,都是从 _source 里获取到的。

所以,虽然 _source 也重复了一遍索引中的数据,一般我们并不建议关闭这个功能。因为一旦关闭,你搜索的结果除了一个 _id,啥都看不到。对于日志场景,意义不是很大。

八、动态模板映射

当你有一类相似的数据字段,想要统一设置其映射,就可以用到这项功能 动态模板映射(dynamic_templates)。

 "_default_" : {       "dynamic_templates" : [ {         "message_field" : {           "mapping" : {             "index" : "analyzed",             "omit_norms" : true,             "store" : false,             "type" : "string"           },           "match" : "*msg",           "match_mapping_type" : "string"         }       }, {         "string_fields" : {           "mapping" : {             "index" : "not_analyzed",             "ignore_above" : 256,             "store" : false,             "doc_values" : true,             "type" : "string"           },           "match" : "*",           "match_mapping_type" : "string"         }       } ],       "properties" : {       }     }

这样只会匹配字符串类型字段名以 msg 结尾的,都会经过全文索引,其他字符串字段则进行精确索引。同理,还可以继续书写其他类型(long, float, date 等)的 match_mapping_type 和 match。

索引模板

对每个希望自定义映射的索引,都要定时提前通过发送 PUT 请求的方式创建索引的话,未免太过麻烦。ES 对此设计了索引模板功能。我们可以针对同一类索引,定制相同的模板。

模板中的内容包括两大类,setting(设置)和 mapping(映射)。setting 部分,多为在 elasticsearch.yml 中可以设置全局配置的部分,而 mapping 部分,则是这节之前介绍的内容。如下为定义所有以 te 开头的索引的模板:

curl -XPUT http://localhost:9200/_template/template_1 -d ‘

{     "template" : "te*",     "settings" : {         "number_of_shards" : 1     },     "mappings" : {         "type1" : {             "_source" : { "enabled" : false }         }     } }‘

同时,索引模板是有序合并的。如果我们在同一类索引里,又想单独修改某一小类索引的一两处单独设置,可以再累加一层模板:

curl -XPUT http://localhost:9200/_template/template_2 -d ‘

{     "order" : 1,     "template" : "te*",     "settings" : {         "number_of_shards" : 2     },     "mappings" : {         "type1" : {             "_all" : { "enabled" : false }         }     } }‘

默认的 order 是 0,那么新创建的 order 为 1 的 template_2 在合并时优先级大于 template_1。最终,对tete*/type1 的索引模板效果相当于:

{     "settings" : {         "number_of_shards" : 2     },     "mappings" : {         "type1" : {             "_source" : { "enabled" : false },             "_all" : { "enabled" : false }         }     } }

注1:模版合并可以用在,当不想改变原模版,又想微调模版的相关参数时可使用。 创建一个小模版,设置相关修改的参数,保证template值设置和原模版相同,由于两个模版的template相同,那么当有新的索引被创建时会匹配到两个模版,这时两个模版的配置将会合并,order值大的模版参数,将会覆盖order值小的模版参数。

关于创建小模版的配置编写需要注意几个点

1、先认真分析原模版要修改的几个段值的嵌套关系(建议使用网页的json解析工具辅助查看)

2、小模版不需要写原模版所有内容,只需要写想变更的几个字段值

3、小模版不可和原模版同名

4、可以通过请求ES输出原模版json参考,更改,但是需要删除一些导入不兼容的字段

变更模版配置也是一样的:

1、访问该模版得到json

curl http://10.10.1.90:9200/_template/logstash3?pretty

2、变更配置,删除不兼容的字符(以上标红的字符)

3、删除原模版,重新导入

# 删除模版

curl -XDELETE http://127.0.0.1:9200/_template/logstash3  

# 导入

curl -XPUT http://127.0.0.1:9200/_template/logstash3 -d ‘        

修改后的template json ‘

九、elasticsearch 常用配置参数总结

# ----------------------------------- Index ------------------------------------  

# 设置索引的分片数,默认为5  "number_of_shards" 是索引创建后一次生成的,后续不可更改设置 index.number_of_shards: 5  

# 设置索引的副本数,默认为1

index.number_of_replicas: 1  

# 索引的刷新频率,默认1秒,太小会造成索引频繁刷新,新的数据写入就慢了。(此参数的设置需要在写入性能和实时搜索中取平衡)通常在ELK场景中需要将值调大一些比如60s,在有_template的情况下,需要设置在应用的_template中才生效。 

 index.refresh_interval: 120s  

  # ----------------------------------- Memory -------------------------------------  

# 确保 ES_MIN_MEM 和 ES_MAX_MEM 环境变量设置为相同的值,以及机器有足够的内存分配给Elasticsearch

# 注意:内存也不是越大越好,一般64位机器,最大分配内存别才超过32G  

# 当JVM开始写入交换空间时(swapping)ElasticSearch性能会低下,你应该保证它不会写入交换空间 # 设置这个属性为true来锁定内存,同时也要允许elasticsearch的进程可以锁住内存,linux下可以通过 `ulimit -l unlimited` 命令  

bootstrap.mlockall: true  

# 节点用于 fielddata 的最大内存,如果 fielddata 

# 达到该阈值,就会把旧数据交换出去。该参数可以设置百分比或者绝对值。默认设置是不限制,所以强烈建议设置该值,比如 10%。

 indices.fielddata.cache.size: 50mb

 # indices.fielddata.cache.expire  这个参数绝对绝对不要设置!  indices.breaker.fielddata.limit 默认值是JVM堆内存的60%,注意为了让设置正常生效,一定要确保 indices.breaker.fielddata.limit 的值大于 indices.fielddata.cache.size 的值。否则的话,fielddata 大小一到 limit 阈值就报错,就永远道不了 size 阈值,无法触发对旧数据的交换任务了。  # ------------------------------------ Translog -------------------------------------  

#当事务日志累积到多少条数据后flush一次。

index.translog.flush_threshold_ops: 50000  # --------------------------------- Discovery --------------------------------------  

# 这个参数决定了要选举一个Master至少需要多少个节点,默认值是1,推荐设置为 N/2 + 1,N是集群中节点的数量,这样可以有效避免脑裂

 discovery.zen.minimum_master_nodes: 1   

# 在java里面GC是很常见的,但在GC时间比较长的时候。在默认配置下,节点会频繁失联。节点的失联又会导致数据频繁重传,甚至会导致整个集群基本不可用。

 # discovery参数是用来做集群之间节点通信的,默认超时时间是比较小的。我们把参数适当调大,避免集群GC时间较长导致节点的丢失、失联。

 discovery.zen.ping.timeout: 200s  

discovery.zen.fd.ping_timeout: 200s

 discovery.zen.fd.ping.interval: 30s  

discovery.zen.fd.ping.retries: 6  

# 设置集群中节点的探测列表,新加入集群的节点需要加入列表中才能被探测到。 

discovery.zen.ping.unicast.hosts: ["10.10.1.244:9300",]

 # 是否打开广播自动发现节点,默认为true

discovery.zen.ping.multicast.enabled: false   

indices.store.throttle.type: merge

indices.store.throttle.max_bytes_per_sec: 100mb

十、调优建议

调优节点丢失问题

由于在Java里面GC是很常见的,但在GC时间比较长的时候。在默认配置下,节点会频繁失联。节点的失联又会导致数据频繁重传,甚至会导致整个集群基本不可用。我们可以通过参数调整来避免这些问题。

discovery参数ElasticSearch是用来做集群之间发现的,默认设置的超时时间是比较小的。我们把参数适当调大,避免集群GC时间较长导致节点的丢失、失联。

调优集群脑裂问题

建议采用角色分离的方法。

Master 节点不做数据节点

数据节点也没有资格竞选Master节点。

即不做Master节点,又不做数据节点,就是Client节点,用于响应请求,查询数据。

因为角色混合在一起会产生一个问题,当某个数据节点成为Master之后,它马上就会往其他节点发送数据以保证副本的冗余。如果数据量很大的情况下,这个Master就会一直在传送数据,而其他节点确认Master的请求可能就会被丢掉或者超时,这个时候其他节点就会重新选举新Master,造成集群脑裂。

调优索引写入速率

Index调优

index.refresh_interval: 120s    索引速率与搜索实时直接的平衡

index.translog.flush_threshold_ops: 50000    事务日志的刷新间隔,适当增大可降低磁盘IO

indices.store.throttle.max_bytes_per_sec: 100mb    当磁盘IO比较充足,可增大索引合并的限流值

这几个参数的调优原理,上面都有详细的解释。

提高查询速度

严格限制 fielddata cache 占用的内存,最好完全不用。

索引日常维护

定时删除过期索引,可以使用工具,或者写脚本跑计划任务

关闭暂时无需搜索的索引

对不再更新的索引进行optimize

Elasticsearch常用配置及性能参数

cluster.name: estest   集群名称

node.name: “testanya”  节点名称

node.master: false  是否主节点

node.data: true   是否存储数据

index.store.type: niofs 读写文件方式

index.cache.field.type: soft 缓存类型

bootstrap.mlockall: true 禁用swap

gateway.type: local 本地存储

gateway.recover_after_nodes: 3 3个数据节点开始恢复

gateway.recover_after_time: 5m 5分钟后开始恢复数据

gateway.expected_nodes: 4 4个es节点开始恢复

cluster.routing.allocation.node_initial_primaries_recoveries:8 并发恢复分片数

cluster.routing.allocation.node_concurrent_recoveries:2 同时recovery并发数

indices.recovery.max_bytes_per_sec: 250mb 数据在节点间传输最大带宽

indices.recovery.concurrent_streams: 8 同时读取数据文件流线程

discovery.zen.ping.multicast.enabled: false 禁用多播

discovery.zen.ping.unicast.hosts:[“192.168.169.11:9300”, “192.168.169.12:9300”]

discovery.zen.fd.ping_interval: 10s 节点间存活检测间隔

discovery.zen.fd.ping_timeout: 120s 存活超时时间

discovery.zen.fd.ping_retries: 6 存活超时重试次数

http.cors.enabled: true 使用监控

index.analysis.analyzer.ik.type:”ik” ik分词

thread pool setting

threadpool.index.type: fixed 写索引线程池类型

threadpool.index.size: 64 线程池大小(建议2~3倍cpu数)

threadpool.index.queue_size: 1000 队列大小

threadpool.search.size: 64 搜索线程池大小

threadpool.search.type: fixed 搜索线程池类型

threadpool.search.queue_size: 1000 队列大小

threadpool.get.type: fixed 取数据线程池类型

threadpool.get.size: 32 取数据线程池大小

threadpool.get.queue_size: 1000 队列大小

threadpool.bulk.type: fixed 批量请求线程池类型

threadpool.bulk.size: 32 批量请求线程池大小

threadpool.bulk.queue_size: 1000 队列大小

threadpool.flush.type: fixed 刷磁盘线程池类型

threadpool.flush.size: 32 刷磁盘线程池大小

threadpool.flush.queue_size: 1000 队列大小

indices.store.throttle.type: merge

indices.store.throttle.type: none 写磁盘类型

indices.store.throttle.max_bytes_per_sec:500mb 写磁盘最大带宽

index.merge.scheduler.max_thread_count: 8 索引merge最大线程数

index.translog.flush_threshold_size:600MB 刷新translog文件阀值

cluster.routing.allocation.node_initial_primaries_recoveries:8 并发恢复分片数

cluster.routing.allocation.node_concurrent_recoveries:2 同时recovery并发数

使用bulk API 增加入库速度

初次索引的时候,把 replica 设置为 0

增大 threadpool.index.queue_size 1000

增大 indices.memory.index_buffer_size: 20%

index.translog.durability: async –这个可以异步写硬盘,增大写的速度

增大 index.translog.flush_threshold_size: 600MB

增大 index.translog.flush_threshold_ops: 500000

1.2 注意:

有以下一些情况可能会导致 Elasticsearch 的 CPU 使用率变高:

  1. 查询负载增加:当 Elasticsearch 集群承受的查询负载增加时,会导致 CPU 使用率变高。这通常发生在索引大量新数据或者搜索流量增加的情况下。

  2. 索引负载增加:当 Elasticsearch 集群承受的索引负载增加时,会导致 CPU 使用率变高。这通常发生在索引大量新数据的情况下。

  3. GC:当 Elasticsearch 的 Java 进程发生垃圾回收(GC)时,会导致 CPU 使用率变高。GC 是清理 Java 堆内存中不再使用的对象,它是 Java 程序中自带的机制,当 Java 堆内存中的对象数量增加时,GC 的频率和时间也会相应增加。

  4. 机器性能不足:当 Elasticsearch 部署的机器性能不足时,会导致 CPU 使用率变高。例如,CPU 处理器的速度较慢,内存不足,磁盘 I/O 较慢等。

  5. 插件/脚本等造成的性能问题:当 Elasticsearch 使用的插件或脚本存在性能问题时,会导致 CPU 使用率变高。在某些情况下,某些插件和脚本可能会影响 Elasticsearch 的性能,例如使用复杂的脚本或者调用较慢的第三方库。

总之,当 Elasticsearch 集群处理数据量增加、索引负载增加、查询负载增加、GC 或者机器性能不足等情况时,会导致 CPU 使用率变高。为了避免这种情况的发生,可以采取一些优化策略,如添加更多的节点、升级硬件等。

1.3 当CPU使用率升高时如何处理?

  1. 调整集群配置。可以通过调整Elasticsearch集群的配置来减少CPU的使用率。例如,可以调整查询的并发数、增加分片的数量、减少索引的副本数等,来减轻CPU的负担。

  2. 优化查询语句。复杂的查询语句可能会导致CPU的使用率飙升。因此,可以考虑优化查询语句,使用更简单、更高效的查询语句来减少CPU的使用率。

  3. 关闭不必要的插件。Elasticsearch的插件可以扩展其功能,但某些插件可能会占用大量的CPU资源。因此,可以考虑关闭某些不必要的插件来减少CPU的使用率。

  4. 增加硬件资源。如果Elasticsearch集群的CPU使用率经常超过限制,可能需要考虑增加硬件资源,例如增加CPU核心数量或升级CPU型号,以提高集群的性能。

  5. 增加集群规模。如果Elasticsearch集群的CPU使用率经常超过限制,还可以考虑增加集群规模,将负载分摊到多台机器上,以提高集群的性能。

  6. 调整集群中节点的负载均衡策略。Elasticsearch的负载均衡策略可能会导致某些节点的CPU使用率过高。因此,可以考虑调整负载均衡策略,将负载更均衡地分配到各个节点上。

1.4 当Elasticsearch集群的CPU使用率升高时,可以考虑调整哪些参数?

  1. indices.store.throttle.max_bytes_per_sec: 索引写入速度控制参数,用于限制每秒写入的数据量。如果写入速度太快,可能会导致CPU使用率过高。可以降低该参数的值来减缓写入速度。该参数的最优值取决于硬件配置和写入负载,一般建议将其设置为每秒写入速率的80%到90%。例如,如果每秒写入速率为50MB/s,则该参数的最优值可能在40MB/s到45MB/s之间。

  2. indices.memory.index_buffer_size: 索引缓冲区大小。如果该值太小,会导致频繁的IO操作,从而增加CPU负载。可以适当增大该值来减少IO操作。该参数的最优值取决于可用内存、索引大小和查询负载等因素,一般建议将其设置为可用内存的20%到30%。例如,如果集群有100GB的可用内存,该参数的最优值可能在20GB到30GB之间。

  3. indices.fielddata.cache.size: 字段数据缓存大小。如果字段数据缓存过小,可能会导致频繁的磁盘读取,从而增加CPU负载。可以适当增大该值来减少磁盘读取操作。该参数的最优值取决于字段数据大小和查询负载等因素,一般建议将其设置为可用内存的20%到30%。例如,如果集群有100GB的可用内存,该参数的最优值可能在20GB到30GB之间。

  4. indices.queries.cache.size: 查询缓存大小。如果查询缓存过小,可能会导致频繁的查询操作,从而增加CPU负载。可以适当增大该值来减少查询操作。该参数的最优值取决于查询负载,一般建议将其设置为查询缓存占用可用内存的20%到30%。例如,如果集群有100GB的可用内存,该参数的最优值可能在20GB到30GB之间。

  5. indices.recovery.max_bytes_per_sec: 索引恢复速度控制参数,用于限制每秒恢复的数据量。如果恢复速度太快,可能会导致CPU使用率过高。可以降低该参数的值来减缓恢复速度。该参数的最优值取决于恢复速度和集群负载,一般建议将其设置为每秒恢复速率的80%到90%。例如,如果每秒恢复速率为50MB/s,则该参数的最优值可能在40MB/s到45MB/s之间。

  6. indices.search.slowlog.threshold.query.warn: 查询慢日志告警阈值。如果查询操作太慢,可能会导致CPU使用率过高。可以降低该参数的值来快速发现查询慢的问题。该参数的最优值取决于查询负载和业务需求,一般建议将其设置为查询执行时间的90%到95%。例如,如果查询执行时间的中位数为1秒,则该参数的最优值可能在900ms到950ms之间。

2、内存使用率

当前使用的内存量占可用内存总量的比例。

2.1 详细说明:

  1. Elasticsearch 会使用 JVM 来运行,因此 JVM 的内存分配对 Elasticsearch 的性能非常重要。默认情况下,Elasticsearch 的 JVM 内存分配为 1GB,但在生产环境中通常需要将其调整为更大的值。

  2. Elasticsearch 将内存分为两部分:堆内存和非堆内存。堆内存用于存储文档、字段和查询缓存等数据,非堆内存用于存储索引缓存、文件系统缓存和其他内部缓存等数据。

  3. Elasticsearch 会自动管理缓存,以确保常用的数据在内存中。当内存不足时,Elasticsearch 会将较少使用的数据从内存中移除,并将其存储到磁盘上。

  4. 对于单个节点的 Elasticsearch 集群,通常建议将 JVM 堆内存设置为总内存的一半,以留出一定的空间给操作系统和其他进程使用。而对于大型集群,建议将 JVM 堆内存设置为 30GB 到 32GB

2.2 注意:

关于Elasticsearch的内存使用率,以下因素可能会对其产生影响:

  1. 索引的大小和数量:索引的大小和数量会直接影响 Elasticsearch 使用的内存量。较大的索引需要更多的内存来处理,而较小的索引则需要较少的内存。

  2. 查询负载:查询负载是指 Elasticsearch 在任何给定时刻处理的查询数量和类型。更多的查询负载需要更多的内存来处理和缓存查询结果。

  3. JVM 堆内存大小:Elasticsearch 在 JVM 堆内存中缓存文档、字段和查询结果等数据,堆内存的大小直接影响 Elasticsearch 的性能。通常,增加堆内存大小可以提高 Elasticsearch 的性能,但是在可用内存受限的情况下,过大的堆内存大小可能会导致系统负载过重,甚至导致 OutOfMemoryError 错误。

  4. 硬件资源:Elasticsearch 的内存使用率还受限于硬件资源,包括 CPU、磁盘和网络带宽等。如果硬件资源不足,可能会导致 Elasticsearch 性能下降,甚至无法正常运行。

  5. Elasticsearch 版本:Elasticsearch 版本之间的内存使用率也可能有所不同。较新的版本通常会更有效地利用内存,提高性能和稳定性。

综上所述,Elasticsearch 的内存使用率受多种因素影响,需要根据具体情况进行分析和调整,以优化性能和稳定性。

2.3 当内存使用率升高时如何处理?

  1. 检查Elasticsearch集群中是否有索引或搜索查询的负载异常,这可能导致内存使用率飙升。可以使用Elasticsearch的监控工具或REST API来查看负载和性能指标,并识别问题所在。

  2. 调整JVM的堆内存大小。JVM是Elasticsearch节点的内存管理器,可以通过调整JVM的堆内存大小来控制Elasticsearch的内存使用率。可以通过在elasticsearch.yml文件中设置-Xms和-Xmx参数来增加或减少JVM的堆内存大小。注意,不要将JVM的堆内存大小设置过小,否则会影响Elasticsearch的性能。

  3. 减小索引分片的数量。索引分片是Elasticsearch的分布式存储机制,但太多的分片会占用过多的内存资源。可以通过减小索引分片的数量来降低内存使用率。

  4. 使用更高效的查询。复杂的查询可能需要占用更多的内存资源,因此,可以尝试使用更高效的查询来降低内存使用率。例如,使用过滤器而不是查询语句来获取数据,或使用聚合操作来减少数据的返回。

  5. 增加硬件资源。如果Elasticsearch集群的内存使用率经常超过限制,可能需要考虑增加硬件资源,例如增加内存或CPU,以满足集群的性能需求。

2.4 当Elasticsearch集群的内存使用率升高时,可以考虑调整哪些参数?

  1. indices.memory.index_buffer_size:该参数控制索引模块使用的内存缓冲区大小。如果内存使用率升高,可以尝试降低该参数的值,以减少索引模块占用的内存。不过,降低该参数的值可能会降低索引性能。此参数应该根据索引的大小和使用情况进行调整。建议将其设置为每个索引的可用堆内存的5%-10%。

  2. indices.fielddata.cache.size:该参数控制字段数据缓存的大小。如果内存使用率升高,可以尝试降低该参数的值,以减少字段数据缓存占用的内存。不过,降低该参数的值可能会降低查询性能。此参数应该设置为尽可能大的值,以利用可用的内存来缓存字段数据。建议设置为10%-30%的可用堆内存。

  3. indices.queries.cache.size:该参数控制查询缓存的大小。如果内存使用率升高,可以尝试降低该参数的值,以减少查询缓存占用的内存。不过,降低该参数的值可能会降低查询性能。此参数应该设置为尽可能大的值,以利用可用的内存来缓存查询。建议设置为10%-30%的可用堆内存。

  4. indices.breaker.*:该参数控制Elasticsearch使用的熔断器(circuit breaker)阈值。熔断器是一种保护机制,用于防止过度使用内存和磁盘等资源。如果内存使用率升高,可以尝试调整熔断器阈值,以避免过度使用内存。

  5. indices.recovery.max_bytes_per_sec:该参数控制恢复速度。如果内存使用率升高,可以尝试降低该参数的值,以降低恢复操作占用的内存。 此参数应该根据网络带宽进行调整,以确保恢复操作不会占用过多的内存。建议将其设置为网络带宽的70%-80%。

3、磁盘使用率

磁盘使用率是指已用磁盘空间和可用磁盘空间之间的比率,通常以百分比形式表示

3.1 详细说明:

关于磁盘使用率可详细描述的部分较少,这里说明一下常见的几种磁盘类型:

  1. 机械硬盘(HDD):机械硬盘是一种传统的存储设备,使用旋转的磁盘和移动的磁头来读写数据。它们相对较便宜,但速度较慢,因此不适合对性能要求较高的应用。

  2. 固态硬盘(SSD):固态硬盘使用闪存来存储数据,速度比机械硬盘更快,因此可以提供更好的性能。它们相对较昂贵,但在需要高性能的应用场景中通常更受欢迎。

  3. NVMe硬盘:NVMe硬盘是一种专为固态硬盘设计的高速接口,比SATA接口的固态硬盘更快,因此提供更高的性能。

  4. 分布式文件系统:Elasticsearch还支持使用分布式文件系统作为存储后端,如Hadoop Distributed File System(HDFS)和Amazon S3。这些系统通常用于大规模数据存储和分析,但也可以用于Elasticsearch。

3.2 注意:

  1. 数据量:Elasticsearch的磁盘使用率与数据量直接相关,因为数据存储在磁盘上。如果数据量增加,磁盘使用率也会相应增加。

  2. 索引设置:Elasticsearch支持多种索引设置,如分片和副本,这些设置会影响数据在磁盘上的存储方式和占用空间的大小。例如,分片数量越多,每个分片占用的磁盘空间就越小,但需要更多的分片可能会导致额外的磁盘空间占用。

  3. 索引更新频率:当索引频繁更新时,会导致Elasticsearch写入更多的数据到磁盘上,从而增加磁盘使用率。

  4. 删除操作:在Elasticsearch中,删除文档不会立即释放磁盘空间,而是通过后台的段合并(segment merge)操作来回收空间。如果经常删除文档,可能需要定期执行段合并操作,否则会导致磁盘使用率持续增加。

  5. 数据复制:Elasticsearch支持副本机制,即将数据复制到其他节点以实现高可用性。这意味着每个副本都需要占用额外的磁盘空间。

  6. 磁盘类型:不同类型的磁盘对性能和空间占用有不同的影响。例如,固态硬盘通常比机械硬盘更快,但通常也更昂贵,而机械硬盘则更适合低成本应用。

3.3 当磁盘使用率升高时如何处理?

  1. 添加更多的节点:可以通过添加更多的节点来扩展集群的存储能力,从而减轻单个节点的负担。

  2. 删除旧的或不需要的数据:可以通过删除旧的或不需要的数据来释放磁盘空间。但是,在删除数据之前,请确保您不再需要这些数据,因为数据删除是不可逆的操作。

  3. 压缩索引:Elasticsearch提供了一些工具来压缩索引,可以在不降低性能的情况下减小索引的大小。

  4. 增加磁盘空间:如果磁盘使用率升高是由于磁盘空间不足导致的,可以考虑增加磁盘空间。

  5. 调整文档的存储方式:可以通过调整文档的存储方式来减小磁盘使用率。例如,可以将文档中的某些字段设置为不索引,或者将某些字段设置为压缩存储。

4、GC频次

指垃圾回收器在一定时间内执行的次数,它是一个反映JVM垃圾回收效率的指标。

4.1 详细说明:

这里指的是老年代的GC频次,老年代用来存储较老的对象空间,这些对象预期是长久的并且持续了很长时间。在Elasticsearch节点中最大可以设置为30GB。

一个缓慢的GC可能有1s甚至15s以上,从集群稳定性的角度来说是不可接受的。一个频繁长时间GC的集群是重负载并且没有足够内存的。这些长时间GC将使节点短暂离开集群,在Elasticsearch中为了保持集群的稳定和可用的副本,这种不稳定因素经常导致重新迁移分片。当集群尝试服务正常的索引(写入)和查询时,会增加网络流量和磁盘I/O。

  在Elasticsearch集群的垃圾回收器替换成G1后,GC的频次和持续时间均有明显改善。

4.2 注意:

  1. JVM堆内存不足:当JVM堆内存不足时,会导致GC频繁触发,以释放内存空间。这种情况通常是由于索引数据量增加或者查询负载增加等原因导致的。

  2. 索引数据过多:当索引数据量过多时,会占用大量的内存空间,导致JVM堆内存不足,从而引发GC频繁触发。

  3. 查询压力过大:当查询压力过大时,会导致Elasticsearch集群需要处理大量的查询请求,从而导致JVM堆内存不足,引发GC频繁触发。

  4. 代码逻辑问题:有时候可能存在代码逻辑问题,例如内存泄漏等,也会导致JVM堆内存占用过高,从而引发GC频繁触发。

4.3 降低GC频次的措施?

  1. 增加JVM堆内存大小:GC频次过高的一个主要原因是JVM内存不足,因此增加JVM堆内存大小可以有效降低GC频次。一般来说,JVM堆内存大小应该设置为应用程序所需要的最小值加上一定的余量,以确保系统具有足够的内存。

  2. 优化查询和索引操作:查询和索引操作是Elasticsearch集群中最耗费内存和CPU资源的操作,因此可以通过优化查询和索引操作来降低GC频次。例如,可以使用filter代替query,避免使用过多的聚合操作,避免使用过多的脚本等。

  3. 使用合适的垃圾回收器:不同的垃圾回收器有不同的GC算法和优化策略,选择合适的垃圾回收器可以降低GC频次。一般来说,CMS垃圾回收器比较适合应用程序的实时处理,而G1垃圾回收器则适合大型堆内存的应用程序。

  4. 使用合适的JVM参数:合适的JVM参数可以对Elasticsearch集群的GC频次产生重要影响。例如,可以通过设置合适的堆内存大小、调整垃圾回收器的参数等方式来降低GC频次。

5、fielddata内存使用量

fielddata内存使用量是指已经被加载到内存中的fielddata的大小。

5.1 详细说明:

当在Elasticsearch中对一个字段使用聚合、排序、脚本或者用于全文搜索时,该字段的fielddata就会被加载到内存中进行操作。fielddata是一种用于对文本类型的字段进行排序和聚合的数据结构。

如果一个集群中有大量的字段需要在内存中加载fielddata,那么这个指标可能会对集群性能产生负面影响。因此,需要根据实际情况来调整fielddata的使用策略,以平衡内存的使用和查询性能的需求。

5.2 注意:

当fielddata内存使用量达到一定程度时,会对Elasticsearch集群的性能和稳定性产生负面影响。具体来说,主要有以下几点:

  1. 内存不足:当fielddata内存使用量过高时,可能会导致内存不足,从而影响Elasticsearch集群的运行。这可能会导致请求被拒绝、节点故障等问题。

  2. 垃圾回收:当fielddata内存使用量过高时,垃圾回收器会频繁执行,这可能会导致性能下降。特别是在大型集群中,这可能会导致所有节点的性能下降,进而影响整个集群。

  3. 磁盘使用量:当fielddata内存使用量过高时,Elasticsearch可能会将部分数据写入磁盘,从而占用更多的磁盘空间。如果磁盘空间不足,可能会影响Elasticsearch集群的稳定性。

5.3 当fielddata内存使用量增高时如何处理?

有以下几种方法可以减少fielddata内存使用量:

  1. 使用 doc values 替代 fielddata:doc values 是一种结构化的、只读的数据结构,可以直接被用于排序、聚合和脚本,与 fielddata 相比可以减少内存使用。可以使用 doc_values 属性将字段显式地配置为使用 doc values。

  2. 避免过度使用 text 类型的字段:text 类型的字段会产生 fielddata,如果不需要进行全文搜索、聚合、排序等操作,可以考虑使用 keyword 类型的字段来替代。

  3. 减少不必要的聚合操作:聚合操作会对 fielddata 进行操作,如果聚合操作不是必须的,可以考虑避免使用。

  4. 增加 fielddata 缓存大小:可以通过在配置文件中设置 indices.fielddata.cache.size 来增加 fielddata 缓存大小,从而减少 fielddata 的内存使用。

  5. 减少索引的字段数:减少索引的字段数可以减少 fielddata 的内存使用。

注意,以上方法需要根据具体情况来选择,不同的情况可能需要不同的方法来减少 fielddata 内存使用量。

2、写性能调优

2.1 基本原则

写性能调优是建立在对Elasticsearch的写入原理之上。ES 数据写入具有一定的延时性,这是为了减少频繁的索引文件产生。默认情况下 ES 每秒生成一个 segment 文件,当达到一定阈值的时候 会执行merge,merge 过程发生在 JVM中,频繁的生成 Segmen 文件可能会导致频繁的触发 FGC,导致 OOM。为了避免避免这种情况,通常采取的手段是降低 segment 文件的生成频率,手段有两个,一个是 增加时间阈值,另一个是增大 Buffer的空间阈值,因为缓冲区写满也会生成 Segment 文件。

生产经常面临的写入可以分为两种情况:

  • 高频低量:高频的创建或更新索引或文档一般发生在 处理 C 端业务的场景下。
  • 低频高量:一般情况为定期重建索引或批量更新文档数据。

在搜索引擎的业务场景下,用户一般并不需要那么高的写入实时性。比如你在网站发布一条征婚信息,或者二手交易平台发布一个商品信息。其他人并不是马上能搜索到的,这其实也是正常的处理逻辑。这个延时的过程需要处理很多事情,业务层面比如:你的信息需要后台审核。你发布的内容在搜索服务中需要建立索引,而且你的数据可能并不会马上被写入索引,而是等待要写入的数据达到一定数量之后,批量写入。这种操作优点类似于我们快递物流的场景,只有当快递数量达到一定量级的时候,比如能装满整个车的时候,快递车才会发车。因为反正是要跑一趟,装的越多,平均成本越低。这和我们数据写入到磁盘的过程是非常相似的,我们可以把一条文档数据看做是一个快递,而快递车每次发车就是向磁盘写入数据的一个过程。这个过程不宜太多,太多只会降低性能,就是体现在运输成本上面。而对于我们数据写入而言就是体现在我们硬件性能损耗上面。

2.2 优化手段

以下为常见 数据写入的调优手段,写入调优均以提升写入吞吐量和并发能力为目标,而非提升写入实时性。

2.2.1 增加 flush 时间间隔,

目的是减小数据写入磁盘的频率,减小磁盘IO频率。

2.2.2 增加refresh_interval的参数值

目的是减少segment文件的创建,减少segment的merge次数,merge是发生在jvm中的,有可能导致full GC,增加refresh会降低搜索的实时性。

ES的 refresh 行为非常昂贵,并且在正在进行的索引活动时经常调用,会降低索引速度,这一点在索引写入原理中介绍过,了解索引的写入原理,可以关注我的博客Elastic开源社区。

默认情况下,Elasticsearch 每秒定期刷新索引,但仅在最近 30 秒内收到一个或多个搜索请求的索引上。

如果没有搜索流量或搜索流量很少(例如每 5 分钟不到一个搜索请求)并且想要优化索引速度,这是最佳配置。此行为旨在在不执行搜索的默认情况下自动优化批量索引。建议显式配置此配置项,如 30秒。

2.2.3 增加Buffer大小,

本质也是减小refresh的时间间隔,因为导致segment文件创建的原因不仅有时间阈值,还有buffer空间大小,写满了也会创建。 默认最小值 48MB< 默认值 JVM 空间的10% < 默认最大无限制

2.2.4 关闭副本

当需要单次写入大量数据的时候,建议关闭副本,暂停搜索服务,或选择在检索请求量谷值区间时间段来完成。

  • 第一是减小读写之间的资源抢占,读写分离
  • 第二,当检索请求数量很少的时候,可以减少甚至完全删除副本分片,关闭segment的自动创建以达到高效利用内存的目的,因为副本的存在会导致主从之间频繁的进行数据同步,大大增加服务器的资源占用。

具体可通过则设置index.number_of_replicas 为0以加快索引速度。没有副本意味着丢失单个节点可能会导致数据丢失,因此数据保存在其他地方很重要,以便在出现问题时可以重试初始加载。初始加载完成后,可以设置index.number_of_replicas改回其原始值。

2.2.5 禁用swap

大多数操作系统尝试将尽可能多的内存用于文件系统缓存,并急切地换掉未使用的应用程序内存。这可能导致部分 JVM 堆甚至其可执行页面被换出到磁盘。

交换对性能和节点稳定性非常不利,应该不惜一切代价避免。它可能导致垃圾收集持续几分钟而不是几毫秒,并且可能导致节点响应缓慢甚至与集群断开连接。在Elastic分布式系统中,让操作系统杀死节点更有效。

2.2.6 使用多个工作线程

发送批量请求的单个线程不太可能最大化 Elasticsearch 集群的索引容量。为了使用集群的所有资源,应该从多个线程或进程发送数据。除了更好地利用集群的资源外,还有助于降低每个 fsync 的成本。

确保注意TOO_MANY_REQUESTS (429)响应代码(EsRejectedExecutionException使用 Java 客户端),这是 Elasticsearch 告诉我们它无法跟上当前索引速度的方式。发生这种情况时,应该在重试之前暂停索引,最好使用随机指数退避。

与调整批量请求的大小类似,只有测试才能确定最佳工作线程数量是多少。这可以通过逐渐增加线程数量来测试,直到集群上的 I/O 或 CPU 饱和。

2.2.7 避免使用稀疏数据

2.2.8 max_result_window参数

max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。

在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析各项指标从而确定一个最佳值,并非越大越好

3、查询调优

3.1 读写性能不可兼得

首先要明确一点:鱼和熊掌不可兼得。读写性能调优在很多场景下是只能二选一的。牺牲 A 换 B 的行为非常常见。索引本质上也是通过空间换取时间。写生写入实时性就是为了提高检索的性能。

当你在二手平台或者某垂直信息网站发布信息之后,是允许有信息写入的延时性的。但是检索不行,甚至 1 秒的等待时间对用户来说都是无法接受的。满足用户的要求甚至必须做到10 ms以内。

3.2 优化手段

3.2.1 避免单次召回大量数据

搜索引擎最擅长的事情是从海量数据中查询少量相关文档,而非单次检索大量文档。非常不建议动辄查询上万数据。如果有这样的需求,建议使用滚动查询

3.2.2 避免单个文档过大

鉴于默认http.max_content_length设置为 100MB,Elasticsearch 将拒绝索引任何大于该值的文档。您可能决定增加该特定设置,但 Lucene 仍然有大约 2GB 的限制。

即使不考虑硬性限制,大型文档通常也不实用。大型文档对网络、内存使用和磁盘造成了更大的压力,即使对于不请求的搜索请求也是如此,_source因为 Elasticsearch_id在所有情况下都需要获取文档的文件系统缓存有效。对该文档进行索引可能会占用文档原始大小的倍数的内存量。Proximity Search(例如短语查询)和高亮查询也变得更加昂贵,因为它们的成本直接取决于原始文档的大小。

有时重新考虑信息单元应该是什么是有用的。例如,您想让书籍可搜索的事实并不一定意味着文档应该包含整本书。使用章节甚至段落作为文档可能是一个更好的主意,然后在这些文档中拥有一个属性来标识它们属于哪本书。这不仅避免了大文档的问题,还使搜索体验更好。例如,如果用户搜索两个单词fooand bar,则不同章节之间的匹配可能很差,而同一段落中的匹配可能很好。

3.2.3 单次查询10条文档 好于 10次查询每次一条

批量请求将产生比单文档索引请求更好的性能。但是每次查询多少文档最佳,不同的集群最佳值可能不同,为了获得批量请求的最佳阈值,建议在具有单个分片的单个节点上运行基准测试。首先尝试一次索引 100 个文档,然后是 200 个,然后是 400 个等。在每次基准测试运行中,批量请求中的文档数量翻倍。当索引速度开始趋于平稳时,就可以获得已达到数据批量请求的最佳大小。在相同性能的情况下,当大量请求同时发送时,太大的批量请求可能会使集群承受内存压力,因此建议避免每个请求超过几十兆字节。

3.2.4 数据建模

很多人会忽略对 Elasticsearch 数据建模的重要性。

nested属于object类型的一种,是Elasticsearch中用于复杂类型对象数组的索引操作。Elasticsearch没有内部对象的概念,因此,ES在存储复杂类型的时候会把对象的复杂层次结果扁平化为一个键值对列表。

特别是,应避免连接。Nested 可以使查询慢几倍,Join 会使查询慢数百倍。两种类型的使用场景应该是:Nested针对字段值为非基本数据类型的时候,而Join则用于 当子文档数量级非常大的时候。

关于数据建模,在我的博客中有详细的讲解,此处不再赘述

3.2.5 给系统留足够的内存

Lucene的数据的fsync是发生在OS cache的,要给OS cache预留足够的内从大小,详见JVM调优。

3.2.6 预索引

利用查询中的模式来优化数据的索引方式。例如,如果所有文档都有一个price字段,并且大多数查询 range 在固定的范围列表上运行聚合,可以通过将范围预先索引到索引中并使用聚合来加快聚合速度。

3.2.7 使用filter代替query

query和filter的主要区别在: filter是结果导向的而query是过程导向。query倾向于“当前文档和查询的语句的相关度”而filter倾向于“当前文档和查询的条件是不是相符”。即在查询过程中,query是要对查询的每个结果计算相关性得分的,而filter不会。另外filter有相应的缓存机制,可以提高查询效率。

3.2.8 避免深度分页

避免单页数据过大,可以参考百度或者淘宝的做法。es提供两种解决方案 scroll search 和 search after。关于深度分页的详细原理,推荐阅读:详解Elasticsearch深度分页问题

3.2.9 使用 Keyword 类型

并非所有数值数据都应映射为数值字段数据类型。Elasticsearch为 查询优化数字字段,例如integeror long。如果不需要范围查找,对于 term查询而言,keyword 比 integer 性能更好。

3.2.10 避免使用脚本

Scripting是Elasticsearch支持的一种专门用于复杂场景下支持自定义编程的强大的脚本功能。相对于 DSL 而言,脚本的性能更差,DSL能解决 80% 以上的查询需求,如非必须,尽量避免使用 Script

4、索引结构优化

4.1 一句废话

本质上,索引结构取决于业务,业务场景不同,最合适的索引结构也不同。下面我通过一个真实案例来解释这个问题。

4.2 案例

4.2.1 背景

某公司是提供SMS短信服务的,其主要客户群体为各大银行系统。其业务主要是为客户提供短信发送服务,并且要保存所有

4.2.2 痛点

痛点1:索引过于庞大

就短信发送而言并无太大问题,但是在项目初期,在建立短信发送记录的时候,并没有在意索引结构的设计,而是将新产生的数据不断的追加到一个索引中,但是后期发现短信发送量级过于庞大,每天都产生上几百GB甚至是TB级的日志数据,后期的优化手段就是把一个单独的大索引,通过Rollover Index,每天创建一个单独的索引。短期内问题得到了解决

痛点2:索引太多

基于滚动索引的方案在短期内的确解决了单个索引过大而带来的性能下降,因为80%的请求都是基于近期数据而言的,但是随着时间周期的拉长,剩余 20% 的请求的问题日渐突出。的确存在一部分用户,需要查询跨周期范围特别大的数据,比如,工商银行某客户要查询 近三年以来短信发送记录,造成的结果就是需要跨一千多个索引来查询。这种问题,时间越久,就越明显。

4.2.3 思路及解决方案

对于这种需求场景而言,其实索引结构设计的很不合理。固然我们很容易将数据与时间绑定,但是当前场景明显是一个垂直业务。换句话说,索引的拆分不应该简单按照时间进行拆分,而是按照业务属性和业务量。比如:我们要查询某个手机号3年的发送记录,在执行查询之前,我们就能通过手机号的自身属性确定查询范围,如手机号归属地、所属运营商等。北京的手机号,无论如何也不会涉及到上海的数据。所以中国移动在北京和上海的数据就可以保存两个索引,因为他们是互不相干的。中国移动和中国联通的数据,也是互不相干的。这样一来,一个索引可能就被拆成了几百个索引,在检索之前,就可以根据业务属性迅速确定要查询的所有数据所在的索引。

在此基础之上通过Rollover index来创建索引还要根据总量以及每日增量来确定索引创建的粒度大小。这个大小要结合群节点数量、结构分配及角色分配、分片数量的最优原则来确定。比如如果数据量每天很少,那就把粒度改成周或者月,让索引有一个更加合理的大小,过大过小都不好。至于索引和分片大小数量的优化配置,我的文章里有单独介绍。

举个例子:当我们80%的请求落在近三个月,90%的请求都落在近一个月,那其实我们有必要为近三个月单独创建一个索引或者将数据缓存起来,又或者通过冷热集群的方案来做隔离部署。把更优质的资源分配给更需要性能的业务数据上。

然而问题到此还未真正的彻底解决,因为时序数据是具有流转性特点的。近三个月的数据,其实等过三个月就变成了历史数据,本身部署在 hot phase 的数据就要相应的流转到 warm,也有可能需要对数据压缩或冷冻,以提高数据的存储效率降低成本。

有些数据大概率常年是不需要提供任何查询,但是我们不能丢弃,比如银行的转账记录可能要保存十年甚至三十年。平常这些数据我们是用不到的,对于普通用户一般能查询半年或一年,最多三年的记录。但是这些数据因为有可能涉及犯罪信息,当国家或政府需要银行协助调查的时候,银行有保存义务,需要协助国家调查提供数据。这种事情发生的概率很小但是它的确存在。所以可以通过快照或可搜索快照保存。这样代价就很低。

到这里其实还能继续优化,因为即便索引已经合理拆分,但是如果随着业务的继续膨胀以及时间周期的不断拉长,仍然会面临以上问题,那么解决方案就是我们通过数据流来创建索引的生命周期管理策略,数据流在开源社区或者我的个人博客中有单独的讲解,此处不再赘述。

4.3 总结

5、硬件优化

5.1 简述

es的默认配置是一个非常合理的默认配置,绝大多数情况下是不需要修改的,如果不理解某项配置的含义,没有经过验证就贸然修改默认配置,可能造成严重的后果。比如max_result_window这个设置,默认值是1W,这个设置是分页数据每页最大返回的数据量,冒然修改为较大值会导致OOM。ES没有银弹,不可能通过修改某个配置从而大幅提升ES的性能,通常出厂配置里大部分设置已经是最优配置,只有少数和具体的业务相关的设置,事先无法给出最好的默认配置,这些可能是需要我们手动去设置的。关于配置文件,如果你做不到彻底明白配置的含义,不要随意修改。

jvm heap分配:7.x 版本默认1GB,这个值太小,很容易导致OOM。Jvm heap大小不要超过物理内存的50%,最大也不要超过32GB(compressed oop),它可用于其内部缓存的内存就越多,但可供操作系统用于文件系统缓存的内存就越少,heap过大会导致GC时间过长

5.2 节点

  • 相同角色的节点,避免使用差异较大的服务器配置,

  • 避免使用“超大杯”服务器(SS:Super Server),比如128核CPU,1 T的内存,2T的固态硬盘。这样可能会产生较大的资源浪费。

  • 等量的配置,使用较少的物理机好于使用较多的虚拟机。比如一个一个五台4核16G的物理机,好于10甚至11台2核8G的虚拟机,这里不仅仅是虚拟机本身可能也会消耗一部分性能的问题,也涉及数据安全的问题。

  • 避免在同一台服务器上部署多个节点,会增加集群管理的难度。

5.3 分片

5.3.1 分片创建策略

分片产生的目的是为了实现分布式,而分布式的好处之一就是实现“高可用性”(还包括高性能如提高吞吐量等会在后面内容展开讲),分片的分配策略极大程度上都是围绕如何提高可用性而来的,如分片分配感知、强制感知等。

互联网开发没有“银弹”,分片的数量分配也没有适用于所有场景的最佳值,创建分片策略的最佳方法是使用您在生产中看到的相同查询和索引负载在生产硬件上对生产数据进行基准测试。分片的分配策略主要从两个指标来衡量:即数量和单个分片的大小。

5.3.2 分片分配策略

  • ES使用数据分片(shard)来提高服务的可用性,将数据分散保存在不同的节点上以降低当单个节点发生故障时对数据完整性的影响,同时使用副本(repiica)来保证数据的完整性。关于分片的默认分配策略,在7.x之前,默认5个primary shard,每个primary shard默认分配一个replica,即5主1副,而7.x之后,默认1主1副
  • ES在分配单个索引的分片时会将每个分片尽可能分配到更多的节点上。但是,实际情况取决于集群拥有的分片和索引的数量以及它们的大小,不一定总是能均匀地分布。
  • Paimary只能在索引创建时配置数量,而replica可以在任何时间分配,并且primary支持读和写操作,而replica只支持客户端的读取操作,数据由es自动管理,从primary同步。
  • ES不允许Primary和它的Replica放在同一个节点中,并且同一个节点不接受完全相同的两个Replica
  • 同一个节点允许多个索引的分片同时存在

5.3.3 分片的数量

  • 避免分片过多:大多数搜索会命中多个分片。每个分片在单个 CPU 线程上运行搜索。虽然分片可以运行多个并发搜索,但跨大量分片的搜索会耗尽节点的搜索线程池。这会导致低吞吐量和缓慢的搜索速度。
  • 分片越少越好:每个分片都使用内存和 CPU 资源。在大多数情况下,一小组大分片比许多小分片使用更少的资源。

5.3.4 分片的大小决策

  • 分片的合理容量:10GB-50GB。虽然不是硬性限制,但 10GB 到 50GB 之间的分片往往效果很好。根据网络和用例,也许可以使用更大的分片。在索引的生命周期管理中,一般设置50GB为单个索引的最大阈值。
  • 堆内存容量和分片数量的关联:小于20分片/每GB堆内存,一个节点可以容纳的分片数量与节点的堆内存成正比。例如,一个拥有 30GB 堆内存的节点最多应该有 600 个分片。如果节点超过每 GB 20 个分片,考虑添加另一个节点。

5.4 内存

根据业务量不同,内存的需求也不同,一般生产建议不要少于16G。ES是比较依赖内存的,并且对内存的消耗也很大,内存对ES的重要性甚至是高于CPU的,所以即使是数据量不大的业务,为了保证服务的稳定性,在满足业务需求的前提下,我们仍需考虑留有不少于20%的冗余性能。一般来说,按照百万级、千万级、亿级数据的索引,我们为每个节点分配的内存为16G/32G/64G就足够了,太大的内存,性价比就不是那么高了。

5.5 磁盘

对于ES来说,磁盘可能是最重要的了,因为数据都是存储在磁盘上的,当然这里说的磁盘指的是磁盘的性能。磁盘性能往往是硬件性能的瓶颈,木桶效应中的最短板。ES应用可能要面临不间断的大量的数据读取和写入。生产环境可以考虑把节点冷热分离,“热节点”使用SSD做存储,可以大幅提高系统性能;冷数据存储在机械硬盘中,降低成本。另外,关于磁盘阵列,可以使用raid 0。

5.6 CPU

CPU对计算机而言可谓是最重要的硬件,但对于ES来说,可能不是他最依赖的配置,因为提升CPU配置可能不会像提升磁盘或者内存配置带来的性能收益更直接、显著。当然也不是说CPU的性能就不重要,只不过是说,在硬件成本预算一定的前提下,应该把更多的预算花在磁盘以及内存上面。通常来说单节点cpu 4核起步,不同角色的节点对CPU的要求也不同。服务器的CPU不需要太高的单核性能,更多的核心数和线程数意味着更高的并发处理能力。现在PC的配置8核都已经普及了,更不用说服务器了。

5.7 网络

ES是天生自带分布式属性的,并且ES的分布式系统是基于对等网络的,节点与节点之间的通信十分的频繁,延迟对于ES的用户体验是致命的,所以对于ES来说,低延迟的网络是非常有必要的。因此,使用扩地域的多个数据中心的方案是非常不可取的,ES可以容忍集群跨多个机房,可以有多个内网环境,支持跨AZ部署,但是不能接受多个机房跨地域构建集群,一旦发生了网络故障,集群可能直接GG,即使能够保证服务正常运行,维护这样(跨地域单个集群)的集群带来的额外成本可能远小于它带来的额外收益。

5.8 总结

  • 集群需要多少种配置(内存型/IO型/运算型),每种配置需要多少数量,通常需要和产品运营和运维测试商定,视业务量和服务器的承载能力而定,并留有一定的余量。
  • 一个合理的ES集群配置应不少于5台服务器,避免脑裂时无法选举出新的Master节点的情况,另外可能还需要一些其他的单独的节点,比如ELK系统中的Kibana、Logstash等。

6、架构优化:

架构层面,非一言两语可详述,推荐阅读:从单机到百万节点:Elasticsearch高可用集群架构部署方案

  • 合理的分配角色和每个节点的配置,在部署集群的时候,应该根据多方面的情况去评估集群需要多大规模去支撑业务。这个是需要根据在你当前的硬件环境下测试数据的写入和搜索性能,然后根据你目前的业务参数来动态评估的,比如:业务数据的总量、每天的增量、查询的并发以及QPS以及峰值的请求量。

  • 节点并非越多越好,会增加主节点的压力

  • 分片并非越多越好,从deep pageing 的角度来说,分片越多,JVM开销越大,负载均衡(协调)节点的转发压力也越大,查询速度也越慢。单个分片也并非越大越好,一般来说单个分片大小控制在30-50GB。

7、Mpping结构和索引字段优化:

doc_values:正排索引,对于不需要聚合的字段,关闭正排索引可节省资源,提高查询速度

fielddata:可以理解为“runtime_doc_values”,doc_value 为 index time 正排索引。fielddata会消耗JVM空间,如果执行大量数据的聚合使用 fielddata,会造成 OOM

尽量不要使用 dynamic mapping

ignore_above:字段保留的长度,越小越好

调整_source字段,通过include和exclude过滤

store:开辟另一块存储空间,可以节省带宽

注意:_sourse设置为false,则不存储源数据,可以节省磁盘,并且不影响搜索。但是禁用_source必须三思而后行,禁用后将导致以下后果:

update,update_by_query和reindex不可用。

高亮失效

reindex失效,原本可以修改的mapping部分参数将无法修改,并且无法升级索引

无法查看元数据和聚合搜索

影响索引的容灾能力

禁用_all字段:_all字段的包含所有字段分词后的Term,作用是可以在搜索时不指定特定字段,从所有字段中检索,ES 6.0之前需要手动关闭

关闭 Norms 字段:计算评分用的,如果你确定当前字段将来不需要计算评分,设置false可以节省大量的磁盘空间,有助于提升性能。常见的比如filter和agg字段,都可以设为关闭。

关闭 index_options(谨慎使用,高端操作):此设置用于在index time过程中哪些内容会被添加到倒排索引的文件中,例如TF,docCount、postion、offsets等,减少option的选项可以减少在创建索引时的CPU占用率,不过在实际场景中很难确定业务是否会用到这些信息,除非是在一开始就非常确定用不到,否则不建议删除

enabled:是否创建倒排索引,对于不需要查询的字段,关闭正排索引可节省资源,提高查询速度。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值