问题排查帮助手册
通用
一、保留现场
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\n" 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,线程状态归类,着重关注waiting和timed_waiting,blocking就不用说了
二、低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)用jstack和jmap排查内存泄漏
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 -lnt,Send-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