问题排查帮助手册

通用

一、保留现场

dump线程堆栈和内存映射

二、恢复服务

1、如果是发布引起的就回滚(80%的情况)

2、如果运行很长时间了,可能是内存泄漏,重启程序

3、如果是少量机器问题,隔离这几台机器的流量排查

4、如果是某个用户流量突增导致整体服务不稳定,看情况开启限流

5、如果是下游依赖的服务挂了导致雪崩,走降级预案

三、定位问题

1、结合线程堆栈和内存映射排查

2、结合监控指标排查

四、事件复盘

1、总结问题,避免下次再出现

2、补充监控、优化应急措施

cpu

cpu和load的区别

 cpu反应的是采样时间内cpu的使用情况,如果cpu高说明当前cpu处于忙碌状态

 load表示当前系统正在使用cpu、等待使用cpu、等待io的进程数量,top中3个时间表示1分钟、5分钟、15分钟的平均值

 他们的关系是如果cpu较高但是load不高,小于核数量,说明系统处于忙碌状态但是在承受范围内,并没有超过负荷。如果load大于核数量说明有进程在等待使用cpu,系统处于超过负荷状态,需要排查问题,不然可能不久就会崩溃

 load是判断系统能力指标的依据

load的解释

 进程有5种状态:运行态(running)、可中断睡眠(interruptible)、不可中断睡眠(uninterruptible)、就绪态(unnable)、僵死态(zombie)

 处于运行态和不可中断状态的进程会被加入到负载等待进程中,表现为负载的数值

 运行态(running):正在使用cpu、等待使用cpu

 不可中断睡眠(uninterruptible):等待io完成

 如果系统中等待io完成的进程过多,就会导致负载队列过大,但此时cpu可能被分配执行别的任务或者空闲

load的监控策略

 0.7/每核:需要注意并排查原因 。 如果平均负载保持在> 0.70以上,那么应该在情况变得更糟之前进行调查。

 1.0/每核:不紧急,需要处理。如果平均负载保持在1.00以上,需要查找问题原因并立即解决。否则,你的服务器可能在任何时候出现性能问题。

 5.0/每核:紧急状态,立即处理。如果平均负载高于5.00,那么你的系统马上就要崩溃了,很有可能系统挂机或者hang死。因此需要立即处理这种情况,千万不要让你的系统负载达到5!

高load的5种可能

1、死循环或者不合理的大量循环操作,如果不是循环操作,也可能有大量的序列化反序列化操作,除此之外按照现代cpu的处理速度来说处理一大段代码也就一会会儿的事,基本对能力无消耗

2、频繁gc,比如YoungGC、FullGC、MetaspaceGC

3、大量线程频繁切换,这个时候cpu使用率可能不高

3、高磁盘io高网络io

4、如果都不是的话,可能是系统资源吃紧或故障,检查磁盘使用、检查内存使用和外挂io设备状态

一、高cpu高load

说明系统处于超负荷状态,有2种可能:大量循环、大量序列化或者频繁gc

top先查看用户us与空闲us(id)的cpu占比,目的是确认load高是否是cpu引起的

1、频繁gc

 1)在top中查看每个cpu的us,如果只有一个达到90%,其他都很低,可以重点考虑是不是频繁FullGC引起的(多核cpu的服务器,除了GC线程外,在Stop The World的时候都是会挂起的,直到Stop The World结束),需要排查FullGC(YoungGC、MetaspaceGC也有可能)

 2)jstat -gcutil pid 1000 100,观察系统gc情况(每隔1秒打印一次内存情况共打印100次)

