排查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%的性能問題。好比上面這個線上問題,確定是線程處理慢形成的問題。須要針對線程的調用,同步異步等進行分析。

相關文章
相關標籤/搜索