Elasticsearch故障诊断常用方法
作者:互联网
文章目录
背景
在大部分情况下,我们遇到的问题都是由一些简单的原因导致的。但由于分布式系统的复杂性,有时候故障现象只出现了一次,并且难以复现,这就需要采取一些措施来缩小可疑的问题范围,虽然不能立刻解决问题,但是可以向前迈进一步。综合来说,当遇到故障时,分析问题有两种思路:
- 系统化调试思想:从故障的具体现象和具体信息出发,逻辑性地向上推理可能的因素,并逐步排除,渐渐缩小问题范围,直到定位问题。
- 假设猜测思想:根据故障信息和经验直接猜测故障原因,这种凭空设想在面对简单问题时能比较快速定位,但在面对错综复杂、多种因素混合的问题时更多地需要理性推导。尽管如此,“假设故障原因”仍然很重要,在推导问题过程中,以及不可重现的问题时,需要联想到与其相关的都有哪些因素。
下面介绍一些通用的分析问题方法和工具。
使用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部分的基本组成如下:
- query_type,触发的查询类型。
- lucene,启动查询的Lucene方法。
- time,Lucene执行此查询所用的时间。单位是毫秒。
- breaddown,有关查询的更详细的细节,主要与Lucene参数有关。
- children,具有多个关键字的查询被拆分成相应术语的布尔查询,每个查询都作为单独的查询来执行。每个子查询的详细信息将填充到Profile API输出的子段中。从查询中的子段中,我们可以得到关于哪个搜索项在总体搜索中造成最大延迟的信息。
2. Rewrite Time
由于多个关键字会分解以创建个别查询,所以在这个过程中肯定会花费一些时间。将查询重写一个或多个组合查询的时间被称为“重写时间”(单位为纳秒)。
3. Collectors
在Lucene中,收集器负责收集原始结果,并对它们进行组合、过滤、排序等处理。
使用Explain API分析未分配的分片(Unassigned Shards)
一个ES索引由多个分片组成,由于某些原因,某些分片可能会处于未分配状态(Unassigned),导致集群健康处于Yellow或Red状态,这是一种比较常见的错误信息,导致分片处于未分配的原因可能是节点离线、分片数量设置错误等原因,使用Explain API可以很容易分析当前的分片分配情况。这个API主要为了解决下面两个问题:
- 对于未分配的分片,给出为什么没有分配的具体原因。
- 对于已分配的分片,给出为什么将分片分配给特定节点的理由。
接下来我们通过几个例子来演示如何通过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,top获取线程级CPU占用率,再根据线程ID,配合jstack定位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
为基础计算的。我们经常关注的几个指标:
- iops,由r/s(每秒读次数)和w/s(每秒写次数)组成。
- await,平均I/O等待时间,包括硬件处理I/O的时间和在队列中的等待时间。
- %util,设备的繁忙比,是设备执行的I/O时间与所经过的时间百分比。当值接近100%时设备产生饱和。在设备具有并行处理能力的情况下,util达到100%不代表设备没有余力处理更多I/O请求。
当I/O产生性能问题时,iostat可能不足以定位故障,可以使用blktrace来分析I/O请求的各个环节。该工具的原理和使用方式可以参考文章。
进程级I/O状态
iostat提供磁盘级的I/O状态,无法关联到进程,如果想查看哪些进程的I/O最高,则可以使用pidstat或iotop两个工具,它们可以动态给出每个进程的读写速度。如果想看到特定磁盘上特定进程的I/O情况,可以通过systemtap来监控。
内存
top、free、vmstat等工具可以帮助我们看到基础的内存信息,包括物理内存总量、剩余空间、cache量等。当系统物理内存不足时,我们可以通过sar -B来观察内存分页的统计信息来查看系统回收内存的效率如何,输出结果中几个字段的含义如下:
- pgfree/s:每秒被放入空闲列表中的页数,如果其他进程需要内存,则这些页可以被分页(paged out)。
- pgscank/s:每秒被kswapd守护进程扫描的页数。
- pgsteal/s:为了满足内存需要,系统每秒从缓存(pagecache和swapcache)回收的页面数。
- %vmeff:代表页面回收效率:计算方式为
pgsteal/(pgscand+pgscank)
。过低表明虚拟内存存在问题,如果在采样周期内没有发生页面扫描,则该值为0或接近100。
另一种情况,在开启了交换分区的系统上,可以通过sar -W查看页面交换情况。发生页面交换会导致服务器性能严重下降,我们应该在生产环境关闭交换分区。
CPU信息
基本信息
vmstat输出用户级(us)和内核级(sy)的CPU占用百分比,以及采样周期内的上下文切换次数,block in、block out次数等信息。
mpstat除了获取用户级(usr)和内核级(sys)的CPU占用百分比,还可以输出采样周期内的软中断(soft)、硬中断(irq)占用时间的百分比。
诊断导致CPU高的系统调用
正常情况下应用程序占用用户态CPU时间,如果进程占用sys比较高,则表示程序执行在内核态的操作非常耗费CPU,我们可以使用一些工具来检查应用程序产生系统调用的统计信息。
-
strace的-c参数可以统计系统调用次数和执行时间,该命令可以指定跟踪哪些类型的系统调用,例如,文件级、进程相关、网络相关等,也可以跟踪特定的系统调用,例如,open、close。命令执行一段时间后需要手工按"Ctrl+C"组合键退出。
-
perf是Linux用户主要的性能分析工具,它可以做的事情很多,现在我们用它分析CPU。perf top用来实时显示系统最耗时的内核函数及进程,动态输出TopN个结果。我们还可以使用perf record来记录函数级别的统计信息。
网络连接和流量
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