2、大量循环

 1)通过一系列查询查找cpu高的代码:

  ps -ef | grep java,查询Java应用的进程pid

  top -H -p pid,查询占用cpu最高的线程pid

  printf "%x " xxxx,将10进制的线程pid转成16进制的线程pid,例如2000=0x7d0

  jstack -l pid | grep -A 20 '0x7d0',查找nid匹配的线程,查看堆栈,定位引起高cpu的原因(-l是显示锁信息)

 2)实际排查问题的时候建议打印5次至少3次,根据多次的堆栈内容,再结合相关代码段进行分析,定位高cpu出现的原因(因为cpu高可能是某一时刻,需要多次取样)

 3)jstack -l pid > 1.txt,可以将jstack保存为文件

 4)top -H -p pid,就截图吧

 5)cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c,线程状态归类,着重关注waitingtimed_waitingblocking就不用说了

二、低cpu高load

高load低cpu大概率是io问题,也有可能是大量线程频繁切换导致

1、大量线程频繁切换

 1)dstat查看总的线程上下文切换情况(也可以看到io情况),看有没有大量线程切换(每秒几w次或几十w次)

 2)pidstat -wt [-p xxxx] 1,查看每秒[某个进程下]所有线程数量和每秒切换次数

2、高磁盘io、高网络io

 1)查看vmstat,查看procs下的b这列(不可中断睡眠),如果不为0说明存在io等待,初步判断是io问题

 2)磁盘io:检查top中表示磁盘io的wa的百分比,确认是不是磁盘io导致的,也可以用dstat

 3)网络io:对依赖方的调用任何一个出现比较高的耗时都会增加自身系统的load

  检查中间件mysql、redis的调用错误日志,mysql可以用show full processlist命令查看线程等待情况,把其中的语句拿出来进行优化

  检查监控中dubbo、http调用是否存在较高耗时

  多次打印jstack线程堆栈,查找java.net.SocketInputStream相关的代码

3、资源吃紧或故障

 1)检查是否资源吃紧,检查磁盘使用df- h、检查内存使用free -m,可用物理内存不足时会发生比较严重的swap,SSD或网络的故障时系统调用会发生比较严重的中断

 2)检查是否外接io设备故障,比如NFS挂了,导致进程读写请求一直获取不到资源,从而进程一直是不可中断状态,造成负载很高

内存

一、GC

1、FullGC频繁

 1)jstat -gcutil pid 1000 100,每隔1秒打印一次内存情况共打印100次,观察老年代(O)、MetaSpace(MU)的内存使用率与FullGC次数

 2)确认有频繁的FullGC的发生,查看gc日志(结合工具gcviewer查看gc情况),每个应用gc日志配置的路径不同(开启gc日志:-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps-XX:+PrintGCTimeStamps -Xloggc:../logs/gc.log

 3)jmap -heap,查看并保存内存分配情况

 4)jmap -dump:format=b,file=filename pid,保留内存快照(dump出来的内容,结合MAT分析工具分析内存情况,排查是否存在内存泄漏

 5)重启应用,迅速止血,避免引起更大的线上问题

 6)cms和G1还有可能因为回收过程中无法添加大对象触发预案,使用Serial Old来完成回收,暂停时间很长

2、YoungGC频繁

 1)排查代码是不是循环里面反复创建了一些临时对象

 2)通过调整-Xmn-XX:SurvivorRatio增加年轻代大小

3、youngGC耗时过长

耗时过长问题就要看GC日志里耗时耗在哪一块了。以G1日志为例,可以关注Root Scanning、Object Copy、Ref Proc等阶段。Ref Proc耗时长,就要注意引用相关的对象。Root Scanning耗时长,就要注意线程数、跨代引用。Object Copy则需要关注对象生存周期

二、OOM

1、OOM Java heap space

 1)用jstackjmap排查内存泄漏

 2)通过调整Xmx的值来扩大内存

 3)可以在启动参数中指定-XX:+HeapDumpOnOutOfMemoryError来保存OOM时的dump文件

2、OOM Meta space

 1)检查是否是MaxMetaspaceSize设置过小,如果元空间频繁GC,考虑程序中有大量加载类的操作

 2)程序添加jvm参数-verbose:class,查看类加载的情况,定位问题代码,结合实际场景分析

