高效開發與設計:提效Spring應用的執行效率和生產力

2023-11-20 12:00:29

引言

現狀和背景

Spring框架是廣泛使用的Java開發框架之一,它提供了強大的功能和靈活性,但在大型應用中,由於Spring框架的複雜性和依賴關係,應用的啟動時間和效能可能會受到影響。這可能導致開發過程中的遲緩和開發效率低下。優化Spring應用程式的啟動速度和效能是一個重要的任務,通過分析和優化應用的初始化過程、減少不必要的依賴和元件載入、並利用非同步初始化、懶載入等技術,可以顯著改善應用的啟動效能。這將幫助開發者提高開發效率、減少偵錯時間,並提供更好的使用者體驗。

線上的業務 jar 包基本上普遍比較龐大,動不動一個 jar 包幾百 M,啟動時間在10分鐘級,拖慢了我們在故障時快速擴容的響應、以及本地開發偵錯效率。於是做了一些分析,看看 Spring 程式啟動慢到底慢在哪裡,如何去優化,目前的效果是大部分大型應用啟動時間可以縮短 70%~80%。

主要有下面這些內容

  • SpringBean 載入耗時 timeline 視覺化分析(✅)
  • SpringBean 的視覺化依賴分析(✅)
  • 應用未載入的jar包(Jar瘦身)(✅)
  • 應用啟動過程執行緒wall clock火焰圖(✅)

重要性和影響

開發效率提高:較快的應用啟動速度可以顯著減少開發和偵錯的時間。開發人員能夠更快地啟動應用程式,進行功能測試和偵錯,從而提高開發效率和迭代速度。

部署和擴充套件效率提升:優化啟動速度可以減少部署和擴充套件應用程式的時間和成本。快速啟動的應用程式可以更快地響應負載變化,提高系統的可伸縮性和彈性。

資源利用率優化:通過減少初始化時間和優化資源載入,可以降低應用程式的記憶體和CPU佔用率。這有助於提高伺服器的利用率,並降低執行應用程式的成本。

分析工具

  • Arthas:Arthas是一個開源的Java診斷工具,可以實時監控和診斷Java應用程式。它提供了豐富的命令和功能,用於分析應用程式的效能問題,包括啟動過程中的資源消耗和載入時間。
  • JVM Sandbox:JVM Sandbox是一種基於Java安全管理器的技術,用於隔離和限制Java應用程式的存取許可權。它可以幫助減少啟動時的資源消耗和載入時間,提高應用程式的啟動速度。
  • Async Profiler:Async Profiler是一個低開銷的非同步Java效能分析工具,用於收集和分析應用程式的效能資料。它可以幫助你找出啟動過程中的效能瓶頸,以及其他影響啟動速度的問題。
  • 啟動加速-非同步初始化方法:非同步初始化方法是一種啟動加速的技術,通過將一些初始化任務非同步執行,可以減少啟動時間並提高應用程式的響應性。這可以通過使用執行緒池、非同步框架或非同步註解等方式來實現。
  • Spring Boot Startup ReportSpring Boot Startup Report是一個用於生成Spring Boot應用程式啟動報告的工具。它可以提供詳細的啟動過程資訊,包括每個bean的載入時間、自動設定的耗時等,幫助你分析和優化啟動過程。
  • Jaeger UI Jaeger UI是一個用於視覺化和分析分散式追蹤資料的工具。通過使用Jaeger UI,你可以監控和分析應用程式的啟動過程,識別潛在的效能問題和瓶頸。
  • Spring Startup Analyzer:Spring Startup Analyzer是一個用於採集Spring應用程式啟動過程資料並生成互動式分析報告的工具。它的目標是幫助分析Spring應用程式的啟動卡點,並支援Spring Bean的非同步初始化,以減少優化Spring應用程式的啟動時間。該工具支援在Linux、Mac和Windows作業系統上執行,並參考了spring-boot-startup-report實現其使用者介面。使用Spring Startup Analyzer,可以收集應用程式的啟動過程資料,並生成視覺化的HTML報告。這個報告可以幫助你分析Spring應用程式的啟動效能,並找出潛在的優化機會。

Spring Startup Analyzer優化方案

藉助Spring startup analyzer的能力,我們以業務線的ARK專案為例,深入研究如何優化提效Spring專案的啟動過程。下面我們先觀察下ARK的基本啟動情況:

啟動概覽

  • Startup Time(s):啟動時長
  • Num of Bean:初始化的Bean數量
  • Used/Total Jars:使用Jar數量/總量
  • Unused/Total Jars:未使用Jar數量/總量
  • ClassLoader Count:類載入器數量

Spring Bean初始化詳情

  • Name:一級name對應著Bean的名稱
  • Duration with children (ms) :Bean的參照載入時長
  • Duration (ms) :Bean本身的載入時長
  • Detail:包含類載入器、載入該Bean的執行緒資訊(非同步載入的話會有多個不同的)

SpringBean 載入耗時 timeline 視覺化分析

這個觀察項可以一直下探,直到Bean參照的最末級,可以看出每一級的載入時長

應用啟動過程執行緒wall clock火焰圖

如何看懂火焰圖

y 軸表示呼叫棧,每一層都是一個函數。呼叫棧越深,火焰就越高,頂部就是正在執行的函數,下方都是它的父函數。

x 軸表示抽樣數,如果一個函數在 x 軸佔據的寬度越寬,就表示它被抽到的次數多,即執行的時間長。注意,x 軸不代表時間,而是所有的呼叫棧合併後,按字母順序排列的。

火焰圖就是看頂層的哪個函數佔據的寬度最大。只要有"平頂"(plateaus),就表示該函數可能存在效能問題。

顏色沒有特殊含義,因為火焰圖表示的是 CPU 的繁忙程度,所以一般選擇暖色調

火焰圖總覽

從總覽圖中可以看出,有三個入口函數佔用百分比較大,下面分別看一下

火焰區域性圖1

這部分火焰圖可以看出,springfox在啟動過程做了很多初始化,佔了大量時間,對於不需要該功能的專案,可以直接下掉

火焰區域性圖2

瞭解下spring bean 的初始化過程

從這個圖中可以看出,bean的建立過程也佔了很多時間

火焰區域性圖3

從這個圖中可以看出,註冊BeanPostProcessor也耗費了大量時間

應用未載入的jar包(Jar瘦身)

這一個觀察項可以蒐集到專案啟動完之後,沒有用到的Jar包

實施與優化效果

操作步驟和設定項

安裝Spring Startup Analyzer

手動安裝

  1. 點選realease下載最新版tar.gz包

  2. 新建資料夾,並解壓

linux/mac系統可以考慮使用以下命令:

mkdir -p ${HOME}/spring-startup-analyzercd 下載路徑
tar -zxvf spring-startup-analyzer.tar.gz -C 安裝路徑/spring-startup-analyzer



指令碼安裝(linux/mac)

curl -sS https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/main/bin/install.sh | sh



指令碼預設安裝路徑:$HOME/spring-startup-analyzer

應用啟動

spring-startup-analyzer是以agent的方式啟動的,所以在啟動命令中新增引數-javaagent:安裝路徑/spring-startup-analyzer/lib/spring-profiler-agent.jar即可。

  • 以java命令列的方式啟動應用,則在命令列中新增引數,例如:
java -javaagent:/Users/runner/spring-startup-analyzer/lib/spring-profiler-agent.jar \
    -Dproject.name=mac-demo \
    -Dspring-startup-analyzer.admin.http.server.port=8066 \
    -jar /Users/runner/spring-startup-analyzer/ARK.jar



  • IDEA中啟動,則需要在VM options選項中新增:

紀錄檔檔案路徑:安裝路徑/spring-startup-analyzer/logs

  • startup.log: 啟動過程中的紀錄檔
  • transform.log: 被re-transform的類/方法資訊

應用啟動完成後會在console和startup.log檔案中輸出======= spring-startup-analyzer finished, click http://localhost:xxxx to visit details. ======,可以通過此輸出來判斷採集是否完成。

啟動時間和效能改善情況

優化之前

預發平均啟動10分鐘,本地無法啟動,每次需求需要提交到預發環境驗證,開發和發版週期比較長,且預發環境連線的生產庫,不能隨便造數。專案參照585個jar,其中有337個jar沒用到。

慢bean分析

分析可以看到,耗時排名前面的介面都是jsf相關的載入,還有一個es相關的bean。

功能路徑:Details of Method Invoke --> AbstractAutowireCapableBeanFactory.createBean

jsf啟動優化

注:index=「註冊中心地址」中的「註冊中心地址「做了匿名,在具體場景檢視自己程式碼中的設定

