排查Java线上服务故障的方法和实例分析

前言java

  做为在线系统负责人或者是一个技术专家,你可能刚刚接手一个项目就须要处理紧急故障,或者被要求帮忙处理一些紧急的故障,这个时候的情景是:api

  (1)你可能对这个业务仅仅是据说过,而不怎么真正了解;缓存

  (2)你可能没有这个故障的详细信息,好比可能仅仅是有使用方反馈服务中断了10分钟;服务器

  (3)你对代码细节尚未仔细研究过。dom

  这个时候该怎么解决问题呢?根据之前的经验,工程师们经常倾向于直接登上服务器检查代码,试图马上修改问题。或者是把某些多是问题的配置作修改,但并非100%确认这就是问题的根本缘由。但结果每每是在解决问题的同时引入了新的问题,或者是没有找到问题的根本缘由,致使用户的再次投诉。curl

 

正文异步

  处理和排除故障分为4个必须的步骤:工具

  (1) 紧急处理源码分析

  (2) 添加监控性能

  (3) 使用JDK性能监控工具

  (4) 分析源代码。从治标不治本,到治标又治本。

 

紧急处理

  紧急处理,顾名思义,是检查和评度当前故障的影响范围,并尽快使服务先恢复起来。其中检查和评估当前故障的影响范围是很是重要的。

  以微博系统举例,通常用户的投诉率为千分之1,若是有超过10起用户投诉,就多是大面积故障。若是只是负责线上跟踪的QA人员反馈的问题,而没有用户投诉,则能够有较多的时间去处理。

  对于紧急的大面积故障,首先想到的不该该是检查问题。而是须要马上追查最近线上系统是否有更改,咱们的经验是95%的故障都是在新代码上线后的12小时内发生的。此时应该马上回滚新更改。另外5%的故障大部分是因为业务扩展致使的。互联网业有一个规律,线上系统每半年须要重构一次,不然没法对应业务量的增加。对于这种业务量增加形成的故障,一般能够经过重启服务来紧急处理。

  所以,紧急处理的首选是马上回滚新更改

 

添加监控

  紧急处理以后,服务已经恢复了,可是问题并无找到。若是是新代码上线形成的故障,回滚以后,工程师会有各类手段,在测试环境追查问题。而针对系统容量不足形成的故障,须要特别添加监控做为追查问题的重要手段。由于互联网业务请求高峰和低谷差异很是明显,微博业务中的请求高峰每每出如今晚上10点左右,并且不是稳定的出现。要求工程师们天天悲催的坐在电脑前守到晚上10点,却不见得能发现问题,是很是低效的。监控通常用一个简单的脚本就能够搞定,好比http服务监控,Shell脚本代码以下:

  MAX_LATENCY_TIME=5    

  curl -m $MAX_LATENCY_TIME "http://1.2.3.4:8011/...."    

  if [ $? -ne 0 ] ; then  

          sIp=`hostname`  

          errmsg="gexin install latency > "$MAX_LATENCY_TIME"s"  

          // 具体告警的处理  

          echo $errmsg  

          echo send success  

  fi  

 

