• 问题排查帮助手册


    通用

    一、保留现场

    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

  • 相关阅读:
    C# 如何telnet IP的某端口/ping 是否通
    centos7.9设置系统时间,并同步到硬件
    基于阿里云 DNS API 实现的 DDNS 工具
    GridControl 通用类2
    使用JSON.stringify时需注意的坑
    java中BigDecimal和0比较
    c# WindowsCommunityToolkit--- Shade Animation
    WPF 取消在触屏上点击按下不松开会出现矩形背景的效果
    c# 反射私有类和私有方法
    c# 汉字转拼音
  • 原文地址:https://www.cnblogs.com/ctxsdhy/p/14051189.html
Copyright © 2020-2023  润新知