一、背景
在開發聯調階段發現一個接口的響應時間特别長,經常逾時,囧...
本文講講是如何定位到性能瓶頸以及修改的思路,将該接口從 2 s 左右優化到 200ms 以内 。
二、步驟
2.1 定位
定位性能瓶頸有兩個思路,一個是通過工具去監控,一個是通過經驗去猜想。
2.1.1 工具監控
就工具而言,推薦使用 arthas ,用到的是
trace 指令具體安裝步驟很簡單,大家自行研究。
我的使用步驟是,先最終待研究的函數的最外層:
trace com.xxx.service.impl.AServiceImpl refresh
其中耗時最多的子函數會被标紅色
Affect(class-cnt:2 , method-cnt:2) cost in 525 ms.
`---ts=2020-0X-0Y 13:33:18;thread_name=DubboServerHandler-127.0.0.1:20880-thread-36;id=24e;is_daemon=true;priority=5;TCCL=com.mmm.WWWClassLoader@4362d7df
`---[1761.834357ms] com.xxx.service.impl.AServiceImpl$$EnhancerBySpringCGLIB$$e3cd7543:refresh()
+---[0.017066ms] com.xxx.service.impl.AServiceImpl$$EnhancerBySpringCGLIB$$e3cd7543:$jacocoInit()
`---[1761.00347ms] org.springframework.cglib.proxy.MethodInterceptor:intercept()
`---[1757.647111ms] com.xxx.service.impl.AdServiceImpl:refresh()
+---[0.006629ms] com.xxx.biz.yyy.service.impl.AServiceImpl:$jacocoInit()
+---[0.004073ms] java.util.Collections:singletonList()
+---[1709.203302ms] com.yyy.service.impl.AServiceImpl:refreshSomeThings()
`---[48.135719ms] com.yzzzz.service.impl.AServiceImpl:createSurvey()
繼續再 trace 耗時最多的子函數。
trace com.yyy.service.impl.AServiceImpl refreshSomeThings
最終定位到最影響耗時的函數上,繼續往下跟。
最後發現造成性能瓶頸的函數是一個網絡請求,單次請求大概 100多毫秒。
為了避免調用的資料量太大,項目中采用分批調用的方式,但是每個批次太小,導緻請求次數過多。
假設請求 N 次(如 10次),每次請求 M毫秒(如 200ms),總耗時就是 N*M (2000)毫秒。
2.1.2 猜想
如果開發經驗足夠豐富,大緻可以猜出哪些接口可能存在性能問題。
最常見的有:
- 慢 SQL 會是性能瓶頸,主要原因是沒有命中索引。
- 發送遠端資料請求(RPC 遠端調用、HTTP 遠端調用)。
- I/O 操作等。
最常見的是在循環中執行 SQL或者網絡請求。
然後審查一下自己的代碼發現 SQL 查詢部分都可以命中索引,調用鍊路上有一個函數最終會調用 HTTP 請求,而且是在一個循環裡。
是以最有可能成為造成接口延時的是底層依賴的 HTTP 請求。
2.2 解決
既然 HTTP 請求是性能瓶頸,那麼要盡量減少請求,或者讓請求由串行改為多線程并發/并行。
減少網絡請求的次數,可以将多個請求合并成一個批量接口(或者增加批量請求的每個批次的大小)。
這裡的批次甚至可以使用動态配置,根據情況動态修改。
将串行改為并行可以使用
CompletableFuture
來實作,具體參見:
《Java 資料分批調用接口的正确姿勢》最終一個接口從1 s - 2 s降低到了 200 ms 以内。
3、總結
很多人不願意學習 arthas ,如果不去學習不去了解,遇到可以用上的場景想不起來去用。
另外大家可以積累下開發過程中常見的性能瓶頸的原因,以便未來遇到性能瓶頸是可以快速排查和解決問題。
最後大家在開發階段或測試階段,多看錯誤日志,多關注接口的響應時長等,盡早排除問題,盡早做優化。
希望本文對大家開發能夠有幫助。
如果我的文章對你有幫助,歡迎關注,點贊評論!!![]()
将一個接口響應時間從2s優化到 200ms以内的一個案例一、背景二、步驟3、總結