使用JDK性能监控工具

  刚刚添加的监控开始报警了。登上服务器,该作些什么呢?通常须要作以下动做,

  (1)首先要查看日志,看看有没有Exception。另外日志中经常有对接口调用,缓存使用的监控告警信息。

  (2)看看目前gc的情况如何,使用JDK自带的工具就能够。jstat -gc -h 10 vmid 5000,每5秒打出一次。我遇到过两次线上故障,都是简单的经过jstat就找到了问题。一次是Permanent区分配太小,JVM内存溢出。另外一次是发生了过多的Full GC,使得系统中止响应。内存形成的问题经过简单的重启服务就可使得服务恢复,但重启以后每每很快又出现问题。这个期间你要监控gc,看看期间发生了什么事情。若是是old区增加的过快,就多是内存泄露。这个时候,你须要看看究竟是什么对象占用了你的内存。

  (3)jmap -histo vmid > jmap.log,该命令会打出全部对象,包括占用的byte数和实例个数。分享一个线上jmap实例,

  Jmap.log代码  

  num     #instances         #bytes  class name  

  ---------------------------------------------  

  1:      10540623      668712400  [B  

    2:      10532610      252782640  com.google.protobuf.ByteString  

      3:       4305941      234036872  [Ljava.lang.Object;  

      4:       1066822      170691520  cn.abc.data.protobuf.UserWrap$User  

      5:       3950998      158039920  java.util.TreeMap$Entry  

      6:       3950159      126405088  com.google.protobuf.UnknownFieldSet$Field  

      7:        780773      118677496  cn.abc.data.protobuf.StatusWrap$Status  

      8:       4305047      103321128  java.util.ArrayList  

      9:       4301379      103233096  java.util.Collections$UnmodifiableRandomAccessList  

    10:       3207948      102654336  java.util.HashMap$Entry  

    11:       2571737       82300280  [C  

    12:       2569460       82222720  java.lang.String  

    13:       3952892       63246272  java.lang.Integer  

    14:       1314438       63093024  java.util.TreeMap  

    15:       2533229       60797496  java.lang.Long  

    16:       1065093       51124464  cn.abc.data.protobuf.PostSourceWrap$PostSource  

    17:       1314430       42061760  java.util.Collections$UnmodifiableMap  

    18:        284198       40924512  cn.sina.api.data.protobuf.CommentWrap$Comment  

    19:          4273       34273568  [Ljava.util.HashMap$Entry;  

    20:        459983       33118776  cn.abc.data.protobuf.AbcMessageWrap$AbcMessage  

    21:        458776       22021248  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask  

    22:        458776       22021248  java.util.concurrent.FutureTask$Sync  

    23:       1314416       21030656  com.google.protobuf.UnknownFieldSet  

     24:        459982       11039568  cn.sina.datapush.notifier.core.DispatcherBase$MessageProcessor  

     25:        458776       11010624  java.util.concurrent.Executors$RunnableAdapter  

     26:        458776        7340416  cn.abc.datapush.notifier.core.DispatcherBase$MessageProcessor$1  

     27:         40897        5909152  <constMethodKlass>  

     28:         40897        4917592  <methodKlass>  

     29:          3193        3748688  <constantPoolKlass>  

     30:         62093        3274344  <symbolKlass>  

   排名前几位的都是ByteCharStringInteger之类的,不要灰心,继续往下看,后面会出现一些有趣的对象。第22位发现了45万个FutureTask,很显然这是不正常的。应该是某线程响应过慢,形成待处理任务出现了堆积。查看代码,果真处理线程只有4条,大量的请求得不到响应。经过gc分析,每5秒钟会产生100M左右的对象得不处处理,因此JVM heap很快就被耗尽。将线程数改成36条,问题马上获得了解决。

 

  (4)检查目前cpu占用状况,top -H,而后按“1”,会看到当前进程中每一个线程所占CPU的比例。注意观察前几名,而后jstack -l vmid > jstack.log打出线程堆栈,看看是什么线程占用了CPU。这里须要注意的是,top -H显示的线程id是十进制,而jstack打出的线程堆栈是16进制。看看那些最忙的thread是否是那些真正应该忙的thread,若是是一些黑马线程,则要考虑是不是代码有死循环或者是意外的问题。

 

分析源代码

  分析源代码是最有技术含量的事情,也是比较耗时并且不见得有效果的事情。因此我把源码分析放到解决线上问题的最后一步,由于必需要作到有的放矢。带着问题去分析代码,会比较容易。经过20%代码的修改,就能够解决80%的性能问题。好比上面这个线上问题,确定是线程处理慢形成的问题。须要针对线程的调用,同步异步等进行分析。

相关文章
相关标签/搜索