其他分享
首页 > 其他分享> > Elasticsearch故障诊断常用方法

Elasticsearch故障诊断常用方法

作者:互联网

文章目录

背景

在大部分情况下,我们遇到的问题都是由一些简单的原因导致的。但由于分布式系统的复杂性,有时候故障现象只出现了一次,并且难以复现,这就需要采取一些措施来缩小可疑的问题范围,虽然不能立刻解决问题,但是可以向前迈进一步。综合来说,当遇到故障时,分析问题有两种思路:

  1. 系统化调试思想:从故障的具体现象和具体信息出发,逻辑性地向上推理可能的因素,并逐步排除,渐渐缩小问题范围,直到定位问题。
  2. 假设猜测思想:根据故障信息和经验直接猜测故障原因,这种凭空设想在面对简单问题时能比较快速定位,但在面对错综复杂、多种因素混合的问题时更多地需要理性推导。尽管如此,“假设故障原因”仍然很重要,在推导问题过程中,以及不可重现的问题时,需要联想到与其相关的都有哪些因素。

下面介绍一些通用的分析问题方法和工具。

使用Profile API定位慢查询

有时在发起一个查询时,查询会被延迟执行,或者响应时间很慢,查询缓慢可能会有多种原因:分片问题,或者计算查询中的某些元素。ES提供Profile API供用户检查查询执行时间和其他详细信息。Profile API返回所有分片的详细信息,其结果是基于每个分片计算的,每个分片信息主要包含三大部分:1. Query;2. Rewrite Time;3. Collectors。

Profile API让我们清楚地看到查询时间。通过向我们提供有关子查询的详细信息,我们可以清楚地知道在哪个环节查询慢,这是非常有用的。另外,在API返回的结果中,关于Lucene的详细信息也让我们深入了解到ES是如何执行查询的。

1. Query

Query部分由构成Query的元素及它们的时间信息组成。Profile API结果中Query部分的基本组成如下:

2. Rewrite Time

由于多个关键字会分解以创建个别查询,所以在这个过程中肯定会花费一些时间。将查询重写一个或多个组合查询的时间被称为“重写时间”(单位为纳秒)。

3. Collectors

在Lucene中,收集器负责收集原始结果,并对它们进行组合、过滤、排序等处理。

使用Explain API分析未分配的分片(Unassigned Shards)

一个ES索引由多个分片组成,由于某些原因,某些分片可能会处于未分配状态(Unassigned),导致集群健康处于Yellow或Red状态,这是一种比较常见的错误信息,导致分片处于未分配的原因可能是节点离线、分片数量设置错误等原因,使用Explain API可以很容易分析当前的分片分配情况。这个API主要为了解决下面两个问题:

  1. 对于未分配的分片,给出为什么没有分配的具体原因。
  2. 对于已分配的分片,给出为什么将分片分配给特定节点的理由。

接下来我们通过几个例子来演示如何通过Explain API来定位分片分配问题。

诊断未分配的主分片

我们创建一个名为test_idx的索引,该索引只有一个主分片,没有副分片。集群有两个节点,名为A和B。但是在创建索引时,我们设置分配过滤器,使其不能被分配到节点A和B上。

虽然索引能创建成功,但是因为过滤规则的限制,索引分片无法分配到集群仅有的A和B两个节点。此时集群处于Red状态,我们通过Explain API来获取第一个未分配分片的原因解释。

GET /_cluster/allocation/explain

Explain API给出发该分片未分配的原因,由于索引刚创建(unassigned_info),它处于未分配状态(current_state)。由于没有节点允许分配给该分片(allocate_explain),所以无法分配分片(can_allocation)。深入每个节点的决策信息(node_allocation_decisions),可以看到由于索引的过滤器设置,分配操作被decider拦截。decider给出了具体的decider名称,接下来是决策结果及具体的原因(explanation)。

诊断未分配的副分片

我们把刚才的索引test_idx副本数调整为1,因为A节点已经存储了主分片,所以副分片将被分配到节点B,以达到集群均衡的目的。现在在副分片上运行Explain API:

GET /_cluster/allocation/explain
{
	"index": "test_idx",
	"shard": 0,
	"primary": false
}

从返回信息显示分片已经被分配到节点B,并且为started状态。接下来,我们再次设置索引的分配过滤器,但是这次阻止分配分片的节点B。

PUT /test_idx/_settings
{
	"index.routing.allocation.exclude._name": "B"
}

现在重启节点B,在副分片上重新运行Explain API,结果显示副分片当前处于未分配状态(can_allocate),因为分配过滤设置了禁止把分片分配到节点B上(explanation)。因为节点A上已经指派了主分片,所以不允许再把该分片的其他副本分配到A节点(explanation)。ES会避免将主副分片分配到同一个节点,主要是为了防止当节点失效时所有副本都不可用,以及可能的数据丢失。

诊断已分配的分片

如果分片可以正常分配,为什么还要关注explain信息呢?一个常见的原因是想将分片手工迁移到某个节点,但是出于某些原因分片没有迁移,这时Explain API帮助我们展示其中原因。

我们重新设置分配过滤器,使主分片从当前节点A迁移走。

PUT /test_idx/_settings
{
	"index.routing.allocation.exclude._name": "A"
}

我们期望的结果是主分片从节点A移动到另一个节点,然而事与愿违,在主分片上运行Explain API,看看具体原因。

GET /_cluster/allocation/explain
{
	"index": "test_idx",
	"shard": 0,
	"primary": true
}

从返回结果可以看出,主分片仍然被分配给节点A(current_node),集群知道这个分片不应该保留在当前节点(can_remain_on_current_node),原因是主分片被当前节点的decider拦截(can_remain_decisions)。尽管分片不允许保留在当前节点,但它也不能移动到其他节点(can_move_to_other_node),因为被节点B的decider拦截,主副分片不能分配到同一节点。

节点CPU使用率高

节点占用CPU很高,我们想知道CPU在运行什么任务,一般通过线程堆栈来查看,有两种方式可以查看哪些线程CPU占用率比较高。

推荐使用hot_threads API获取繁忙线程的堆栈。top+jstack的方式由于两个命令的执行在时间上(对进程采样的时间点)存在误差,所以定位出的堆栈存在一定概率的偏差。为了降低这种误差,可以在脚本中让两个命令同时执行。

节点内存使用率高

节点内存使用率高,我们想要知道内存是被哪些数据结构占据的,通用方式是用jmap导一个堆出来,加载到MAT中进行分析,可以精确定位数据结构占用内存的大小,以及被哪些对象引用。这是定位此类问题最直接的方式。

jmap导出的堆可能非常大,操作比较花时间,我们也可以简单看一下ES中几个占用内存比较大的数据结构(有些数据结构无法看到其当前的实际大小,只能通过设置的上限粗略评估):

bulk队列

可以通过_cat API查看bulk队列中当前的使用量,任务总数乘以bulk请求的大小就是占用内存的大小。如果队列设置很大,则在写入压力大的时候就会导致比较高的内存占用。默认值为200,一般情况下都够用了。

Netty缓冲

在一些特别的情况下,Netty的内存池也可能会占用比较高的内存。Netty收到一个客户端请求时,为连接分配内存池,客户端发送的数据存储到Netty的内存池中,直到ES层处理完上层逻辑,回复客户端时,才释放该内存。当ES收到客户端请求时,如果在处理完毕之前客户端关闭连接,则ES依然会把这个请求处理完,只是最后才出现回复客户端失败。这个过程可能会导致内存累积,例如,执行bulk请求时,客户端发送完毕,不等ES返回响应就关闭连接,然后立即发起下一个请求,结果这些请求实际上都在等待处理,就可能占用非常多的内存。所以客户端的请求超时时间应该尽量设置得长一些,建议设置为分钟级。

indexing buffer

索引写入缓冲用于存储索引好的文档数据,当缓冲满时,生成一个新的Lucene分段。在一个节点上,该节点的全部分片共享indexing buffer。该缓冲默认大小为堆内存的10%,加大该缓冲需要考虑到对GC的压力。