3、OOM unable to create new native thread

 1)没有足够的内存空间给线程分配java栈,可能是线程池问题,可以用jstack查看整个线程状态

 2)可以通过指定Xss来减少单个thread stack的大小

4、OOM Direct buffer memory

 1)堆外内存溢出往往是和NIO的使用相关,关注错误日志里的OutOfDirectMemoryError、OutOfMemoryError: Direct buffer memory

 2)可以通过-XX:MaxDirectMemorySize调整堆外内存的使用上限

5、StackOverflowError

 1)当栈深度超过虚拟机分配给线程的栈大小时就会出现此error,一般在大量递归运算的时候出现

 2)表示线程栈需要的内存大于Xss值,排查后可以适当调大

网络

常用语句

 抓包并保存:tcpdump tcp -i eth0 -s 0 and host xxx.xxx.xxx.xxx and port xxxx -w log.pcap

 (-i:只抓经过接口eth0的包、-s 0:抓到完整的数据包、-w:保存在文件)

 查看某个端口的tcp连接状态统计:netstat -nat | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

 查看连接某端口最多的ip:netstat -nat |grep -i "80"| awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

 查看某个端口的连接数量:netstat -nat|grep -i "80"|wc -l

tcp常用参数

 参数查询:sysctl -a|grep -E 'tcp|somax'|grep -E 'tw|syn|somax|abort|times'

 net.core.somaxconn:每个端口最大监听队列长度,默认128,全连接队列长度就是它与服务器的backlog值的最小值

 net.ipv4.tcp_max_syn_backlog:半连接队列长度,默认1024

 net.ipv4.tcp_abort_on_overflow:连接队列溢出后的抛弃策略,默认0,表示认为溢出的原因是偶然产生,在之后的重发中连接将恢复状态,改为1则立刻返回rst包终止连接,会影响客户端体验

 net.ipv4.tcp_syncookies:是否开启防止syn flood攻击,默认0,改为1表示在半连接队列满了以后开启,改为2一直开启

 net.ipv4.tcp_max_tw_buckets:time_wait状态连接上限,默认180000

 net.ipv4.tcp_timestamps:防止伪造的seq号,默认1开启

 net.ipv4.tcp_tw_reuse:是否重用time_wait状态的连接,默认0,改为1后大约1秒回收(对挥手的发起方有效,需要tcp_timestamps为1)

 net.ipv4.tcp_tw_recycle:是否快速回收time_wait状态的连接,默认0,改为1后大约 3.5*RTO 内回收(客户端和服务端都要配置,需要tcp_timestamps为1),这个参数压测时候开线上一般不开,经过NAT公网服务负载的时候包里的timestamps可能被清空,开启后会导致大量丢包

 net.ipv4.tcp_synack_retries:对于收到的syn,回复ack+syn的重试次数,默认5,降低可以缓解ddos攻击

 net.ipv4.tcp_syn_retries:主动新建连接时syn的重试次数,默认5

一、连接队列异常

在压测和高并发服务的场景,出现建立连接失败时(现象是客户端日志里有大量的connection reset、connection reset by peer),需要排查tcp两个队列的情况,看是否发生了连接队列溢出

tcp的连接有两个队列,syns queue(半连接队列)、accept queue(全连接队列),三次握手时,在server收到client的syn后,把消息放到syns queue,回复syn+ack给client,server收到client的ack,如果这时accept queue没满,那就从syns queue拿出暂存的信息放入accept queue中,否则按tcp_abort_on_overflow指示的执行

1、半连接队列溢出

 1)检查半连接队列是否存在溢出:netstat -s | grep LISTEN,如果一直增加,就说明溢出了

 2)检查半连接队列长度:netstat -natp | grep SYN_RECV | wc -l,统计当前syn_recv状态的连接

 3)增加半连接队列长度:调大tcp_max_syn_backlog