jsf的生產者的註冊中心在啟動的時候,會拉取一批ip,不斷嘗試註冊jsf,在辦公環境這些ip無法存取,導致啟動過程一直重試

    <!-- 預發、生產的註冊中心 -->
    <jsf:registry id="jsfRegistry" protocol="jsfRegistry" index="註冊中心地址"/>



在本機host裡面增加jsf釋出地址的host設定,下面*...* 在使用的時候替換成自己的,可以 ping test.註冊中心地址 獲取。「註冊中心地址」 替換成上面index後面的地址

*.*.*.* 註冊中心地址 



再次啟動專案,時長來到185s

開啟Bean懶載入

將ES的Bean初始化進行懶載入,以及開啟全域性懶載入,時長來到131s;

全域性懶載入:

1、根據spring版本的不同,開啟全域性懶載入的方式可能會不相同

2、不建議生產環境開啟全域性懶載入,因為基本上我們的服務都是部署在k8s上的,有可能服務在伸縮的時候,在存取量大的時候,由於懶載入的設定,服務快速啟動成功了,會返回給docker容器服務已經準備就緒狀態,導致k8s把流量分給該服務,導致預想不到的問題。

Jar瘦身

對於應用未使用的jar包,可以謹慎剔除,在剔除的時候一個一個下,每下一個都要重複編譯和啟動驗證是否會對專案造成影響,這是一個持續和長期的過程,Jar瘦身不僅對啟動時長有收益,而且對編譯提效很明顯,減少了大量的Jar複製過程

最終效果

做完上述優化之後:

  • 本地能夠啟動和debug專案,這對開發人員來說有極大的提效。
  • 預發使用該方案進行優化之後,能夠縮短專案編譯以及釋出的時間,對於快速驗證和迭代需求有極大提效。
  • 整體啟動效率提升70%~80%。
  • 在intel晶片電腦,啟動速度在2min11s。
  • 在m1晶片的電腦,速度會更快,大概啟動時間在90s左右。
  • 使用該思路,可以優化大部分spring以及spring boot專案,建議定期做一輪這種排查和優化。

優化關鍵點和方法

  • 去除未使用的jar包:定位未使用的jar包。通過分析和整理專案依賴,可以將這些未使用的jar包從應用中移除,減少編譯、啟動時間和資源消耗。
  • 優化慢速的Bean初始化:找到啟動耗時較長的Bean。可以考慮對這些介面和Bean進行優化,例如使用延遲載入或非同步載入的方式,以減少啟動時的耗時。
  • 取消不需要的釋出:對於本地開發環境而言,如果不需要釋出jsf介面,可以在本地取消這部分的釋出,以節省啟動時間。
  • 開啟全域性懶載入:通過開啟全域性懶載入,可以延遲載入一些不必要的元件和資源,從而減少啟動時間。確保在需要使用時才進行載入。
  • 拆分大型元件:定位載入時間較長的元件,可以考慮將其拆分成多個元件,並在啟動時只載入需要的部分。這樣可以減少啟動時的載入時間和資源消耗。
  • 使用效能分析工具:結合之前提到的效能分析工具,如Spring Startup Analyzer、Java Profiler、VisualVM等,對應用進行效能分析。通過監測和分析應用的效能資料,可以找到效能瓶頸,並針對性地進行優化。
  • 定期進行程式碼優化和重構:定期審查和優化程式碼,識別和消除潛在的效能問題。使用優化的演演算法和資料結構,減少不必要的計算和迴圈,優化資料庫查詢等,以提高應用的效能。
  • 使用快取機制:合理地使用快取來減少對資料庫或其他資源的頻繁存取。通過快取常用資料或計算結果,可以顯著提升應用的響應速度和效能。
  • 並行化處理:如果有一些獨立的任務可以並行處理,可以考慮使用多執行緒或非同步機制來提高處理速度和效率。

資訊補充

oracle jdk8下載地址

https://www.oracle.com/java/technologies/downloads/

oracle登入賬號

請聯絡作者提供免費賬號

本地redis安裝

https://redis.io/docs/install/install-redis/install-redis-on-windows/

spring-startup-analyzer啟動分析工具

https://github.com/linyimin0812/spring-startup-analyzer/blob/main/README_ZH.md

作者:京東健康 樑燦

來源:京東雲開發者社群 轉載請註明來源