在阿里內(nèi)部是如何 Debug 線上問題的?
在日常工作中我們經(jīng)常會(huì)遇到一些線上異常的情況,而且有些問題只有在線上才會(huì)出現(xiàn),由于環(huán)境和數(shù)據(jù)不一樣在本地和測(cè)試環(huán)境根本沒辦法復(fù)現(xiàn),而且線上也沒有輸出日志,那么遇到這種情況我們往往要怎么去解決呢?
常規(guī)做法
如果實(shí)在遇到上面的情況,在本地和測(cè)試都無法復(fù)現(xiàn),那最常規(guī)的做法就是拉個(gè)線上分支的版本,增加一些調(diào)試日志,然后再重新發(fā)布版本進(jìn)行調(diào)試。運(yùn)氣好加一次日志就可以找到問題,運(yùn)氣不好的話可能還要發(fā)布好幾次才能定位到問題。
高級(jí)做法
下載安裝 arthas
Arthas 是阿里開源的一款線上監(jiān)控診斷產(chǎn)品,通過全局視角實(shí)時(shí)查看應(yīng)用 load、內(nèi)存、gc、線程的狀態(tài)信息,并能在不修改應(yīng)用代碼的情況下,對(duì)業(yè)務(wù)問題進(jìn)行診斷,包括查看方法調(diào)用的出入?yún)ⅰ惓#O(jiān)測(cè)方法執(zhí)行耗時(shí),類加載信息等,大大提升線上問題排查效率。
上面的 Arthas 這款工具的官方介紹,從中我們可以看到這個(gè)工具可以查看方法的出入?yún)ⅲ惓R约氨O(jiān)測(cè)方法的耗時(shí),我們排查問題的時(shí)候最重要的就是想知道一些方法的入?yún)⒑头祷兀辛巳雲(yún)⒑头祷氐臄?shù)據(jù)我們就可以模擬出具體的場(chǎng)景從而解決線上的問題。
注意這里說的方法不單單是外層的接口方法,任何 Service 層或者 RPC 層的方法都是可以的。之所以提出這一點(diǎn)是因?yàn)楹芏鄷r(shí)候線上的接口都會(huì)有請(qǐng)求和結(jié)果的日志記錄,但是并沒有內(nèi)部某個(gè)具體方法的入?yún)⒑头祷兀ㄟ^ Arthas 我們可以監(jiān)控任何方法的入?yún)⒑头祷刂怠?/p>
我們可以在https://github.com/alibaba/arthas/releases 這個(gè)地址上下載具體的版本,下載下來就可以用,沒什么難度。接下來阿粉通過一個(gè)案例來帶大家使用一下 Arthas。
測(cè)試
首先我們啟動(dòng)一個(gè) Spring Boot 應(yīng)用,并且對(duì)外一個(gè) http 的接口,具體的 Controller 和 Service 代碼實(shí)現(xiàn)如下。
package com.example.demo.controller;
import com.example.demo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping(value = "/hello")
public String hello(@RequestParam("name") String name) {
return helloService.sayHello(name);
}
}
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class HelloService {
public String sayHello(String name) {
String result = doSomething(name);
return "hello: " + result;
}
private String doSomething(String name) {
return name + " 歡迎關(guān)注 Java 極客技術(shù)";
}
}
上面的代碼非常簡(jiǎn)單,對(duì)外暴露了一個(gè) hello 接口,接收一個(gè) name 參數(shù),Service 中通過 sayHello 方法,調(diào)用了一個(gè)內(nèi)部的 doSomething 方法。其中過后我們?cè)L問 http://127.0.0.1:8080/hello?name=ziyou 可以看到如下的內(nèi)容。
到這里都沒什么問題,但是我們想象一下,如果在某些 case doSomething 的方法比較復(fù)雜,我們想知道在執(zhí)行 doSomething 方法的時(shí)候具體的入?yún)⒑头祷刂凳鞘裁矗@個(gè)時(shí)候我們要怎么辦呢?
當(dāng)然如果采用上面的常規(guī)做法,增加一些日志,當(dāng)然是可以,不過我們這里就來演示一下如何使用 Arthas 來實(shí)現(xiàn)這個(gè)效果。前面我們已經(jīng)下載了 Arthas 了,下載完成后是一個(gè)壓縮包,解壓過后,可以通過命令java -jar arthas-boot.jar 來啟動(dòng) Arthas。效果如下
可以看到啟動(dòng)過后 Arthas 會(huì)找到目前系統(tǒng)當(dāng)中在運(yùn)行的 Java 進(jìn)程,我這里有四個(gè)進(jìn)程,然后輸入我們要監(jiān)控的進(jìn)程編號(hào),這里是 4,然后回車,接下來我們就進(jìn)入了 Arthas 的界面。
接下來我們使用 Arthas 的 watch 命令來監(jiān)控入?yún)⒑头祷刂担暾拿钊缦拢簑atch com.example.demo.service.HelloService doSomething '{params,returnObj}
接下來我們?cè)偃ピL問一下剛剛的接口,可以看到在終端上面有對(duì)應(yīng)的數(shù)據(jù)輸出。
簡(jiǎn)單解釋一下對(duì)應(yīng)的命令和輸出的信息,watch 是 Arthas 提供的命令,后面根據(jù)第一個(gè)參數(shù)是完整的類路徑,第二個(gè)參數(shù)是我們要監(jiān)控的方法名,第三個(gè)參數(shù)是一個(gè)表達(dá)是這里我們可以獲取入?yún)⒁约胺祷刂祷蛘弋惓5刃畔ⅲ瑢?duì)應(yīng)的取值有 params 數(shù)組形式,可以通過 params[0] 來獲取對(duì)應(yīng)的屬性,returnObj 表示返回值,并且這兩個(gè)都是可以通過 .的形式,獲取下面屬性的值的。
watch 命令后面的第三個(gè)參數(shù)其實(shí)是一個(gè) ognl 表達(dá)式,如下所示我們是可以做一些計(jì)算和邏輯處理的,這就帶來了很多高級(jí)的用法。Arthas的一些特殊用法文檔說明 https://github.com/alibaba/arthas/issues/71
總結(jié)
Arthas 除了 watch 之外還有很多其他的命令可以使用,比如 trace 可以統(tǒng)計(jì)每個(gè)方法執(zhí)行的耗時(shí),對(duì)于我們做一些性能優(yōu)化有很大的幫助,還有比如支持熱部署的功能等。完整的命令和使用方式大家可以在這個(gè)網(wǎng)址找到 https://arthas.aliyun.com/doc/,并且這里還提供了一套在線演示的功能,可以在在線教程中通過終端依次親手執(zhí)行每個(gè)命令來學(xué)習(xí)。官方提供了一個(gè)應(yīng)用程序 math-game.jar 可以讓我們?cè)诰€調(diào)試,感興趣的小伙伴可以去試一試。