面介绍了 ElasticSearch 基础概念 、技术原理、安装和基础使用、索引管理、 DSL 查询、聚合查询、索引文档与读取文档流程
、集群部署、规划与运维经验总结、集群规划与运维、数据备份与迁移、常用 Curl 命令、可视化工具、性能优化相关的知识点。今天我将详细的为大家介绍 ElasticSearch 性能监控相关知识。
Elasticsearch 提供了大量的 Metric,可以帮助您检测到问题的迹象,在遇到节点不可用、out-of-memory、long garbage collection times 的时候采取相应措施。 一些关键的检测如下:
这里提供了一个metric 搜集和监控的框架 Monitoring 101 series,所有这些指标都可以通过 Elasticsearch 的 API 以及 Elasticsearch 的 Marvel 和 Datadog 等通用监控工具访问。
搜索请求是Elasticsearch中的两个主要请求类型之一,另一个是索引请求。 这些请求有时类似于传统数据库系统中的读写请求。 Elasticsearch提供与搜索过程的两个主要阶段(查询和获取)相对应的度量。 下图显示了从开始到结束的搜索请求的路径。
step1.客户端向Node 2 发送搜索请求
step2.Node 2(此时客串协调角色)将查询请求发送到索引中的每一个分片的副本
step3. 每个分片(Lucene实例,迷你搜素引擎)在本地执行查询,然后将结果交给Node 2。Node 2 sorts and compiles them into a global priority queue.
step4. Node 2发现需要获取哪些文档,并向相关的分片发送多个GET请求。
step5. 每个分片loads documents然后将他们返回给Node 2
step6. Node 2将搜索结果交付给客户端
节点处理时,由谁分发,就由谁交付。
如果您使用Elasticsearch主要用于搜索,或者如果搜索是面向客户的功能。您应该监视查询延迟和设定阈值。 监控关于查询和提取的相关指标很重要,可以帮助您确定搜索随时间的变化。 例如,您可能希望跟踪查询请求的尖峰和长期增长,以便您可以做好准备。
搜索性能指标的要点:
索引请求类似于传统数据库系统中的写入请求,如果es的写入工作量很重,那么监控和分析您能够如何有效地使用新数据更新索引非常重要。在了解指标之前,让我们来探索Elasticsearch更新索引的过程,在新数据被添加进索引、更新或删除已有数据,索引中的每个shard都有两个过程:refresh 和 flush Index fresh 新索引的文档不能立马被搜索的。 首先,它们被写入一个内存中的缓冲区(in-memory buffer),等待下一次索引刷新,默认情况下每秒一次。刷新是以in-memory buffer为基础创建in-memory segment的过程(The refresh process creates a new in-memory segment from the contents of the in-memory buffer )。这样索引进的文档才能是可被搜索的,创建完segment后,清空buffer 如下图:
A special segment on segments
索引由shards构成,shard又由很多segments组成,The core data structure from Lucene, a segment is essentially a change set for the index. 这些segments在每次刷新的时候被创建,随后会在后台进行合并,以确保资源的高效利用(每个segment都要占file handles、memory、CPU) segments 是mini的倒排索引,这些倒排索引映射了terms到documents。每当搜索索引的时候,每个主副shards都必须被遍历。更深一步说shards上的每个segment会被依次搜索。 segment是不可变的,因此updating a document 意味着如下:
当多个outdated segment合并后才会被删除。(意思是不单个删除,合并后一起删)。
Index flush
在新索引的document添加到in-memory buffer的同时,它们也会被附加到分片的translog(a persistent, write-ahead transaction log of operations)中。每隔30分钟,或者每当translog达到最大大小(默认情况下为512MB)时,将触发flush 。在flush 期间,在in-memory buffer上的documents会被refreshed(存到新的segments上),所有内存中的segments都提交到磁盘,并且translog被清空。 translog有助于防止节点发生故障时的数据丢失。It is designed to help a shard recover operations that may otherwise have been lost between flushes. 这个translog每5秒将操作信息(索引,删除,更新或批量请求(以先到者为准))固化到磁盘上。
Elasticsearch提供了许多指标,可用于评估索引性能并优化更新索引的方式。
索引性能指标的要点:
Indexing latency: Elasticsearch不会直接公开此特定指标,但是监控工具可以帮助您从可用的index_total和index_time_in_millis指标计算平均索引延迟。 如果您注意到延迟增加,您可能会一次尝试索引太多的文档(Elasticsearch的文档建议从5到15兆字节的批量索引大小开始,并缓慢增加)。如果您计划索引大量文档,并且不需要立即可用于搜索。则可以通过减少刷新频率来优化。索引设置API使您能够暂时禁用刷间隔。
curl -XPUT <nameofhost>:9200/<name_of_index>/_settings -d '{
"index" : {
"refresh_interval" : "-1"
}
}'
完成索引后,您可以恢复为默认值“1s”</name_of_index>
Flush latency: 在flush完成之前,数据不会被固化到磁盘中。因此追踪flush latency很有用。比如我们看到这个指标稳步增长,表明磁盘性能不好。这个问题将最终导致无法向索引添加新的数据。 可以尝试降低index.translog.flush_threshold_size。这个设置决定translog的最大值(在flush被触发前)。
在运行Elasticsearch时,内存是您要密切监控的关键资源之一。 Elasticsearch和Lucene以两种方式利用节点上的所有可用RAM:JVM heap和文件系统缓存。 Elasticsearch运行在Java虚拟机(JVM)中,这意味着JVM垃圾回收的持续时间和频率将成为其他重要的监控领域。
JVM heap: A Goldilocks tale
Elasticsearch强调了JVM堆大小的重要性,这是“正确的” - 不要将其设置太大或太小,原因如下所述。 一般来说,Elasticsearch的经验法则是将少于50%的可用RAM分配给JVM堆,而不会超过32 GB。 您分配给Elasticsearch的堆内存越少,Lucene就可以使用更多的RAM,这很大程度上依赖于文件系统缓存来快速提供请求。 但是,您也不想将堆大小设置得太小,因为应用程序面临来自频繁GC的不间断暂停,可能会遇到内存不足错误或吞吐量降低的问题 Elasticsearch的默认安装设置了1 GB的JVM heap大小,对于大多数用例来说,太小了。 您可以将所需的heap大小导出为环境变量并重新启动Elasticsearch:
export ES_HEAP_SIZE=10g
如上我们设置了es heap大小为10G,通过如下命令进行校验:
curl -XGET http://:9200/_cat/nodes?h=heap.max
Garbage collection
Elasticsearch依靠垃圾收集过程来释放heap memory。因为垃圾收集使用资源(为了释放资源!),您应该注意其频率和持续时间,以查看是否需要调整heap大小。设置过大的heap会导致GC时间过长,这些长时间的停顿会让集群错误的认为该节点已经脱离。
JVM指标的要点
虽然Elasticsearch通过API提供了许多特定于应用程序的指标,但您也应该从每个节点收集和监视几个主机级别的指标。 Host指标要点:
es节点使用线程池来管理线程如何消耗内存和CPU。 由于线程池设置是根据处理器数量自动配置的,所以调整它们通常没有意义。However, it’s a good idea to keep an eye on queues and rejections to find out if your nodes aren’t able to keep up; 如果无法跟上,您可能需要添加更多节点来处理所有并发请求。Fielddata和过滤器缓存使用是另一个要监视的地方,as evictions may point to inefficient queries or signs of memory pressure。
Thread pool queues and rejections每个节点维护许多类型的线程池; 您要监视的确切位置将取决于您对es的具体用途,一般来说,监控的最重要的是搜索,索引,merge和bulk,它们与请求类型(搜索,索引,合并和批量操作)相对应。 线程池队列的大小反应了当前等待的请求数。 队列允许节点跟踪并最终服务这些请求,而不是丢弃它们。 一旦超过线程池的maximum queue size,Thread pool rejections就会发生。
指标要点:
Bulk rejections and bulk queues: 批量操作是一次发送许多请求的更有效的方式。 通常,如果要执行许多操作(创建索引或添加,更新或删除文档),则应尝试以批量操作发送请求,而不是发送许多单独的请求。 bulk rejections 通常与在一个批量请求中尝试索引太多文档有关。根据Elasticsearch的文档,批量rejections并不是很需要担心的事。However, you should try implementing a linear or exponential backoff strategy to efficiently deal with bulk rejections。
Cache usage metrics: 每个查询请求都会发送到索引中的每个分片的每个segment中,Elasticsearch caches queries on a per-segment basis to speed up response time。另一方面,如果您的缓存过多地堆积了这些heap,那么它们可能会减慢速度,而不是加快速度! 在es中,文档中的每个字段可以以两种形式存储:exact value 和 full text。 例如,假设你有一个索引,它包含一个名为location的type。每个type的文档有个字段叫city。which is stored as an analyzed string。你索引了两个文档,一个的city字段为“St. Louis”,另一个的city字段为“St. Paul”。在倒排索引中存储时将变成小写并忽略掉标点符号,如下表
分词的好处是你可以搜索st。结果会搜到两个。如果将city字段保存为exact value,那只能搜“St. Louis”, 或者 “St. Paul”。 Elasticsearch使用两种主要类型的缓存来更快地响应搜索请求:fielddata和filter。
1.扫描倒排索引查看哪些文档(documents)包含这个term(在本例中为Doc1和Doc2) 。
2.对1中的每个步骤,通过索引中的每个term 从文档中来收集tokens,创建如下结构。
3.现在反向索引被再反向,从doc中compile 独立的tokens(st, louis, and paul)。compile这样的fielddata可能会消耗大量堆内存。特别是大量的documents和terms的情况下。 所有字段值都将加载到内存中。对于1.3之前的版本,fielddata缓存大小是无限制的。 从1.3版开始,Elasticsearch添加了一个fielddata断路器,如果查询尝试加载需要超过60%的堆的fielddata,则会触发。
pending task只能由主节点来进行处理,这些任务包括创建索引并将shards分配给节点。任务分优先次序。如果任务的产生比处理速度更快,将会产生堆积。待处理任务的数量是您的群集运行平稳的良好指标,如果您的主节点非常忙,并且未完成的任务数量不会减少,我们需要仔细检查原因。
默认情况下,集群对外开启了 9200 端口,用于整个集群的管理操作、索引的增删改查,以及整体集群、节点、索引的状态信息,通常需要关注如下几个对外 API。
如下表列出了一些常见的指标以及对应的 API 接口。
Node 状态接口是一个功能强大的工具,能提供除集群运行状况和挂起任务外几乎全部的性能指标。
注意: 对于节点状态 API 来讲,最重要的就是 indices 。在 ElasticSearch 的所有对外接口参数中,pretty 的 URI 参数标识以 json 格式进行输出,否则将输出的是字符串。
# 查看集群全部节点的指标
$ curl "localhost:9200/_nodes/stats"
# 输出的3级指标
{
"_nodes":{
"total":6,
"successful":6,
"failed":0
},
"cluster_name":"prod-one-id",
"nodes":{
"T3bjsBQUSeu0bstT7m8LCA":{
"timestamp":1604802841283,
"name":"iZbp11gqesu0zk5sqrgwu4Z",
"transport_address":"172.16.71.231:9300",
"host":"172.16.71.231",
"ip":"172.16.71.231:9300",
"roles":Array[3],
"indices":Object{...},
"os":Object{...},
"process":Object{...},
"jvm":Object{...},
"thread_pool":Object{...},
"fs":Object{...},
"transport":Object{...},
"http":Object{...},
"breakers":Object{...},
"script":Object{...},
"discovery":Object{...},
"ingest":Object{...},
"adaptive_selection":Object{...}
},
"93HMUUReSYeQEaNTfNUWCQ":Object{...},
"_6TNUy4nSZ-jxumgiroqlg":Object{...},
"cEEZcNJGS0mSgppe82SZ9Q":Object{...},
"utKipUwYQpi9ac4Q7sI53g":Object{...},
"SE7IppNARjugsLSnPhil9g":Object{...}
}
}
在集群规模比较大时,整个 node 的状态数据会比较多,此时可以指定 id,address,name 或者节点的其他属性来查看指定节点的状态信息。
# 可以指定节点id,ip,name
$ curl -s "http://localhost:9200/_nodes/T3bjsBQUSeu0bstT7m8LCA/stats"
$ curl -s "http://localhost:9200/_nodes/172.16.71.231/stats"
$ curl -s "http://localhost:9200/_nodes/iZbp11gqesu0zk5sqrgwu4Z/stats"
当然,有时候,我们依然觉得,单个节点的指标比较多,我们对某些指标项目进行过滤。
# 查看某个节点的指定指标
$ curl -s "http://localhost:9200/_nodes/172.16.71.231/stats/jvm,os "
集群指标接口提供了集群范围内的信息,因此,它基本上是集群中每个节点的所有统计数据相加。虽然提供的数据不够详细,但是对于快速了解集群状态是非常有用的。
集群级别比较重要的几个指标:
# 查看集群整体状况
$ curl -s "localhost:9200/_cluster/stats"
# 输出的三级指标
{
"_nodes":{
"failed":0,
"successful":6,
"total":6
},
"cluster_name":"prod-one-id",
"indices":{
"completion":Object{...},
"count":5,
"docs":Object{...},
"fielddata":Object{...},
"query_cache":Object{...},
"segments":Object{...},
"shards":Object{...},
"store":Object{...}
},
"nodes":{
"count":Object{...},
"fs":Object{...},
"jvm":Object{...},
"network_types":Object{...},
"os":Object{...},
"plugins":Array[0],
"process":Object{...},
"versions":Array[1]
},
"status":"green",
"timestamp":1604806385128
}
Index 状态接口可以反映一个指定索引的状态信息。
使用该接口可以快速查看索引的分片状态,主分片的各个操作详情统计,以及单个索引的详情统计,indices 下具体索引的详情信息 (indexing,get,search,merges,refresh,flush)
# 查看指定索引的状态信息
# .elastichq 为索引名称
$ curl -s localhost:9200/.elastichq/_stats
# 输出的三级指标
{
"_shards":{
"total":10,
"successful":10,
"failed":0
},
"_all":{
"primaries":Object{...},
"total":Object{...}
},
"indices":{
".elastichq":{
"primaries":Object{...},
"total":Object{...}
}
}
}
在所有对外接口中,提供集群级别的运行态数据外,还提供了集群健康状态的接口。该接口可以公开整个集群运行状况的关键信息。
$ curl localhost:9200/_cluster/health
# 集群健康状态
{
"cluster_name":"prod-one-id",
"status":"green",
"timed_out":false,
"number_of_nodes":6,
"number_of_data_nodes":6,
"active_primary_shards":13,
"active_shards":31,
"relocating_shards":0,
"initializing_shards":0,
"unassigned_shards":0,
"delayed_unassigned_shards":0,
"number_of_pending_tasks":0,
"number_of_in_flight_fetch":0,
"task_max_waiting_in_queue_millis":0,
"active_shards_percent_as_number":100
}
待处理任务 API 是一种快速查看群集中待处理任务的快速方法。需要注意的是,pending task 是只有主节点才能执行的任务,比如创建新索引或者重建集群的分片。
如果主节点无法跟上这些请求的速度,则挂起的任务将开始排队。
$ curl localhost:9200/_cluster/pending_tasks
{"tasks":[]}
正常情况下,将返回空的待处理任务。否则,您将收到关于每个未决任务的优先级、它在队列中等待了多长时间以及它代表了什么动作的信息。
{
"tasks" : [ {
"insert_order" : 13612,
"priority" : "URGENT",
"source" : "delete-index [old_index]",
"executing" : true,
"time_in_queue_millis" : 26,
"time_in_queue" : "26ms"
}, {
"insert_order" : 13613,
"priority" : "URGENT",
"source" : "shard-started ([new_index][0], node[iNTLLuV0R_eYdGGDhBkMbQ], [P], v[1], s[INITIALIZING], a[id=8IFnF0A5SMmKQ1F6Ot-VyA], unassigned_info[[reason=INDEX_CREATED], at[2016-07-28T19:46:57.102Z]]), reason [after recovery from store]",
"executing" : false,
"time_in_queue_millis" : 23,
"time_in_queue" : "23ms"
}, {
"insert_order" : 13614,
"priority" : "URGENT",
"source" : "shard-started ([new_index][0], node[iNTLLuV0R_eYdGGDhBkMbQ], [P], v[1], s[INITIALIZING], a[id=8IFnF0A5SMmKQ1F6Ot-VyA], unassigned_info[[reason=INDEX_CREATED], at[2016-07-28T19:46:57.102Z]]), reason [master {master-node-1}{iNTLLuV0R_eYdGGDhBkMbQ}{127.0.0.1}{127.0.0.1:9300} marked shard as initializing, but shard state is [POST_RECOVERY], mark shard as started]",
"executing" : false,
"time_in_queue_millis" : 20,
"time_in_queue" : "20ms"
} ]
}
CAT 接口也提供了一些查看相同指标的可选方案,类似于 UNIX 系统中的 cat 命令。
$ curl http://localhost:9200/_cat
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates
比如,我们可以使用 curl localhost:9200/_cat/nodes?help 来查看 node api 相关的指标和描述,进而采用这些描述来查询具体的指标项。
如果我们只想查看节点的堆内存使用率、合并数量 (merges) 以及段数量 (segments),可以采用如下方式来查看:
# 指定查看每个节点的堆内存使用率,段数量和合并数量
$ curl "http://localhost:9200/_cat/nodes?h=http,heapPercent,segmentsCount,mergesTotal"
172.16.71.231:9200 56 99 108182
172.16.71.229:9200 31 95 122551
172.16.71.232:9200 50 66 73871
172.16.71.230:9200 41 63 76470
172.16.71.234:9200 32 64 93256
172.16.71.233:9200 14 90 136450
注意: 上述输出相当于是 Node Stats API 中的 jvm.mem.heap_used_percent,segments.count,merges.total,整个 CAT 接口是一个可以快速获取集群,节点,索引以及分片的状态数据,并且能够以可读的方式展示出来。
虽然整个 ES 对外的接口已经能够提供很好的接口来描述瞬时的指标,但是通常情况下,我们有很多节点需要进行持续的监控,而接口的 JSON 格式又不便于我们进行解析和分析,很难快速识别到问题节点并及时发现问题趋势。
为了更加有效的监控 ElasticSearch,我们通常需要一些工具来定期采集 API 的指标数据,然后聚合指标结果来反应当前集群的整体状态。而在开源社区中,也产生了很多这种类似的工具系统。
ElasticHQ 是一个可座位托管方案,插件化下载的开源监控工具。它能够提供你的集群,节点,索引,以及一些相关的查询和映射的指标。
ElasticHQ 会自动对指标进行颜色编码,以突出潜在的问题。
插件化安装:$ ${ES_HOME}/bin/elasticsearch-plugin install royrusso/elasticsearch-HQ
安装完成后,可以访问 http://localhost:9200/_plugin/hq/ 来访问当前集群的监控信息。
使用 Docker 进行托管方式安装:$ docker run -itd -p 8081:5000 -v /opt/data/elastichq:/src/db --restart=always --name elastichq elastichq/elasticsearch-hq
接下来,就可以访问主机的 8081 端口来查看 ElasticHQ 的监控管理了,需要注意的是,此时需要添加集群地址。
开源领域也有其他插件,比如 kopf 和 Cerebro 前者比较老,且现在不再更新了,而后者是一个比较全面的监控工具,且支持 LDAP 工具登录。
Cerebro:https://github.com/lmenezes/cerebro/
# ldap 的配置信息
$ cat env-ldap
# Set it to ldap to activate ldap authorization
AUTH_TYPE=ldap
# Your ldap url
LDAP_URL=ldap://exammple.com:389
LDAP_BASE_DN=OU=users,DC=example,DC=com
# Usually method should be "simple" otherwise, set it to the SASL mechanisms
LDAP_METHOD=simple
# user-template executes a string.format() operation where
# username is passed in first, followed by base-dn. Some examples
# - %s => leave user untouched
# - %s@domain.com => append "@domain.com" to username
# - uid=%s,%s => usual case of OpenLDAP
LDAP_USER_TEMPLATE=%s@example.com
# User identifier that can perform searches
LDAP_BIND_DN=admin@example.com
LDAP_BIND_PWD=adminpass
# Group membership settings (optional)
# If left unset LDAP_BASE_DN will be used
# LDAP_GROUP_BASE_DN=OU=users,DC=example,DC=com
# Attribute that represent the user, for example uid or mail
# LDAP_USER_ATTR=mail
# If left unset LDAP_USER_TEMPLATE will be used
# LDAP_USER_ATTR_TEMPLATE=%s
# Filter that tests membership of the group. If this property is empty then there is no group membership check
# AD example => memberOf=CN=mygroup,ou=ouofthegroup,DC=domain,DC=com
# OpenLDAP example => CN=mygroup
# LDAP_GROUP=memberOf=memberOf=CN=mygroup,ou=ouofthegroup,DC=domain,DC=com
$ docker run -p 9000:9000 --env-file env-ldap lmenezes/cerebro
登录首页
集群概况以及索引信息
节点状态
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!