超大数据集的聚合

协调节点对检索结果进行汇总和聚合,当聚合涉及的数据量很大时,协调节点需要拉取非常多的内容,大范围的聚合是导致节点OOM的常见原因之一。

分段内存

一个Lucene分段就是一个完整的倒排索引,倒排索引由单词词典和倒排列表组成。在Lucene中,单词词典中的FST结构会被加载到内存。因此每个分段都会占用一定的内存空间。可以通过下面的API来查看某个节点上的所有分段占用的内存总量:

curl -X GET "localhost:9200/_cat/nodes?v&h=segments.memory"

也可以单独查看每个分段的内存占用量:

curl -X GET "localhost:9200/_cat/segments?v&h=index,shard,segment,size.memory"

size.memory字段代表分段的内存占用量。该API可以根据索引名称等进行过滤。

Fielddata cache

在text类型字段上进行聚合和排序时会用到Fielddata,默认是关闭的,如果开启了Fielddata,则其大小默认没有上限,可以通过indices.fielddata.cache.size设置一个百分比来控制其使用的堆内存上限。可以通过下面的命令查看节点上的Fielddata使用情况:

curl -X GET "localhost:9200/_cat/nodes?v&h=fielddata.memory_size"
curl -X GET "localhost:9200/_nodes/stats/indices/fielddata?fields=field1,field2&pretty"

也可以查看索引级的Fielddata使用情况:

curl -X GET "localhost:9200/_stats/fielddata/?fields=field1,field2&pretty"

Shard request cache

分片级别的请求缓存。每个分片独立地缓存查询结果。该缓冲默认是开启的,默认为堆大小的1%,可以通过indices.requests.cache.size选项来动态调整,也可以在某个请求中指定不使用缓存。其使用LRU淘汰策略。默认情况下,只会缓存size=0(结果为空)的请求,它并不缓存命中结果(hits),但是会缓存hits.total、aggregations和suggestions。可以使用下面的API来获取缓存使用量:

curl -X GET "localhost:9200/_stats/request_cache?pretty"
curl -X GET "localhost:9200/_nodes/stats/indices/request_cache?pretty"
curl -X GET "localhost:9200/_cat/nodes?v&h=request_cache.memory_size"

Node Query Cache

节点查询缓存由节点上的所有分片共享,也是一个LRU缓存,用于缓存查询结果,只缓存在过滤器上下文中使用的查询。该缓存默认开启,大小为堆大小的10%。可以通过indices.queries.cache.size选项来配置大小,同时可以通过index.queries.cache.enabled选项在索引级启用或禁用该缓存。该缓存的使用量可以通过下面的命令来获取:

curl -X GET "localhost:9200/_cat/nodes?v&h=query_cache.memory_size"

Slow Logs

当遇到查询慢的时候,想知道对方的查询语句是什么,在日志中记录所有查询语句可能会导致日志量太大,因此ES允许只将执行慢的请求记录日志,“慢”的程度可以自己定义。目前,ES记录了两种类型的慢日志。

慢搜索日志

用来记录哪些查询比较慢,“慢”的程度由程序自己定义,每个节点可以设置不同的阈值。ES的搜索由两个阶段组成:查询和取回。慢搜索日志给出了每个阶段所花费的时间,以及整个查询内容本身。

慢搜索日志可以为查询和取回阶段单独设置以时间为单位的阈值,如果设置为0,则输出全部搜索日志。在定义好每个级别的时间后,通过level决定输出哪个级别的日志。

慢索引日志

用来记录哪些索引操作比较慢,其记录了哪些索引操作耗时比较长,阈值同样可以设置。与慢搜索不同,索引内容可能非常大,因此默认记录源文档内容的前1000行。可以设置为捕获整个文档 ,或者不记录原始文档本身。

与慢搜索日志的配置类似,同样可以定义4种不同的慢日志级别,为每个级别设置不同的时间,然后通过level来决定输出哪个级别的日志。如果阈值设置为0,则将输出全部索引日志。source参数设置了日志中捕获源文档的行数。