2、全连接队列溢出

 1)检查全连接队列是否存在溢出:netstat -s | grep listen,如果一直增加,就说明溢出了

 2)检查全连接队列长度:ss -lntSend-Q为全连接长度,Recv-Q为当前使用了多少

 3)增加全连接队列长度:调大somaxconn,调大服务器的backlog(在tomcat中backlog叫做acceptCount,在jetty里面则是acceptQueueSize

3、ddos攻击

 1)如果是单一ip加入黑名单

 2)通过减少syn等待时间、减少syn重发次数tcp_synack_retries(默认5次)、增加半连接队列长度tcp_max_syn_backlog缓解

 3)开启syn_cookies(取消半连接队列,通过传递计算得到的cookies间接保存一部分SYN报文的信息),可以极大缓解。缺点是增加了运算量,拒绝syn报文中其他协商选项(比如扩大窗口请求)(值为1时在半连接队列满了以后触发)

 4)使用前置机将流量路由到防御网关进行流量清洗,攻击特征检查、限速

 5)开启阿里云的ddos防护

二、连接状态异常

 通过netstat -nat | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'语句统计

1、time_wait过多

 1)time_wait队列溢出会出现这个错误time wait bucket table overflow

 2)可以调大tcp_max_tw_buckets增加time_wait队列长度

 3)可以设置tcp_tw_reuse为1,开启tw连接重用

2、close_wait过多

close_wait往往都是因为应用程序写的有问题,没有在ACK后再次发起FIN报文。往往是由于某个地方阻塞住了,没有正常关闭连接,从而渐渐地消耗完所有的线程

3、fin-wait2过多

可以减小tcp_fin_timeout超时时间,默认60,防止fin-wait2状态连接过多

三、RST异常

出现rst异常一般有这些原因

 1、对端端口不存在,返回rst中断连接

 2、对端开启了使用rst主动代替fin快速终止连接

 3、对端发生异常,返回rst告知关闭连接(大部分原因)

 4、对端tcp连接已经不在了,返回rst告知重新建立连接

 5、对端长时间未收到确认报文,在多次重传后返回rst报文

1、Connection reset错误

在一个已关闭的连接上读操作会报connection reset

2、connection reset by peer错误

在一个已关闭的连接上写操作则会报connection reset by peer

3、broken pipe错误

在收到rst报出connection reset错后如果继续读写数据broken pipe,这是管道层面的错误,表示对已关闭的管道进行读写。根据tcp的约定,当收到rst包的时候,上层必须要做出处理,调用将socket文件描述符进行关闭才行

4、Connection reset解决办法

 1)出错后重试,需要注意操作的幂等性

 2)客户端和服务器统一使用TCP长连接

 3)客户端和服务器统一使用TCP短连接

四、超时

超时主要分连接超时和读写超时,如果没有网络问题的话,需要看一下服务端的服务能力

硬盘

1、iostat -x查看磁盘的状况,%util 接近100%,说明I/O系统已经满负荷,该磁盘可能存在瓶颈,如果await远大于svctm,说明I/O队列太长,io响应太慢,则需要进行必要优化

2、dstat可以看磁盘总的吞吐

3、top wa查看磁盘io占cpu的比例

4、iotop查看每个进程的io占用

中间件

一、redis

1、检查redis

 1)检查异常是否分布在少量节点,如果一个或少量节点超时,说明存在热key,如果大部分都超时过,说明redis整体压力较大

 2)检查相应时间是否存在慢请求,是的话可能存在大key

2、检查客户端

 1)检查客户端cpu,如果接近或超过80%说明计算资源不足

 2)频繁gc或者gc耗时过长会让线程无法及时被调度到读取redis响应

 3)检查TCP重传率(有一个shell脚本可以计算得出),看是不是网络问题

参考资料:

https://fredal.xin/java-error-check

原文地址:https://www.cnblogs.com/ctxsdhy/p/14051189.html