前言:最近在使用云上的 ElasticSearch 服务时,由于前期没有配置监控和告警,导致磁盘被写满,而此时对外的 API 已经不可用。准备去云厂商控制台进行磁盘扩容时又因为集群状态非
green,从而导致陷入僵局,紧急联系云厂商后才恢复,整个异常虽然很小但是前后修复过程依然比较繁琐和冗长。基于此,将过去总结的 ElasticSearch 相关监控重新学习并梳理一遍。
Elasticsearch 提供了大量的指标,可以帮助您检测到问题的迹象,并在遇到诸如不可靠节点,内存不足错误以及长时间垃圾收集时间等问题时采取行动。
整个监控需要关注的几个关键领域是:
一、如何监控ElasticSearch的性能
1. 什么是Elasticsearch?
Elasticsearch是一个开源的分布式文档存储和搜索引擎,可以实时存储和检索数据结构。它是依赖于一个用Java编写的全文本搜索引擎 Apache Lucene
Elasticsearch 以结构化 JSON 文档的形式表示数据,并通过 RESTful API 来对外统一提供服务,并以此来提供各种语言的对外 API。
同时它的弹性还体现在很容易水平扩展,只需添加更多节点来分配负载。如今,大多数的互联网公司都使用它来存储、搜索和动态分析大量数据。
1.1 Elasticsearch 是如何工作的
通常情况下,我们需要优化开源组件,第一件事就是需要知道它内部的工作原理,再根据内部的具体使用场景,就能够将其组件做到最优化的状态。
在一个Elasticsearch 集群中,由以下集中角色组成。
注意: 节点的角色状态,可以在elasticsearch.yml 中通过如下方式来选定:
# 当一个节点既不是master也不是data时,他就是client节点
node.master: false
node.data: false
master: 主要负责整个集群数据的读写转发,协调集群任务,比如跨节点分发碎片,创建和删除索引。是该角色的都有资格选举为整个集群的master节点,该参数可以保证集群的master可用性,即可读写。通常情况下主节点也会默认承载数据节点,但是作为核心的业务集群而言,用户可以启用专用的主节点,来提高整体可靠性,这些节点不存储数据node.data: false。在高使用率的环境中,将主角色从数据节点移开有助于确保始终有足够的资源分配给只有符合主资格的节点才能处理的任务。data: 数据节点,主要用于存储索引分片的数据。默认情况下,每个节点都可以作为数据节点来存储数据,并执行与索引、搜索和聚合数据相关的操作。在较大集群中,需要专门设置独立的node节点来创建专用的数据节点node.master: false。确保这些节点有足够的资源来处理与数据相关的请求,而不需要承担与集群相关的管理任务的额外工作负载。indicent: 客户端节点,通常通过多一层代理来实现性能的扩展。当设置node.data: false和node.master: false时,节点为客户端节点,独立的客户端节点将承载着整个集群的负载均衡器,来帮助路由索引和搜索请求。客户端节点帮助承担一些搜索工作负载,这样数据和符合主条件的节点就可以专注于它们的核心任务。但通常情况下,可能不需要单独设置客户端节点,因为数据节点本身也可以承担路由请求。但是当你的search/index工作负载比较高时,增加额外的客户端节点可以有利于请求路由。
$ curl 172.16.74.117:9200/_cat/nodes?v
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
172.16.74.120 28 99 8 1.45 1.34 1.54 mdi - iZbp14u93p47v72i0yvxicZ
172.16.74.118 66 97 8 0.96 1.36 1.45 mdi - iZbp14u93p47v72i0yvxigZ
172.16.75.9 29 97 7 1.47 1.65 1.46 mdi - iZbp109ka6i98dvzuntlf3Z
172.16.75.8 64 98 7 2.52 1.75 1.56 mdi - iZbp109ka6i98dvzuntlezZ
172.16.75.12 65 98 7 1.14 1.51 1.54 mdi - iZbp109ka6i98dvzuntlf1Z
172.16.74.116 64 94 8 1.46 1.67 1.74 mdi * iZbp14u93p47v72i0yvxidZ
172.16.74.59 8 68 11 0.53 0.75 0.89 i - iZbp15vaovl42trxmrjetbZ
172.16.74.115 10 96 8 1.48 1.50 1.52 mdi - iZbp14u93p47v72i0yvxifZ
172.16.75.10 30 98 7 1.78 1.38 1.30 mdi - iZbp14u93p47v72i0yvxihZ
172.16.74.119 44 96 8 2.77 2.03 1.81 mdi - iZbp14u93p47v72i0yvxieZ
172.16.74.117 56 99 8 2.04 1.96 1.77 mdi - iZbp14u93p47v72i0yvxihZ
172.16.75.11 35 99 8 1.52 1.55 1.57 mdi - iZbp109ka6i98dvzuntleyZ
172.16.74.60 7 68 10 0.96 0.97 1.00 i - iZbp15vaovl42trxmrjetaZ
172.16.75.13 67 97 8 1.46 1.46 1.56 mdi - iZbp109ka6i98dvzuntlf2Z
1.2 Elasticsearch 是如何组织数据的
ElasticSearch 中,数据的存储涉及到如下几个概念:
index: 在es中,数据通常会被存储在索引中,相当于关系型数据哭中的tabledocuments: 每个索引中包含一组相似的json格式的文档,该文档相当于关系型数据库中的rows(注意:在后来的版本中,取消了type的概念,官方认为index层就应该区分了类型)shard: 用于真正存储索引数据的逻辑存储,通常一个索引数据会存储在多个shard的多个副本中,用于提供数据索引的性能和数据的可用性。每个shard都是Lucene的一个完整实例,就像一个迷你搜索引擎。
es 的秘密武器,其实是全文搜索引擎Lucene的倒排索引,当文档被索引时,Elasticsearch会自动为每个字段创建一个反向索引,反向索引将术语映射到包含这些术语的文档。
当你去创建索引时,可以显示的指定索引的分片数量,以及每个主分片的副本数量。需要注意的是,一旦创建了索引,就不能修改主分片的数量了,否则就需要重新索引数据,而副本的数量则是可以在整个索引期间进行动态修改的。
注意:为了防止数据丢失,主分片需要确保每个副本分片不会处于相同的节点,否则可能造成分片数据丢失的风险。另外,如果是物理硬件,需要考虑到机架的可用性,幸运的是,es 也提供了aware rack的功能特性。
2. 需要监控ElasticSearch 的关键性能指标
ElasticSearch 提供了大量的指标,可以帮助您检测故障的迹象,并在遇到诸如不可靠节点、内存不足错误和垃圾收集时间过长的问题时采取行动。
2.1 搜索(search)性能指标
在Elasticsearch中,搜索请求和索引请求(search and index requests)是两种主要请求类型之一,类似与传统数据库中的读写请求。ElasticSearch 提供了在整个search 请求过程中的主要指标(query and fetch)。如下图,表明了一个search从开始到结束的整个过程。
- Step1: 客户端发送请求到node-2
- Step2: Node-2发送一个复制请求,到该索引的分片中
- Step3: 每个分片执行查询逻辑并返回结果给node-2节点,Node-2对全部结果进行全局排序并编译成全局优先级队列
- Step4: Node-2节点找出需要获取哪些文档,并向相关的分片发送一个multi GET请求
- Step5: 每个分片加载文档并将它们返回到Node-2
- Step6: Node-2将搜索结果交付给客户端
如果您使用Elasticsearch主要用于搜索,或者搜索是面向客户的特性,该特性是整个业务的关键,那么您应该监视查询延迟,并在其超过阈值时采取行动。监控查询和获取的相关指标是很重要的,这可以帮助您确定您的搜索在一段时间内的执行情况。比如,你可能希望跟踪查询请求的峰值和长期增长,以此来准备调整您的配置以优化更好的性能和可靠性。
如下是一些核心指标:
| 指标描述 | 指标名称 | 指标类型 |
|---|---|---|
| 查询(query)请求总数 | indices.search.query_total | 吞吐 |
| 在查询(query)上花费的总时间 | indices.search.query_time_in_millis | 性能 |
| 当前在执行的查询(query)数量 | indices.search.query_current | 吞吐 |
| fetches的总数 | indices.search.fetch_total | 吞吐 |
| fetches上花费的总时间 | indices.search.fetch_time_in_millis | 性能 |
| 当前在执行的fetch数量 | indices.search.fetch_current | 吞吐 |
因此,关于搜索 (searching) 性能指标,如下几个指标需要关注:
Query load: 可以通过查看请求的总数,来查看集群的整体吞吐,并对潜在的突生突降指标进行预警。同时也需要监控线程池队列的大小thread_poolQuery latency: 查询延迟,我们可以通过es提供的指标进行计算,通过定期抽样查询总数和总运行时间来计算平均查询延迟。如果延迟超过阈值,请设置警报,如果触发警报,请查找潜在的资源瓶颈,或者调查是否需要优化查询。Fetch latency: 在fetch阶段,通常比查询阶段花费的时间要少得多,如果这个指标比较高,可能表示磁盘速度比较慢,文档内容较多(搜索结果中突出显示相关文本)或者请求太多导致的结果(没有选择分页的结果,即全量数据查询)。
2.2 索引(indexing)性能指标
如果你的 es 的写请求比较多,那么监控和分析 如何使用新信息更新索引是非常有效的。
我们都知道,当向索引添加新信息或更新或删除现有信息时,索引中的每个分片将通过两个过程更新:
- refresh
- flush
index refresh
新索引的文档不能立即用于搜索,首先,它们被写入内存中的缓冲区,等待下一次索引刷新 refresh (默认情况下每秒刷新一次)。
刷新过程从内存缓冲区的内容创建一个新的内存段(使新索引的文档可搜索),然后清空缓冲区,过程如下图。
索引的分片由多个不同的段 segment 组成,它是 Lucene 的核心数据结构,本质是索引的一个可更改的数据集合。这些段在每次刷新时创建,随后在后台随着时间的推移合并在一起,以确保有效地使用资源(每个段使用文件句柄、内存和CPU)。
段是小型倒排索引,将数据映射到包含这些数据的文档。每次搜索一个索引时,必须依次搜索每个切分中的每个段来搜索每个分片的主或副本版本。
段是不可变的,所以更新文档意味着:
- 在刷新过程中将信息写入新段
- 将旧信息标记为已删除
当过时的段与另一个段合并时,旧的信息最终会被删除。
index flush
当将新索引的文档添加到内存缓冲区的时候,它们还将被添加到分片的 translog 中 ,它是一个持久的,提前写入的操作事务日志。
每隔30分钟,或者当 translog 达到最大时(512M)时,就会触发一次 flush。
在 flush 过程中,在内存缓冲区的任何文档都会被 refresh (存储到新的segments中),并且所有的内存中的segment都将会被提交到磁盘中,整个 translog 将被清理。
translog 可以防止在节点失败时数据丢失,它的设计是为了帮助分片在 flush 过程中数据丢失时的恢复操作。
translg 会每隔5s被提交到磁盘,或者当有有一个成功的index,delete,update或者bulk请求时。
整个flush过程如下:
可以使用如下这些指标来评估索引性能和优化您更新索引的方式:
| 指标描述 | 指标名称 | 指标类型 |
|---|---|---|
| 被索引的文档总数 | indices.indexing.index_total | 吞吐 |
| 索引文档花费的总时间 | indices.indexing.index_time_in_millis | 性能 |
| 当前正在索引的文档总数 | indices.indexing.index_current | 吞吐 |
| 索引refresh的总数 | indices.refresh.total | 吞吐 |
| refresh索引总共话费的时间 | indices.refresh.total_time_in_millis | 性能 |
| flush到磁盘的索引总数 | indices.flush.total | 吞吐 |
| flush到磁盘的索引总花费时间 | indices.flush.total_time_in_millis | 性能 |
因此,关于索引 (indexing) 性能指标,如下几个指标需要关注:
indexing latency: 需要注意的是,es并没有直接公开该指标,但是可以使用index_total和index_time_in_millis指标计算出来。
如果注意到延迟增加,可以尝试使用批量接口一次索引多个文档。如果计划索引大量文档,并且不需要新的信息立即用于搜索,那么可以通过降低刷新频率来优化索引性能,直到完成索引.
# 关闭刷新频率限制,一旦索引完成,就可以恢复到默认值1。
curl -XPUT <nameofhost>:9200/<name_of_index>/_settings -d '{
"index" : {
"refresh_interval" : "-1"
}
}'
Flush latency: 由于数据在刷新成功完成之前不会持久保存到磁盘,因此跟踪刷新延迟并在性能开始下降时采取行动可能很有用。
如果您看到此指标稳步增长,则可能表明磁盘速度较慢存在问题;此问题可能会升级,并最终使您无法向索引中添加新信息。
可以使用index.translog.flush_threshold_size来修改flush的参数,降低flush阈值,该参数可以决定在flush触发之前,日志能够保留多大。如果是写多读少的场景,可以使用iostat 来实时关注磁盘的压力。
2.3 内存使用和垃圾回收
在运行 Elasticsearch 时,内存是需要密切监视的关键资源之一。
Elasticsearch 和 Lucene 会以两种方式使用节点上的全部可用内存:JVM堆和文件系统缓存(file system cache),因此,在ES运行期间,整个JVM的垃圾回收持续时间和频率将很值得监控。
JVM heap
ElasticSearch 官方建议分配给JVM堆的可用RAM不超过50%,并且不超过32 GB。分配给Elasticsearch的堆内存越少,Lucene可用的RAM就越多,Lucene严重依赖文件系统缓存来快速处理请求。但是,您也不希望将堆大小设置得太小,因为您可能会遇到内存不足错误或吞吐量降低,因为应用程序面临频繁的垃圾收集导致的持续的短暂停。ElasticSearch 设置堆内存可以参考官方文档,或者参考使用说明。
a-heap-of-trouble:www.elastic.co/cn/blog/a-h…
# 查看节点的堆内存使用情况
curl -XGET http://<nameofhost>:9200/_cat/nodes?h=heap.max
垃圾回收
Elasticsearch依赖于垃圾收集进程来释放堆内存。因为垃圾收集使用资源(为了释放资源!),您应该密切关注它的频率和持续时间,以确定是否需要调整堆大小。将堆设置得过大会导致垃圾收集时间过长;这些过度的暂停是危险的,因为它们可能导致集群错误地将节点注册为已经脱离网络。
如下是一些关于垃圾回收相关的指标:
| 指标描述 | 名称 | 指标类型 |
|---|---|---|
| 年轻代GC总次数 | jvm.gc.collectors.young.collection_count | 其他 |
| 年轻代GC总时间 | jvm.gc.collectors.young.collection_time_in_millis | 其他 |
| 老年代GC总次数 | jvm.gc.collectors.old.collection_count | 其他 |
| 老轻代GC总时间 | jvm.gc.collectors.old.collection_time_in_millis | 其他 |
| JVM堆内存使用率 | jvm.mem.heap_used_percent | 资源使用率 |
| JVM堆提交次数 | jvm.mem.heap_committed_in_bytes | 资源使用率 |
值得关注的几个指标:
JVM heap in use: 当JVM堆使用率达到75%时,将设置Elasticsearch来启动垃圾收集。当JVM使用率长期处于较高位置,我们就需要增加堆的空间,或者通过增加节点来向外扩展集群。JVM heap used vs. JVM heap committed:Garbage collection duration and frequency: 当JVM进行GC时,年轻代和老年代都会经历STW过程,在这期间,进程将无法提供工作。需要注意的是,主节点每隔30秒检查其他节点的状态,如果任何节点的垃圾收集时间超过30秒,它将导致主节点认为该节点已经失败。Memory usage: Elasticsearch很好地利用了没有分配给JVM堆的任何RAM。和Kafka一样,Elasticsearch的设计依赖于操作系统的文件系统缓存来快速可靠地服务请求。如果segment最近被写入磁盘,那相关数据就已经在缓存中了,但是,如果一个节点已经关闭并重新启动,那么第一次查询一个段时,很可能必须从磁盘读取信息。这就是为什么确保集群保持稳定、节点不会崩溃非常重要的原因之一。
2.4 主机级别的网络和系统指标
| 名称 | 指标类型 |
|---|---|
| 磁盘可用空间 | 资源使用率 |
| I/O utilization | 资源使用率 |
| CPU usage | 资源使用率 |
| Network bytes sent/received | 资源使用率 |
| Open file descriptors | 资源使用率 |
虽然 Elasticsearch 通过 API 提供了许多特定于应用程序的指标,但您还应该从每个节点收集和监控多个主机级指标。
需要重点关注的指标:
I/O utilization: 在创建、查询和合并段的过程中,Elasticsearch会对磁盘进行大量的写入和读取,对于有大量的写操作的集群,集群持续的会有大量的I/O 活动过的,建议可以使用SSD来提高性能。CPU utilization on your nodes: 通常情况下,可以创建三个不同的图来表示集群中的每一组节点(例如数据节点、主节点和客户端节点),以查看一种类型的节点与另一种节点相比是否超载了活动。如果您看到CPU使用量增加,这通常是由繁重的搜索或索引工作负载造成的。设置一个通知以查明您的节点CPU使用率是否持续增加,并添加更多节点以在需要时重新分配负载。Network bytes sent/received: 节点之间的通信是平衡集群的关键组成部分。Elasticsearch 提供了关于集群通信的传输度量,但是您还可以查看发送和接收字节的速率,以了解您的网络正在接收多少流量。Open file descriptors: 文件描述符用于节点到节点的通信、客户端连接和文件操作。如果这个数字达到了你的系统的最大容量,那么新的连接和文件操作将不可能,直到旧的已经关闭。如果超过80%的可用文件描述符正在使用中,您可能需要增加系统的最大文件描述符计数。大多数Linux系统对每个进程只允许1024个文件描述符,在世纪运行过程中,我们建议应将OS文件描述符计数重置为更大的值,例如64000。HTTP connections: 整个 es 的数据读写都是通过 RESTful API 通信的,因此需要关注 HTTP 链接相关的监控指标。如果打开的连接数http.current_open不断增加,则表明客户端没有正确的使用持久链接,重新建立连接会使请求响应时间增加几毫秒甚至几秒http.total_opened。确保您的客户端配置正确以避免对性能的负面影响,或者使用官方的Elasticsearch客户端,它已经正确配置了HTTP连接。
2.5 集群健康和节点可用性
| 指标描述 | 指标 | 指标类型 |
|---|---|---|
| 集群状态(green, yellow, red) | cluster.health.status | 其他 |
| 节点数量 | cluster.health.number_of_nodes | 资源可用性 |
| 正在初始化的分片数 | cluster.health.initializing_shards | 资源可用性 |
| 未分配的分片数 | cluster.health.unassigned_shards | 资源可用性 |
Cluster status: 如果集群状态为黄色,则至少有一个副本碎片未分配或丢失,搜索结果仍将是完整的,但如果更多碎片消失,您可能会丢失数据;红色的集群状态表示至少丢失了一个主分片,并且丢失了数据,这意味着搜索将返回部分结果。通常情况下,我们需要尽快发现集群的一场状态。Initializing and unassigned shards: 当您第一次创建索引时,或者当一个节点被重新引导时,由于主节点试图为集群中的节点分配切分,因此在转换到启动或未分配状态之前,它的切分将短暂地处于初始化状态。如果您看到分片处于初始化或未分配状态的时间过长,这可能是集群不稳定的警告信号。
2.6 资源饱和度和错误
Elasticsearch 节点使用线程池来管理线程如何消耗内存和CPU。
由于线程池设置是根据处理器的数量自动配置的,因此调整它们通常没有意义。
但是,建议关注 队列和拒绝,以确定节点是否无法跟上进度,如果较多,可能需要添加更多节点来处理所有并发请求。Fielddata 和过滤器缓存的使用情况是另一个需要监视的领域,因为回收可能导致查询效率低下或内存压力的迹象。
Thread pool queues and rejections
每个节点都包含了几种类型的线程池,具体希望关注哪种对象使用,取决于对 ElasticSerach 的使用。
通常需要关注的是search merge bulk的线程池,他们与对应的请求类型对应。
bulk thread pool 现在也归类到 write thread pool(包含 writes/updates/deletes请求)。每个线程池队列的大小表示节点当前处于容量状态时等待服务的请求数量,队列允许节点跟踪并最终为这些请求提供服务,而不是丢弃它们。
一旦到达线程池的最大队列大小(根据线程池的类型而不同),就会发生线程池拒绝。
需要关注的指标名称:
| 指标描述 | 指标名称 | 类型 |
|---|---|---|
| 线程池的队列数 | thread_pool.search.queue/thread_pool.merge.queue/thread_pool.write.queue/thread_pool.index.queue* | 资源饱和度 |
| 线程池中被拒绝的线程数 | thread_pool.search.rejected/thread_pool.merge.rejected/thread_pool.write.rejected/thread_pool.index.rejected* | 资源错误 |
需要关注的几个指标:
Thread pool queues: 比较大的队列不是个好主意,因为它们会耗尽资源,而且在节点宕机时还会增加丢失请求的风险。如果您看到排队和被拒绝的线程数量稳步增加,您可能想尝试减慢请求的速度(如果可能的话),增加您的节点上的处理器数量,或者增加集群中的节点数量。如下面的屏幕截图所示,查询负载峰值与搜索线程池队列大小峰值相关,因为节点试图跟上查询请求的速度。
Bulk rejections and bulk queues: 通常,如果您想要执行许多操作(创建索引或添加、更新或删除文档),您应该尝试将请求作为一个批量操作发送,而不是多个单独的请求。批量拒绝通常与试图在一个批量请求中索引太多文档有关。
Cache usage metrics
每个查询请求被发送到索引中的每个分片,然后每个分片的每个片段都会被访问。Elasticsearch按段缓存查询,以加快响应时间。
另一方面,如果您的缓存占用了太多的堆,它们可能会减慢而不是加速。
在 es 中,文档中的每个字段可以以两种形式之一存储 : 精确值或全文。
作为精确值(例如时间戳记或年份)将按照其索引编制的方式进行存储,因为您不希望1/1/16来作为“2016年1月1日”的查询。
如果一个字段被存储为全文,这意味着它基本上被分析,它被分解为记号,并且,根据分析器的类型,标点符号和停止词(如is或the)可能会被删除。分析器将字段转换为规范化格式,使其能够匹配更广泛的查询。
Elasticsearch 使用两种主要类型的缓存来更快地提供搜索请求: fielddata 和 filter。
FIELDDATA CACHE
fielddata缓存在对一个字段进行排序或聚合时使用,这个过程基本上必须对反向索引进行反求,以按文档顺序创建每个字段的每个字段值的数组。
FILTER CACHE
过滤器缓存也使用JVM堆。在2.0之前的版本中,Elasticsearch以堆的10%的最大值自动缓存过滤过的查询,并驱逐最近最少使用的数据。在2.0 之后的版本,Elasticsearch根据频率和段大小自动开始优化其过滤器缓存(缓存仅发生在文档少于10,000或索引中文档总数少于3%的段上)。
Pending tasks
| 指标描述 | 指标名称 | 类型 |
|---|---|---|
| pending 状态任务数 | pending_task_total | 资源饱和度 |
紧急(urgent)pending 状态任务数 | pending_tasks_priority_urgent | 资源饱和度 |
高优(high-priority)pending 状态任务数 | pending_tasks_priority_high | 资源饱和度 |
待处理的任务只能由主节点处理。
这些任务包括创建索引和将分片分配给节点。
挂起的任务按优先级顺序处理,紧急优先,然后高优先级。当
更改的数量比主程序处理它们的速度更快时,它们就开始累积。
需要持续关注该指标,不让其不断增加。
挂起任务的数量可以很好地指示集群操作的顺畅程度。如果您的主节点非常繁忙,而挂起的任务数量没有减少,则可能导致集群不稳定。
Unsuccessful GET requests
| 指标描述 | 指标名称 | 类型 |
|---|---|---|
| miss状态的GET请求 | indices.get.missing_total | Work: Error |
| miss状态的GET请求花费的时间 | indices.get.missing_time_in_millis | Work: Error |
GET 请求比普通的搜索请求更直接,它根据文档的 ID 检索文档。
一个不成功的 get-by-ID 请求意味着没有找到文档 ID,这种类型的请求通常不会有问题,但是当不成功的GET 请求发生时,可能需要注意下。
二、如何收集ElasticSearch的指标
ElasticSearch指标收集工具:
- 集群健康状态和各种性能API
- 有表格数据的
_catAPI - 开源的监控工具(ElasticHQ, Kopf, Marvel)
1. ElasticSearch 的 RESTFull API + JSON
默认情况下,集群对外开启了9200端口,用于整个集群的管理操作、索引的增删改查,以及整体集群、节点、索引的状态信息,通常需要关注如下几个对外API。
- Node Stats API: 节点状态API
- Cluster Stats API: 集群状态API
- Index Stats API: 索引状态API
- Cluster Health API: 集群健康状态API
- Pending Tasks API: 阻塞任务状态API
如下表列出了一些常见的指标以及对应的API接口。
| Metrics 分类 | 可用的API |
|---|---|
| 检索性能指标 | Node Stats API, Index Stats API |
| 索引性能指标 | Node Stats API, Index Stats API |
| 内存和垃圾回收GC | Node Stats API, Cluster Stats API |
| 网络指标 | Node Stats API |
| 集群健康和节点可用性 | Cluster Health API |
| 资源饱和度和错误 | Node Stats API,Index Stats API,Cluster Stats API,Pending Tasks API |
1.1 Node Stats API
Node 状态接口是一个功能强大的工具,能提供除集群运行状况和挂起任务外几乎全部的性能指标。
注意: 对于节点状态API 来讲,最重要的就是indices和
- 集群名称以及节点状态
- 节点元数据(名称,角色,)
indices: 索引相关数据-
- 索引文档统计和存储统计(docs,store)
-
- 索引内部操作统计(
indexing,get,search,merges,refresh,flush,warmer,query_cache,fielddata,completion,segments,translog,request_cache,recovery)
- 索引内部操作统计(
os: 节点系统层指标-
- cpu使用率
-
- 负载状态(1,5,15)
-
- 内存使用率(es统计的内存会将buffer/cache算进去)
-
- Cgroup信息(使用systemd管理的cpuacct,cpu,memory)
process: 进程状态信息-
- FD 限制和使用
-
- 进程对cpu和mem的使用
jvm: 节点JVM指标-
- 内存整体指标(堆内存,非堆内存,JVM区域内存)
-
- gc情况统计(年轻代和老年代垃圾收集)
-
- 线程数
-
- buffer池状态(直接访问和映射状态)
-
- 加载的类情况
fs: 文件系统-
- 文件系统元数据和使用率
-
- IO stats
transport: 传输状态-
- 进出包数据统计以及流量
thread_pool: 各种操作的线程池状态-
- bulk
-
- fetch_shard_started
-
- fetch_shard_store
-
- flush
-
- force_merge
-
- generic
-
- get
-
- index
-
- listener
-
- management
-
- refresh
-
- search
-
- snapshot
-
- warmer
注意: 在 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 "
1.2 Cluster Stats API
集群指标接口提供了集群范围内的信息,因此,它基本上是集群中每个节点的所有统计数据相加。虽然提供的数据不够详细,但是对于快速了解集群状态是非常有用的。
集群级别比较重要的几个指标
status: 集群状态(green|red|yellow)nodes: 集群的整体节点统计信息(/_nodes/stats的求和,指标和指标项会比较精简:fs,jvm,os,process )indices: 集群的索引整体状况
# 查看集群整体状况
$ 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
}
1.3 Index Stats API
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{...}
}
}
}
1.4 Cluster Health HTTP API
在所有对外接口中,提供集群级别的运行态数据外,还提供了集群健康状态的接口。
该接口可以公开整个集群运行状况的关键信息。
$ 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
}
1.5 Pending Tasks API
待处理任务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"
} ]
}
2. cat API
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 接口是一个可以快速获取集群,节点,索引以及分片的状态数据,并且能够以可读的方式展示出来。
3. 已实现的开源工具
虽然整个ES对外的接口已经能够提供很好的接口来描述瞬时的指标,但是通常情况下,我们有很多节点需要进行持续的监控,而接口的JSON格式又不便于我们进行解析和分析,很难快速识别到问题节点并及时发现问题趋势。
为了更加有效的监控 ElasticSearch,我们通常需要一些工具来定期采集API的指标数据,然后聚合指标结果来反应当前集群的整体状态。而在开源社区中,也产生了很多这种类似的工具系统。
3.1 ElasticHQ
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 的监控管理了,需要注意的是,此时需要添加集群地址.
3.2 其他监控插件
开源领域也有其他插件,比如 kopf 和 Cerebro 前者比较老,且现在不再更新了,而后者是一个比较全面的监控工具,且支持LDAP工具登录。
# 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
三、如何解决 Elasticsearch 的性能问题和扩展性问题
通常情况下,一些优秀的开源软件通常都意味着用户可以快速的启动并运行起来,但这并不能保证你的整个集群就是可以一直的稳定运行下去,这和具体的业务场景有一定关系,当然,也可以长时间运行的软件有关,就好比行驶中的汽车或不断变老的我们,需要在不断的使用过程中,进行保养和维修,才能保证能够相对稳定且高效的运行下去。
问题1: 集群状态变为red或者yello 应该怎么做
注意: 如果丢失一个或多个主分片(及其副本),则集群状态报告为红色,如果丢失一个或多个副本分片,则报告为黄色。
通常情况下,当某个节点由于某种原因(硬件故障,垃圾回收时间过长)离开或退出集群时,就会发生这种情况。
一旦节点恢复,其分片在转换回活动状态之前将保持初始化状态。
初始化分片的数量通常在节点重新加入集群时达到峰值,然后在分片转换到活动状态时下降。
在初始化期间,集群状态可能会从绿色变为黄色或者红色,直到恢复节点上的分片为活动状态。
一般而言,当设置了集群的自动迁移分片和自动均衡,如果集群出现了状态为 red 或者 yello 时,不需要管理员进行过多的关注,节点在及时加入后能够尽快的实现相对的均衡。
但是,如果集群在 red 或者 yellow 状态停留了较长时间,我们就需要及时去查看集群识别的正确Elasticsearch 节点数量。
如果活动节点的数量低于预期,则意味着至少有一个节点丢失了连接,无法重新加入集群,此时应该在节点的日志中查看如下相关的行:
[TIMESTAMP] ... Cluster health status changed from [GREEN] to [RED]
通常情况下,节点故障的原因会比较复杂,从硬件故障到管理程序故障到内存不足等等错误都可能导致整个节点故障。
总结
因此,我们可以借助于上面的ElasticHQ 和 Cobrao 监控工具,来查看节点故障时,相关的性能指标如何。例如当前搜索 search 或索引请求 indexing requests 速率的突然峰值。
如果是由于短暂故障,我们可以立马对相关操作进行隔离,并进行快速恢复集群节点;如果是永久性故障,并且无法恢复节点,则可以通过快速添加节点,并让 ElasticSearch 负责从可用的副本分片中自动恢复;副本分片可以升级为主分片,并在新添加的节点上进行重新分发。
但是,如果您丢失了一个碎片的主副本和副本副本,那么您可以使用 Elasticsearch 的快照和恢复模块来尽可能多地恢复丢失的数据。
如果您还不熟悉这个模块,那么可以使用它在远程存储库中存储索引的快照,以便进行备份。
问题2: 在运行的数据节点磁盘满了
如果所有数据节点的磁盘空间都不足,则需要向集群添加更多数据节点。
还需要确保索引有足够的主分片,以便能够在所有这些节点之间平衡它们的数据。
但是,如果只有某些节点耗尽了磁盘空间,这通常表明您初始化的索引分片太少。
如果一个索引由几个非常大的分片组成,那么 Elasticsearch 很难以均衡的方式将这些分片分布到各个节点上。
在给节点分配分片时,Elasticsearch 会考虑可用的磁盘空间。默认情况下,它不会为使用超过85%磁盘的节点分配shard,一般而言,我们也需要对磁盘进行配置报警,以提前发现整个磁盘的可用性。
因此,在这个问题上,通常是一个集群或业务规划,以及告警运维的问题。
如果数据盘容量,实在已经到了瓶颈,一种方法是删除过期的数据并将其存储在集群中。
这对所有用户来说可能不是一个可行的选项,但是,如果您存储基于时间的数据,您可以在集群外存储旧索引数据的快照以进行备份,并更新索引设置以关闭对这些索引的复制。
如果集群的数据不能进行删除,第二种方法就是选择垂直或者水平扩容。
垂直扩容可直接扩展磁盘空间即可;水平扩容需要进行节点以及分片的动态迁移。
为了更好地适应未来的增长,您最好重新索引数据并在新创建的索引中指定更多的主分片(确保您有足够的节点来均匀地分布这些分片)。
另一种水平伸缩的方法是通过 创建新索引来滚动索引,并使用别名将两个索引连接到一个名称空间中。
尽管在单个分片上可以存储多少数据在技术上没有限制,但是Elasticsearch建议每个分片上有50gb的软上限,您可以使用它作为一个通用准则,指示何时开始新的索引。
总结
其实对于 ElasticSearch 的容量问题,可以归结为规划问题,一般而言,在构建初始集群时,我们需要选择相对高配且适合的硬件,并且在构建集群初期需要考虑到未来的数据量规模;另外就是索引的规划问题,一般而言,当业务方索引数据时,建议使用一定的规范进行设计索引结构,并且在创建索引前期做好容量和分片的规划。
# 索引容量 (30G 是考虑到单分片数据量的迁移成本和索引成本)
索引未来容量=分片数*30G
# 集群容量 (N 代表集群的索引数量)
集群容量=(索引未来容量*副本数量) * N
问题3: 搜索花费了较长时间
根据搜索的数据类型和每个查询的结构,搜索性能有很大的不同。
因此,一般的做法是,需要将 ElasticSearch 中的检索上下文拿出来进行分析。
根据数据组织方式的不同,在找到有助于提高搜索性能的方法之前,您可能需要试验几种不同的方法,通常用的几种方式:
- 自定义路由(
custom routing) - 强制合并(
force merging)
通常,当节点接收到搜索请求时,它需要将该请求通信到索引中每个分片的副本(主或副本)。
自定义路由 允许我们将数据存储在同一分片上,因此只需要搜索单个分片即可满足查询。
例如,您可以在您的索引 blog_index 中为 blogger 类型指定映射中的 _routing 值,从而将所有 blogger1 数据存储在同一个碎片上。
首先,确保 _routing 被设置,这样当您索引 blogger 类型的信息时,您就不会忘记指定一个自定义路由值。
curl -XPUT "localhost:9200/blog_index" -d '
{
"mappings": {
"blogger": {
"_routing": {
"required": true
}
}
}
}'
当您准备索引属于blogger1的文档时,请指定路由值:
curl -XPUT "localhost:9200/blog_index/blogger/1?routing=blogger1" -d '
{
"comment": "blogger1 made this cool comment"
}'
现在,为了搜索blogger1的内容,您需要记住在查询中像这样指定路由值:
curl -XGET "localhost:9200/blog_index/_search?routing=blogger1" -d '
{
"query": {
"match": {
"comment": {
"query": "cool comment"
}
}
}
}'
在 Elasticsearch 中,每个搜索请求必须检查它命中的每个分片的每个段。
因此,一旦您减少了需要搜索的切分的数量,您还可以通过在一个或多个索引上触发强制合并API来减少每个分片上段的数量。
只要考虑到触发大量合并的计算成本,就值得对该特性进行试验。
但是,当涉及到具有大量分段的分片时,强制合并过程的计算成本会高得多。
例如,强行合10,000个段到5,000个段的索引并不需要花费很多时间,但是将10,000个段一直合并到一个段可能需要几个小时。必须发生的合并越多,从满足搜索请求中带走的资源就越多,这可能一开始就违背了强制合并的目的。
在任何情况下,在非高峰时间(比如晚上)安排强制合并通常是一个好主意,因为此时您不希望有很多搜索或索引请求。
注意: 一定需要注意业务的高低峰周期,在低峰期间进行强制合并。
问题4: 如何提高索引性能( Indexing 性能)
Elasticsearch 预先配置了许多设置,以确保您保留足够的资源来搜索和索引数据。
但是,如果您使用的 Elasticsearch 严重偏向于写,您可能会发现调整某些设置来提高索引性能是有意义的,即使这意味着失去一些搜索性能或数据复制。
下面,我们将探索一些方法来优化索引(而不是搜索)数据的用例。
Shard allocation
作为一种高级策略,如果您正在创建一个计划频繁更新的索引,请确保指定足够的主分片,以便能够将索引负载均匀地分布在所有节点上。
一般建议为集群中的每个节点分配一个主分片,可能为每个节点分配两个或更多主分片,但前提是这些节点上有大量的CPU和磁盘带宽。
但也要注意,分片过多会增加开销,并可能对搜索产生负面影响(搜索需要访问索引的每个分片)。
另一方面,如果分配的主分片比节点的数量少,就可能创建热点,因为包含这些分片的节点需要处理的索引请求比不包含任何索引分片的节点多。
Disable merge throttling
合并限制是 ES 在他检测到葛冰落后于索引时自动倾向于限制索引请求的特性。
如果想要优化索引性能而不是搜索的话,则可以设置禁用集群的合并限制(indices.store.throttle.type to “none”)
并且可以设置为persistent 或 transient,是合并限制永久或临时生效。
Increase the size of the indexing buffer
indices.memory.index_buffer_size 参数将确定文档在被写入到磁盘的段(segment)之前,缓冲区可以达到到的最大空间。
默认限制为总堆内存的10%,以便服务搜索请求保留更多的堆。
Index first, replicate later
初始化索引时,可以先在索引设置中指定零个副本碎片,并在建立索引后添加副本。
这将提高索引的性能,但是如果拥有唯一数据副本的节点在您有机会进行复制之前崩溃,则可能会有些冒险。
Refresh less frequently
增加索引设置 API 中的刷新间隔。
默认情况下,索引刷新过程每秒进行一次,但是在繁重的索引期间,降低刷新频率可以帮助减轻某些工作量。
Tweak your translog settings
Elasticsearch 将在每次请求后将转日志数据刷新到磁盘,从而降低了发生硬件故障时数据丢失的风险。
如果要优先考虑 索引性能 而不是潜在的数据丢失,可以在索引设置中将 index.translog.durability 更改为 async。
有了此功能,索引将仅在每个 sync_interval 时才将写入操作提交到磁盘,而不是在每个请求之后才将其提交到磁盘,从而腾出更多的资源来处理索引请求。
问题5: 如何处理大量 _bulk 线程池拒绝
线程池拒绝通常是向节点发送过多请求、速度过快的信号。
如果这是一个临时的情况(例如,您必须在本周索引异常大量的数据,并且预期它很快就会恢复正常),那么您可以尝试降低请求的速度。
但是,如果希望集群能够维持当前的请求速率,则可能需要通过添加更多数据节点向外扩展集群。
为了利用增加的节点数量带来的处理能力,还应该确保索引包含足够的分片,以便能够将负载均匀地分布在所有节点上。
注意: 如果节点已经足够,且自愿利用率不高,可以尝试对相关操作的线程池就行参数优化调整。
thread_pool:
index:
size: 8
queue_size: 2000
search:
size: 100
queue_size: 2000
min_queue_size: 100
max_queue_size: 3000
get:
size: 30
queue_size: 2000
bulk:
size: 8
queue_size: 2000
主动优化
通常,我们会发现,集群当前存在各种风险点以及问题,此时我们需要借助各种监控指标,去主动协调上层使用方进行性能优化。
因为正常情况下,即使是相同的硬件,相同的配置,但不那么相同的业务场景,将会表现出来不同的性能。此时,我们需要从上到下,分析业务场景以及业务模型,以此来优化整个集群的配置,甚至根据不同的业务场景去选择合适的硬件设备(当然如果是自建IDC,硬件设备通常可选择性较低)。
参考文章:
-
[监控ElasticSearch的性能指标]:www.datadoghq.com/blog/monito…
-
[如何收集ElasticSearch的指标]:www.datadoghq.com/blog/collec…
-
[使用datadog监控ElasticSearch]:www.datadoghq.com/blog/monito…
-
[ElasticSearch性能和扩展问题]:www.datadoghq.com/blog/elasti…