分析工具

本节介绍一些基础的分析工具,它们大部分是系统自带且常用的,无论分析性能问题还是排查故障,都离不开它们。

I/O信息

iostat

iostat是用来分析I/O状态的常用工具,其输出结果是以/proc/diskstats为基础计算的。我们经常关注的几个指标:

当I/O产生性能问题时,iostat可能不足以定位故障,可以使用blktrace来分析I/O请求的各个环节。该工具的原理和使用方式可以参考文章

进程级I/O状态

iostat提供磁盘级的I/O状态,无法关联到进程,如果想查看哪些进程的I/O最高,则可以使用pidstat或iotop两个工具,它们可以动态给出每个进程的读写速度。如果想看到特定磁盘上特定进程的I/O情况,可以通过systemtap来监控。

内存

top、free、vmstat等工具可以帮助我们看到基础的内存信息,包括物理内存总量、剩余空间、cache量等。当系统物理内存不足时,我们可以通过sar -B来观察内存分页的统计信息来查看系统回收内存的效率如何,输出结果中几个字段的含义如下:

另一种情况,在开启了交换分区的系统上,可以通过sar -W查看页面交换情况。发生页面交换会导致服务器性能严重下降,我们应该在生产环境关闭交换分区。

CPU信息

基本信息

vmstat输出用户级(us)和内核级(sy)的CPU占用百分比,以及采样周期内的上下文切换次数,block in、block out次数等信息。

mpstat除了获取用户级(usr)和内核级(sys)的CPU占用百分比,还可以输出采样周期内的软中断(soft)、硬中断(irq)占用时间的百分比。

诊断导致CPU高的系统调用

正常情况下应用程序占用用户态CPU时间,如果进程占用sys比较高,则表示程序执行在内核态的操作非常耗费CPU,我们可以使用一些工具来检查应用程序产生系统调用的统计信息。

网络连接和流量

sar

sar是用来查看网卡流量的最常用方式,它以字节为单位输出网络传入和传出流量。

netstat

netstat -anp可以列出连接的详细信息,并且可以将连接、监听的端口对应到进程。其中Recv-Q和Send-Q代表该连接在内核中等待发送和接收的数据长度,单位为字节。例如,发送数据时,send调用将数据从用户态复制到内核态后返回,TCP协议栈负责将数据发送出去,Send-Q代表了尚未发送出去(未被对端ACK)的数据量。在未发送完之前,这些数据停留在内核缓冲,原因可能是网络延时,或者对端的滑动窗口限制(例如,对端没有read)。Recv-Q则代表协议栈已完成接收,但尚未被应用层的read调用从内核态复制到用户态的数据长度。

netstat -s提供了各个协议下的统计信息,例如,活跃连接数、重传次数、reset信息等非常有用。

ss

netstat是观察网络连接的常用工具,但是无法处理海量网络连接的情况下可以用ss代替。ss与netstat功能类似,但适合处理海量连接。sudo ss -anp返回信息与netstat类似。

ifconfig

除了用来查看IP地址,还需要留意其中的RX/TX errors、dropped、overruns信息,大部分情况下它们没什么问题,但是当网卡流量跑满的时候可能会出现意外。

sysdig

sysdig可以分析系统级和进程级许多方面的状况,例如,系统调用、网络统计、文件I/O等,现在我们用它来捕获某个进程到某个IP的网络流量。

通过proc.pid指定要捕获的进程,fd.cip过滤特定IP地址,evt.buffer contains指定要过滤的文本内容,此处我们以捕获ES的ping请求为例:

sysdig -s 4096 -A -c echo_fds fd.type=socket proc.pid=18914 and fd.cip=10.10.13.13 and evt.buffer contains "internal:discovery/zen/fd/ping"

sysdig还可以将捕获的数据录制为文本或二进制格式。

标签:常用,分配,查询,故障诊断,API,Elasticsearch,内存,分片,节点
来源: https://blog.csdn.net/qq_27639777/article/details/117062725