javascript
Spring Boot 微服务性能下降九成!使用 Arthas 定位根因
作者 | 王瑞顯? 掌門教育基礎(chǔ)架構(gòu)部研發(fā)工程師
來源|阿里巴巴云原生公眾號(hào)
背景
接收到公司業(yè)務(wù)部門的開發(fā)反饋,應(yīng)用在升級(jí)公司內(nèi)部框架后,UAT(預(yù)生產(chǎn))環(huán)境接口性能壓測(cè)不達(dá)標(biāo)。
升級(jí)前壓測(cè)報(bào)告:
升級(jí)后壓測(cè)報(bào)告:
在機(jī)器配置(1C4G)相同的情況下,吞吐量從原來的 53.9/s 下降到了 6.4/s,且 CPU 負(fù)載較高。
并且開發(fā)反饋從公司全鏈路監(jiān)控系統(tǒng) SkyWalking 中查詢到的鏈路信息可以得知大部分請(qǐng)求 Feign 調(diào)用的耗時(shí)不太正常(390ms),而實(shí)際被調(diào)用的下游服務(wù)響應(yīng)速度很快(3ms)。
定位問題
在接收到反饋以后,我立即申請(qǐng)了相應(yīng)機(jī)器的權(quán)限,并往相應(yīng)機(jī)器上傳了 Arthas(version 3.4.3)。
讓業(yè)務(wù)方保持壓測(cè),開始問題定位。
1. 執(zhí)行 profiler 命令對(duì) CPU 進(jìn)行性能分析
[arthas@17962]$ profiler start -d 30 -f /tmp/arthas/1.txt等待 30s 后,打開 1.txt,查看 CPU 性能分析結(jié)果,開頭部分示例如下:
--- 1630160766 ns (4.24%), 141 samples......[14] org.springframework.boot.loader.LaunchedURLClassLoader.definePackageIfNecessary[15] org.springframework.boot.loader.LaunchedURLClassLoader.loadClass[16] java.lang.ClassLoader.loadClass[17] java.lang.Class.forName0[18] java.lang.Class.forName[19] org.springframework.util.ClassUtils.forName[20] org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.registerWellKnownModulesIfAvailable[21] org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.configure[22] org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.build[23] org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.addDefaultHttpMessageConverters[24] org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getMessageConverters[25] org.springframework.boot.autoconfigure.http.HttpMessageConverters$1.defaultMessageConverters[26] org.springframework.boot.autoconfigure.http.HttpMessageConverters.getDefaultConverters[27] org.springframework.boot.autoconfigure.http.HttpMessageConverters.<init>[28] org.springframework.boot.autoconfigure.http.HttpMessageConverters.<init>[29] org.springframework.boot.autoconfigure.http.HttpMessageConverters.<init>[30] com.zhangmen.xxx.DefaultFeignConfig.lambda$feignDecoder$0[31] com.zhangmen.xxx.DefaultFeignConfig$$Lambda$704.256909008.getObject[32] org.springframework.cloud.openfeign.support.SpringDecoder.decode[33] org.springframework.cloud.openfeign.support.ResponseEntityDecoder.decode......2. 對(duì)可疑的方法執(zhí)行 trace 命令輸出方法路徑上每個(gè)節(jié)點(diǎn)的耗時(shí)
分析上一步得到的 CPU 性能分析結(jié)果,可以發(fā)現(xiàn)最占用 CPU 的棧中,確實(shí)有 Feign 相關(guān)的棧幀。
并且發(fā)現(xiàn) Feign 相關(guān)的棧幀周圍出現(xiàn)了 com.zhangmen 相關(guān)的棧幀:com.zhangmen.xxx.DefaultFeignConfig$$Lambda704.256909008.getObject和com.zhangmen.xxx.DefaultFeignConfig.lambda704.256909008.getObject 和 com.zhangmen.xxx.DefaultFeignConfig.lambda704.256909008.getObject和com.zhangmen.xxx.DefaultFeignConfig.lambdafeignDecoder$0。
在 1.txt 中搜索 com.zhangmen.xxx.DefaultFeignConfig,發(fā)現(xiàn)有 340 次命中,因此認(rèn)為這是一個(gè)非常可疑的方法。
執(zhí)行 trace 命令輸出該方法路徑上每個(gè)節(jié)點(diǎn)的耗時(shí):
[arthas@17962]$ trace com.zhangmen.xxx.DefaultFeignConfig * '#cost>200' -n 3 `---[603.999323ms] com.zhangmen.xxx.DefaultFeignConfig:lambda$feignEncoder$1()`---[603.856565ms] org.springframework.boot.autoconfigure.http.HttpMessageConverters:<init>() #42發(fā)現(xiàn) org.springframework.boot.autoconfigure.http.HttpMessageConverters:() 比較耗時(shí),并繼續(xù)一層層 trace 追蹤下去:
[arthas@17962]$ trace org.springframework.boot.autoconfigure.http.HttpMessageConverters <init> '#cost>200' -n 3 ...... [arthas@17962]$ trace org.springframework.http.converter.json.Jackson2ObjectMapperBuilder registerWellKnownModulesIfAvailable '#cost>200' -n 3最終發(fā)現(xiàn) org.springframework.util.ClassUtils:forName() 比較耗時(shí),并且拋出了異常。
使用 watch 命令查看具體的異常:
[arthas@17962]$ watch org.springframework.util.ClassUtils forName -e "throwExp" -n解決問題
將定位到的問題,反饋給相關(guān)業(yè)務(wù)開發(fā),并建議引入 jackson-datatype-joda 依賴。
引入依賴后壓測(cè)報(bào)告:
吞吐量從原來的 6.4/s 提升到了 69.3/s,比升級(jí)框架前的 53.9/s 還要高。
這時(shí)候相關(guān)業(yè)務(wù)開發(fā)反饋,這個(gè)問題是代碼中自定義了 Feign 的編解碼器(下圖所示)導(dǎo)致的,并且這個(gè)編解碼器在升級(jí)框架前也是一直存在的。
于是,對(duì)升級(jí)框架前的代碼進(jìn)行壓測(cè)并在壓測(cè)過程中使用 Arthas 執(zhí)行以下命令:
發(fā)現(xiàn)同樣有這個(gè)異常。引入 jackson-datatype-joda 依賴,再次進(jìn)行壓測(cè),壓測(cè)報(bào)告如下:
匯總前面的壓測(cè)結(jié)果:
可以發(fā)現(xiàn)一個(gè)新的問題:為什么新舊版本同時(shí)不引入依賴,吞吐量相差近 8 倍,新舊版本同時(shí)引入依賴,吞吐量相差近 1 倍?
進(jìn)一步定位問題
根據(jù)上一步中發(fā)現(xiàn)的新問題,接下來對(duì) 未升級(jí)框架并引入依賴的版本 和 升級(jí)框架并引入依賴的版本 分別進(jìn)行壓測(cè),并在壓測(cè)過程中使用 Arthas 的 profiler 命令采樣 CPU 性能分析數(shù)據(jù),得到樣本 1 和樣本 2 。并從樣本 1 和樣本 2 中找到相似棧進(jìn)行對(duì)比:
通過對(duì)比可以發(fā)現(xiàn),兩個(gè)樣本的相似棧的前 17 行有所不同。并對(duì)樣本 2 中的可疑棧幀進(jìn)行 trace 追蹤:
[arthas@10561]$ trace org.apache.catalina.loader.WebappClassLoaderBase$CombinedEnumeration * '#cost>100' -n 3 `---[171.744137ms] org.apache.catalina.loader.WebappClassLoaderBase$CombinedEnumeration:hasMoreElements()`---[171.736943ms] org.apache.catalina.loader.WebappClassLoaderBase$CombinedEnumeration:inc() #2685`---[171.724546ms] org.apache.catalina.loader.WebappClassLoaderBase$CombinedEnumeration:inc()發(fā)現(xiàn)升級(jí)框架后,在類加載器這部分存在比較耗時(shí)的情況。
而對(duì)樣本 1 中這部分進(jìn)行 trace 追蹤沒有出現(xiàn)耗時(shí)大于 100ms 的情況。
又進(jìn)一步使用 profiler 命令,分別生成兩個(gè)版本在壓測(cè)場(chǎng)景下的火焰圖,并找到相似棧進(jìn)行對(duì)比:
[arthas@10561]$ profiler start -d 30 -f /tmp/arthas/1.svg發(fā)現(xiàn) 升級(jí)框架并引入依賴的版本 還多出了一些 org/springframework/boot/loader/ 相關(guān)的棧。
進(jìn)一步解決問題
將新的發(fā)現(xiàn)反饋給相關(guān)業(yè)務(wù)開發(fā)。
他們反映此次除了框架升級(jí)外,還有 Spring Boot war to jar 部署的調(diào)整。從使用獨(dú)立的 Tomcat war 部署,改造成用 Spring Boot 內(nèi)嵌 Tomcat java -jar 部署。故懷疑兩種部署方式在類加載器上存在性能差異。
相關(guān)業(yè)務(wù)開發(fā)在我上一步定位問題期間,根據(jù)我最初定位到的問題,在 Google 搜索 feign com.fasterxml.jackson.datatype.joda.JodaModule,找到了一篇相關(guān)的文章《loadClass 導(dǎo)致線上服務(wù)卡頓分析》。
文章中的作者,遇到的問題與我們基本相似。
看了這篇文章后,我又 Debug 了部分源碼,最后了解到問題產(chǎn)生的根本原因是:SpringEncoder / SpringDecoder 在每次編碼 / 解碼時(shí)都會(huì)調(diào)用 ObjectFactory.getObject()).getConverters() 獲取 HttpMessageConverters。而我們自定義的 DefaultFeignConfig 中配置的ObjectFactory 的實(shí)現(xiàn)是每次都 new 一個(gè)新的 HttpMessageConverters 對(duì)象。
HttpMessageConverters 的構(gòu)造方法又默認(rèn)會(huì)執(zhí)行 getDefaultConverters 方法獲取默認(rèn)的 HttpMessageConverter 集合,并初始化這些默認(rèn)的 HttpMessageConverter。其中的?MappingJackson2HttpMessageConverter(有兩個(gè),見下圖) 每次初始化時(shí)都會(huì)加載不在?classpath?中的?com.fasterxml.jackson.datatype.joda.JodaModule和?com.fasterxml.jackson.datatype.joda$JodaModule(org.springframework.util.ClassUtils?加載不到類時(shí),會(huì)嘗試再加載一下內(nèi)部類),并拋出?ClassNotFoundException,且該異常最后被生吞。
而部分和 XML 相關(guān)的默認(rèn)的?HttpMessageConverter,SourceHttpMessageConverter?和?Jaxb2RootElementHttpMessageConverter(各有兩個(gè),見下圖)每次初始化時(shí),會(huì)執(zhí)行?TransformerFactory.newInstance(),執(zhí)行過程中會(huì)使用 SPI 掃描?classpath?下的?META-INF / services?目錄獲取具體的實(shí)現(xiàn),并且每次掃描完也沒有獲取到具體的實(shí)現(xiàn),最終使用默認(rèn)指定的?com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl作為實(shí)現(xiàn)。
最終導(dǎo)致每一次 Feign 調(diào)用(包含編碼和解碼),都會(huì)加載 4 次不在?classpath?中的?com.fasterxml.jackson.datatype.joda.JodaModule?和?com.fasterxml.jackson.datatype.joda$JodaModule(共8次),且 8 次使用 SPI 掃描?classpath?下的?META-INF / services?目錄獲取查找不到的實(shí)現(xiàn),而 war to jar 后,類加載器在頻繁查找和加載資源上的性能有所下降,最終導(dǎo)致嚴(yán)重影響接口性能。
默認(rèn)的 HttpMessageConverter 集合:
部分關(guān)鍵代碼如下。
org/springframework/boot/autoconfigure/http/HttpMessageConverters.:
org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.registerWellKnownModulesIfAvailable:
org/springframework/util.ClassUtils.forName:
org/springframework/http/converter/xml/SourceHttpMessageConverter:
javax/xml/transform/FactoryFinder.find:
文章中對(duì)于這個(gè)問題還提供了兩種解決方法:
第一種方法,就是我最初建議的引入 jackson-datatype-joda 依賴,避免每次初始化默認(rèn)的 MappingJackson2HttpMessageConverter 時(shí) ClassLoader 反復(fù)加載不在 classpath 中的 com.fasterxml.jackson.datatype.joda.JodaModule 和 com.fasterxml.jackson.datatype.joda$JodaModule。
第二種方法,不初始化默認(rèn)的 HttpMessageConverter。由于我們此處只需要使用自定義的 FastJsonHttpMessageConverter 來執(zhí)行編解碼,完全可以避免執(zhí)行 getDefaultConverters 方法,重復(fù)初始化許多用不到的默認(rèn)的 HttpMessageConverter。因此在 new HttpMessageConverters 對(duì)象時(shí) ,可以將 addDefaultConverters 參數(shù)設(shè)置為 false。
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(false, new HttpMessageConverter[] { (HttpMessageConverter)fastJsonHttpMessageConverter });實(shí)際上,我們還可以修改 DefaultFeignConfig 中 ObjectFactory 的實(shí)現(xiàn),避免每次都 new 一個(gè)新的 HttpMessageConverters 對(duì)象(重復(fù)初始化 HttpMessageConverters),實(shí)現(xiàn)進(jìn)一步優(yōu)化。
故建議相關(guān)業(yè)務(wù)開發(fā),將 DefaultFeignConfig 修改成如下代碼:
在相關(guān)業(yè)務(wù)開發(fā)將新舊版本代碼中的 DefaultFeignConfig 都進(jìn)行改進(jìn)并部署到 FAT(測(cè)試)環(huán)境以后,我在自己本機(jī)用 JMeter 對(duì) FAT 環(huán)境進(jìn)行了模擬壓測(cè)。
舊版改進(jìn)后壓測(cè)結(jié)果:
新版改進(jìn)后壓測(cè)結(jié)果:
發(fā)現(xiàn)此時(shí),兩個(gè)版本的接口性能已經(jīng)基本相同。
最終測(cè)試人員在 UAT 環(huán)境,對(duì)升級(jí)框架并改進(jìn) DefaultFeignConfig 后的代碼再次進(jìn)行壓測(cè),壓測(cè)結(jié)果如下:
吞吐量從最初不達(dá)標(biāo)的 6.4/s,提升到了160.4/s。
那為什么 war to jar 部署的調(diào)整,會(huì)導(dǎo)致類加載器在頻繁查找和加載資源時(shí)性能有所下降呢?
在對(duì) SpringBoot 可執(zhí)行 jar 的原理做了一番了解以后。懷疑和 Spring Boot 為了做到能夠以一個(gè) fat jar 來啟動(dòng),從而擴(kuò)展了 JDK 的 JarFile URL 協(xié)議,并定制了自己的 ClassLoader 和 jar 文件協(xié)議的 Hander,實(shí)現(xiàn)了 jar in jar、jar in directory 的加載方式有關(guān)。
對(duì) SpringBoot 可執(zhí)行 jar 原理感興趣的朋友可以參考:《可執(zhí)行 jar 包》。
War2Jar 類加載器性能下降根因探究
為了驗(yàn)證自己的猜測(cè),我在自己本機(jī)搭建了一個(gè)簡(jiǎn)單的 Demo。
Demo 中有兩個(gè)服務(wù),A 和 B。將 A 和 B 都注冊(cè)到 Eureka 注冊(cè)中心,并且 A 通過 Feign 調(diào)用 B。
接下來使用 Jmeter 在相同的配置下對(duì)各種場(chǎng)景進(jìn)行壓測(cè),并在壓測(cè)過程中使用 Arthas 的 profiler 命令生成各種場(chǎng)景下的火焰圖。
壓測(cè)結(jié)果如下(-Xms512m -Xmx512m):
通過對(duì)比表格三和表格四可以得知,代碼優(yōu)化后,是否引入依賴,對(duì)吞吐量幾乎沒有影響。
通過表格三和表格四可以得知,代碼優(yōu)化后,在不會(huì)頻繁查找和加載不存在的資源時(shí),三種部署方式的吞吐量基本一致。
通過表格二可以得知,在頻繁使用 SPI 獲取 classpath 下查找不到的實(shí)現(xiàn)時(shí),Tomcat war 部署性能更好。
通過表格一可以得知,在頻繁加載不存在的類時(shí),將 jar 包解壓后通過 JarLauncher 啟動(dòng)性能更好。
對(duì)比表格一中 ③ 和 ② 相似棧的火焰圖:
可以發(fā)現(xiàn)兩者在 org/springframework/boot/loader/LaunchedURLClassLoader.loadClass 加載類時(shí),存在差異。
② 不僅會(huì)執(zhí)行 java/lang/ClassLoader.loadClass,還會(huì)執(zhí)行 org/springframework/boot/loader/LaunchedURLClassLoader.definePackageIfNecessary。
查看 org/springframework/boot/loader/LaunchedURLClassLoader.loadClass 的源碼:
發(fā)現(xiàn)存在一個(gè)條件分支。
查看 org/springframework/boot/loader/Launcher.createArchive 的源碼:
發(fā)現(xiàn)這個(gè)條件的值與應(yīng)用是 可執(zhí)行 jar 文件 還是 文件目錄 有關(guān)。
對(duì) ② 再次進(jìn)行壓測(cè),并 trace org/springframework/boot/loader/LaunchedURLClassLoader.definePackageIfNecessary:
`---[165.770773ms] org.springframework.boot.loader.LaunchedURLClassLoader:definePackageIfNecessary()+---[0.00347ms] org.springframework.boot.loader.LaunchedURLClassLoader:getPackage() #197`---[165.761244ms] org.springframework.boot.loader.LaunchedURLClassLoader:definePackage() #199發(fā)現(xiàn)這個(gè)地方確實(shí)存在比較耗時(shí)的情況。
閱讀該部分源碼,從注釋中即可得知,definePackageIfNecessary 主要是為了在調(diào)用 findClass 之前嘗試根據(jù)類名去定義類所在的 package,確保 jar 文件嵌套 jar 包里的 manifest 能夠和 package 關(guān)聯(lián)起來。
Debug definePackageIfNecessary 這部分代碼,發(fā)現(xiàn)在 definePackage 時(shí),會(huì)遍歷 BOOT-INF/lib/ 下所有的 jar 包 以及 BOOT-INF/classes/。如果發(fā)現(xiàn)這些資源中存在指定的類,就繼續(xù)調(diào)用 definePackage 方法,否則遍歷完直接返回 null。
前面說過,每一次 Feign 調(diào)用都會(huì)加載 4 次不在 classpath 中的 com.fasterxml.jackson.datatype.joda.JodaModule 和 com.fasterxml.jackson.datatype.joda$JodaModule (共8次)。而我這個(gè)簡(jiǎn)單的 Demo 應(yīng)用依賴的 jar 有 117 個(gè)(實(shí)際企業(yè)級(jí)項(xiàng)目將會(huì)更多)。那么每一次 Feign 調(diào)用,就會(huì)執(zhí)行 8 * (117 + 1),總計(jì) 944 次循環(huán)里的邏輯。而邏輯中的 org.springframework.boot.loader.jar.Handler.openConnection 方法在執(zhí)行過程中又會(huì)涉及到比較耗時(shí)的 IO 操作,最終導(dǎo)致嚴(yán)重影響接口性能。從生成的火焰圖中,也可以看到這部分處理邏輯。
至此,已經(jīng)可以確認(rèn) war to jar 部署的調(diào)整,導(dǎo)致類加載器在頻繁查找和加載資源時(shí)性能有所下降的根因就是:Spring Boot 為了做到能夠以一個(gè) fat jar 來啟動(dòng),增加了一些定制的處理邏輯,而這部分定制的處理邏輯在頻繁執(zhí)行時(shí),會(huì)對(duì)程序性能產(chǎn)生較大影響。
至于 [為什么在頻繁加載不存在的類時(shí),將 jar 包解壓后通過 JarLauncher 啟動(dòng)比 Tomcat war 部署性能更好?] 、[在頻繁使用 SPI 獲取 classpath 下查找不到的實(shí)現(xiàn)時(shí),Tomcat war 部署又比將 jar 包解壓后通過 JarLauncher 啟動(dòng)性能更好?] ,受限于篇幅,就不在本文中繼續(xù)展開了。感興趣的朋友可以按照本文中介紹的方法,再結(jié)合相關(guān)源碼進(jìn)行進(jìn)一步探究。
總結(jié)
大家在自定義 Feign 的編解碼器時(shí),如果用到了 SpringEncoder / SpringDecoder,應(yīng)避免 HttpMessageConverters 的重復(fù)初始化。如果不需要使用那些默認(rèn)的 HttpMessageConverter,可以在初始化 HttpMessageConverters 時(shí)將第一個(gè)入?yún)⒃O(shè)置為 false,從而不初始化那些默認(rèn)的 HttpMessageConverter。
另外,應(yīng)該了解不同的部署方式在類加載器頻繁查找和加載資源時(shí)是存在性能差異的。
我們?cè)趯懘a時(shí),也應(yīng)該要避免重復(fù)初始化,以及反復(fù)查找和加載不存在的資源。
最后,善用SkyWalking 和Arthas 可以幫助我們更加高效地排查程序錯(cuò)誤和性能瓶頸。
彩蛋
如果應(yīng)用使用了 SkyWalking Agent,再使用 Arthas,可能會(huì)遇到 Arthas 部分命令(trace、watch 等會(huì)對(duì)類進(jìn)行增強(qiáng)的命令)不能正常工作的問題。
解決方案:https://github.com/apache/skywalking/blob/master/docs/en/FAQ/Compatible-with-other-javaagent-bytecode-processing.md
而當(dāng) Arthas 可以正常工作以后,我們對(duì)于 SkyWalking Agent 已經(jīng)增強(qiáng)過的類的方法執(zhí)行 trace 等命令時(shí),最好在方法名后面加上一個(gè) * 符號(hào)進(jìn)行模糊匹配。Arthas 最終會(huì)將所有匹配方法的 trace 追蹤結(jié)果進(jìn)行匯總展示。
方法名不加 * 進(jìn)行 trace:
方法名加上 * 進(jìn)行trace:
可以看到方法名加上 * 以后,trace 得到的結(jié)果才是我們理想的結(jié)果。
這是由于 SkyWalking Agent 使用了 ByteBuddy 做字節(jié)碼增強(qiáng)。而 ByteBuddy 每增強(qiáng)一個(gè)方法,都會(huì)為該方法生成一個(gè)輔助內(nèi)部類(HelloControllerauxiliaryauxiliaryauxiliaryjiu2bTqU),并且會(huì)對(duì)當(dāng)前類(HelloController)中的原方法(test1)進(jìn)行重命名(test1originaloriginaloriginallyu0XDob),并生成一個(gè)與原方法同名的方法(test1)和一個(gè)不同名但僅供輔助內(nèi)部類調(diào)用的方法(test1originaloriginaloriginallyu0XDob$accessor$8F82ImAF)。
使用同事開發(fā)的 Java 反編譯工具可以直觀地看到相關(guān)代碼:
另外,在使用 Arthas 的時(shí)候,建議選擇最新的版本。比如,3.4.2 以前的版本 trace 追蹤大方法時(shí)就可能會(huì)導(dǎo)致 JVM Metaspace OOM。詳情見:《記一次由 Arthas 引起的 Metaspace OOM 問題》。
如果你想要基于 Arthas 打造企業(yè)級(jí)在線診斷平臺(tái),可以參考《工商銀行打造在線診斷平臺(tái)的探索與實(shí)踐》。
作者簡(jiǎn)介
王瑞顯,開源愛好者,現(xiàn)任掌門教育基礎(chǔ)架構(gòu)部研發(fā)工程師,主要負(fù)責(zé)公司全鏈路監(jiān)控系統(tǒng)和應(yīng)用診斷平臺(tái)的研發(fā)。
歡迎登陸 start.aliyun.com 知行動(dòng)手實(shí)驗(yàn)室體驗(yàn) Arthas 57 個(gè)動(dòng)手實(shí)驗(yàn):https://start.aliyun.com/handson-lab/#!category=arthas
Arthas 實(shí)驗(yàn)預(yù)覽
為了讓更多開發(fā)者開始用上 Arthas 這個(gè) Java 診斷神器,Arthas 社區(qū)聯(lián)合 JetBrains 推出?Arthas 有獎(jiǎng)?wù)魑幕顒?dòng):**聊聊這些年你和 Arthas 之間的那些事兒。**活動(dòng)仍在火熱進(jìn)行中,點(diǎn)擊即可參與,歡迎大家踴躍投稿,參與即有可能獲獎(jiǎng)!
總結(jié)
以上是生活随笔為你收集整理的Spring Boot 微服务性能下降九成!使用 Arthas 定位根因的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里新晋 CNCF TOC 委员张磊:“
- 下一篇: Arthas 使用的各类方式