记 Arthas 实现一次 CPU 排查与代码热更新
作者 | 何波
【Arthas 官方社區(qū)正在舉行征文活動(dòng),參加即有獎(jiǎng)品拿~點(diǎn)擊投稿】
1.前言
背景
線上代碼經(jīng)常會(huì)出現(xiàn) CPU 占用過(guò)高的情況,按以往經(jīng)驗(yàn)我會(huì)使用 top 指令,進(jìn)一步借助于 jstack 去查看具體信息從而進(jìn)行問(wèn)題排查,但基本上都逃不過(guò)需要重新發(fā)包的局面,及時(shí)是一個(gè)增量包,應(yīng)用也需要短暫停啟。后來(lái)運(yùn)維大兄弟讓我試一下 Arthas,說(shuō)是可以進(jìn)行代碼的熱更新操作,正好來(lái)試一下。
關(guān)于 Arthas 的安裝與基礎(chǔ)使用可以參考下面兩篇文章:
- Arthas 安裝與監(jiān)聽(tīng) SpringBoot 應(yīng)用
- Arthas 基礎(chǔ)指令使用說(shuō)明
環(huán)境
JDK1.8
SPringBoot 2.2.2
Arthas
Linux
測(cè)試代碼:
@RequestMapping(value = "/bigThread") @ResponseBody public String bigThread(int id) {ArthasService.test();while (true) {Thread t2 = new Thread();t2.start();id ++;if(100000 == id) {return String.valueOf(id);}} }思路
2.thread -b 查看是否有阻塞線程
thread -b, 找出當(dāng)前阻塞其他線程的線程,執(zhí)行完之后并未發(fā)現(xiàn),說(shuō)明該線程并非一直阻塞、一直執(zhí)行的。
3.thread 查看占用最高的線程
當(dāng) thread 之后不跟參數(shù)時(shí),顯示當(dāng)前全部線程信息,我覺(jué)得 thread -n 10,展示前 10 應(yīng)該就夠用,可根據(jù)實(shí)際需要自己決定。
下圖可以很直觀的看出,我們的應(yīng)用瞬間占用了 77% 的 CPU(這里我是發(fā)起請(qǐng)求瞬間,通過(guò) thread 查看的,所以比較直觀,生產(chǎn)環(huán)境應(yīng)該只有阻塞,死鎖這種狀態(tài)才會(huì)比較直觀)。
4.thread id 查看具體信息
在上一步基礎(chǔ)上,我們進(jìn)一步查看,thread 15(因?yàn)樯厦娴?ID=15)。
他的大致意思就是:線程在等待一個(gè)條件從而繼續(xù)執(zhí)行,可以看到方法是在執(zhí)行 LinkedBlockingQueue.take 方法時(shí)候,查看這個(gè)方法的 API 提示如下:
public E take() throws InterruptedException {E x;int c = -1;final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();try {while (count.get() == 0) {notEmpty.await();}x = dequeue();c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {takeLock.unlock();}if (c == capacity)signalNotFull();return x; }其中:AtomicInteger 是保證高并發(fā)情況下的原子性,ReentrantLock 標(biāo)識(shí)可重入鎖,都是 JUC 包下需要了解的這里不贅述,需要的百度了解下。
這段代碼關(guān)鍵點(diǎn)就在于:notEmpty.await(),從隊(duì)列中消費(fèi)數(shù)據(jù),當(dāng)隊(duì)列為空是,線程阻塞,所以我們大致知道現(xiàn)在出現(xiàn)的問(wèn)題是線程阻塞,但是還是不知道具體哪行代碼的問(wèn)題。
如果能夠明確知道這次更改了哪些代碼,可以直接執(zhí)行步驟 6,不知道的話可以通過(guò)步驟 5 來(lái)定位問(wèn)題。
5.watch 查看哪個(gè) Controller 執(zhí)行了代碼
watch org.springframework.web.servlet.DispatcherServlet getHandler returnObj這個(gè)腳本可以檢測(cè)一切通過(guò) DispatcherServlet 匹配 Handler 的方法,也就是進(jìn)入 Controller 的請(qǐng)求,如下:
找到了對(duì)應(yīng)的代碼之后,我們來(lái)進(jìn)一步觀察異常信息,這里可能會(huì)有一個(gè)問(wèn)題:就是我明明能通過(guò)日志去查看錯(cuò)誤信息,為什么還需要這么繁瑣的去操作。我的業(yè)務(wù)場(chǎng)景是:日志還是非常大的,剛撈到就被刷過(guò)去了,這時(shí)候定位日志不是很好操作,當(dāng)然想撈下來(lái)日志肯定也是可以的,也很直觀,我一般也都是去查看日志進(jìn)行問(wèn)題定位,這里也是提供一個(gè)思路。
6.watch 該方法異常信息
watch 類(lèi)全路徑 方法名 "{params[0],throwExp}" -e -x 2如上,錯(cuò)誤很直觀的提示了出來(lái),下面就可以修復(fù)解決了,這里我們也可以通過(guò) trace 指令,查看執(zhí)行時(shí)長(zhǎng):
trace 類(lèi)全路徑 方法名 "{params[0],throwExp}" -e -x 2返回信息如下,也可以看到錯(cuò)誤信息,和每個(gè)方法執(zhí)行的時(shí)長(zhǎng)。
[arthas@10999]$ trace com.arthas.controller.OrderController bigThread Press Q or Ctrl+C to abort. Affect(class count: 1 , method count: 1) cost in 53 ms, listenerId: 10 `---ts=2020-08-19 14:45:57;thread_name=http-nio-0.0.0.0-8080-exec-10;id=16;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[1452.684406ms] com.arthas.controller.OrderController:bigThread() [throws Exception]+---[0.168814ms] com.arthas.service.ArthasService:test() #20`---throw:java.lang.OutOfMemoryError #-2 [unable to create new native thread]7.jad 反編譯熱更新
在上面知道問(wèn)題之后,我們就來(lái)定位問(wèn)題就好了。
命令:jad 類(lèi)全路徑 方法名
[arthas@13190]$ jad com.arthas.controller.OrderControllerClassLoader: +-org.springframework.boot.loader.LaunchedURLClassLoader@17f052a3 +-sun.misc.Launcher$AppClassLoader@3d4eac69 +-sun.misc.Launcher$ExtClassLoader@45f45fa1 Location: file:/opt/software/arthas/Arthas.jar!/BOOT-INF/classes!/ /** Decompiled with CFR.* * Could not load the following classes:* com.arthas.service.ArthasService* org.springframework.stereotype.Controller* org.springframework.web.bind.annotation.RequestMapping* org.springframework.web.bind.annotation.ResponseBody*/ package com.arthas.controller;import com.arthas.service.ArthasService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;@Controller public class OrderController {@RequestMapping(value={"/bigThread"})@ResponseBodypublic String bigThread(int id) {ArthasService.test();do {Thread t2 = new Thread();t2.start();} while (100000 != ++id);return String.valueOf(id);} }Affect(row-cnt:1) cost in 1513 ms.此時(shí)代碼就被反編譯了,為了能夠更改,所以我們需要輸出為 java 文件。
指令:jad com.arthas.controller.OrderController > /tmp/OrderController.java
即:jad 類(lèi)全路徑 方法名 > 存儲(chǔ)路徑/存儲(chǔ)名稱(chēng)
然后到 tmp 路徑下 vi 修改 java 文件即可,修改完成之后,查看對(duì)應(yīng)的 classloader 為編譯做準(zhǔn)備。
sc -d *OrderController | grep classLoaderHash mc -c 17f052a3 /tmp/OrderController.java -d /tmp但是這里編譯出錯(cuò)了,官方提示:
所以我們本地編譯好 class 文件,上傳上去是一樣的。
編譯前調(diào)用
[arthas@13190]$ trace com.arthas.controller.OrderController bigThread Press Q or Ctrl+C to abort. Affect(class count: 1 , method count: 1) cost in 77 ms, listenerId: 2 `---ts=2020-08-19 15:51:46;thread_name=http-nio-0.0.0.0-8080-exec-1;id=d;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[6734.666529ms] com.arthas.controller.OrderController:bigThread() [throws Exception]+---[0.786517ms] com.arthas.service.ArthasService:test() #20`---throw:java.lang.OutOfMemoryError #-2 [unable to create new native thread]更新前代碼
@RequestMapping(value = "/bigThread") @ResponseBody public String bigThread(int id) {ArthasService.test();while (true) {Thread t2 = new Thread();t2.start();id ++;if(100000 == id) {return String.valueOf(id);}} }更新后代碼
@RequestMapping(value = "/bigThread") @ResponseBody public String bigThread(int id) {ArthasService.test();Thread t2 = new Thread();t2.start();return "success"; }編譯指令
[arthas@13190]$ redefine /tmp/OrderController.class redefine success, size: 1, classes: com.arthas.controller.OrderController編譯后調(diào)用三次
`---ts=2020-08-19 15:52:02;thread_name=http-nio-0.0.0.0-8080-exec-3;id=f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[5.609405ms] com.arthas.controller.OrderController:bigThread()`---[0.204675ms] com.arthas.service.ArthasService:test() #20`---ts=2020-08-19 15:52:04;thread_name=http-nio-0.0.0.0-8080-exec-4;id=10;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[3.900149ms] com.arthas.controller.OrderController:bigThread()`---[0.14636ms] com.arthas.service.ArthasService:test() #20`---ts=2020-08-19 15:52:04;thread_name=http-nio-0.0.0.0-8080-exec-5;id=11;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[1.90945ms] com.arthas.controller.OrderController:bigThread()`---[0.147353ms] com.arthas.service.ArthasService:test() #20可以發(fā)現(xiàn)時(shí)間從 6734.666529ms 變成 3ms 左右,說(shuō)明熱更新的代碼生效了。
8.profile 繪制火焰圖做后續(xù)分析
如下圖所示:
Arthas 征文活動(dòng)火熱進(jìn)行中
Arthas 官方正在舉行征文活動(dòng),如果你有:
- 使用 Arthas 排查過(guò)的問(wèn)題
- 對(duì) Arthas 進(jìn)行源碼解讀
- 對(duì) Arthas 提出建議
- 不限,其它與 Arthas 有關(guān)的內(nèi)容
歡迎參加征文活動(dòng),還有獎(jiǎng)品拿哦~點(diǎn)擊投稿
“阿里巴巴云原生關(guān)注微服務(wù)、Serverless、容器、Service Mesh 等技術(shù)領(lǐng)域、聚焦云原生流行技術(shù)趨勢(shì)、云原生大規(guī)模的落地實(shí)踐,做最懂云原生開(kāi)發(fā)者的公眾號(hào)。”
總結(jié)
以上是生活随笔為你收集整理的记 Arthas 实现一次 CPU 排查与代码热更新的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 应用系统瓶颈排查和分析的思考-Artha
- 下一篇: 爱奇艺在 Dubbo 生态下的微服务架构