天天看點

SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐

項目最初使用 Arthas 主要有兩個目的:

1. 通過 arthas 解決實作測試環境、性能測試環境以及生産環境性能問題分析工具的問題。

2. 通過使用 jad、mc、redefine 功能組合實作生産環境部分節點代碼熱更新的能力。

SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐

作者 | sparrow

來源 | 阿裡巴巴雲原生公衆号

本文來自 Arthas 2021 年 3 月征文投稿,4 月有獎征文參與方式可見文末。

  1. 通過 arthas 解決實作測試環境、性能測試環境以及生産環境性能問題分析工具的問題。
  2. 通過使用 jad、mc、redefine 功能組合實作生産環境部分節點代碼熱更新的能力。

技術選型相關

因為公司還未能建立起較為統一的生産微服務配置以及狀态管理的能力,各自系統的研發運維較為獨立。現在項目使用了 Spring Cloud 以及 Eureka 的架構結構,和 SBA 的基礎支撐能力較為比對,同時,SBA 已經可以提供服務感覺,日志級别配置管理,以及基于 actuator 的 JVM、Spring 容器的衆多管理插件,可以滿足基礎使用的需求。

在調研期間,Arthas 整體版本為 3.4.5,提供了基于 Webconsole 的 Tunner Server 模式,通過前面連結文章已經實踐,與SBA已經可以實作內建。因為項目本身沒有曆史包袱,在實際內建的過程中采用了 SBA 2.0 版本以提供更多的管理功能和圖形界面能力。其他優點:

  • web console 界面嵌入 SBA 整體密碼登入和網頁權限管理,實作登陸 SBA 後才可以使用相關 arthas web console 的功能。
  • 基于SBA 用戶端依賴的 jolokia-core 開放目标服務程序的 jmx 管理,通過實作 jmx 接口複用 SBA 的相關操作界面,減少前端界面開發能力的要求。

整體結構

SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐

幾個關鍵點,使用 JVM 内置 Arthas Spring Boot 插件,參考工商銀行的模式建立完善的用戶端下載下傳以及修改腳本實作遠端控制。内置方案工作開發量小,隻需要內建相關的開源元件即可實作相關的遠端使用的模式并兼顧安全。工銀的方案大而全适合整體架構規劃後配置專有研發團隊之城。内置方案同時包含通過 JMX 的啟停操作(基于 3.4.5 的 Spring Boot 插件無法獲得相關句柄,暫時無法實作),預設不啟動。通過遠端 JMX 開通後,JVM 新增相關線程 8 個,新增虛拟機記憶體 30MB 左右,和本文參考的 SBA1.0 方案相同,需要考慮線上開啟前 JVM 記憶體是否可以支援。

實作效果

SBA 2.0 最大的友善就是提供了配置化連結外部網頁的能力,同時如果網頁實作在目前 JVM 程序,可以實作 Spring-Security 的本地權限管理,在生産環境下隻有在登入 SBA 後才能使用相關內建的 arthas 功能。

  • 登入界面
SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐
  • 外嵌連接配接位置
SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐
  • JMX 的使用
SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐
SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐
  • 跳轉 arthas web console
SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐

改造方案

參考原文 -SpringBoot Admin 內建 Arthas 實踐中實作的幾個步驟。

1. 整體工程結構

SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐

整體工程修改自 SBA 開源項目的 example 工程,具體使用 custom-ui 的工程連結為:[spring-boot-admin-sample-custom-ui]_,_紅色框的部分是 arthas web console 的全部靜态檔案,通過 Maven Resource 的指定配置打入指定目錄,實作 SBA 啟動時的自定義加載。maven resource 配置--下:

<resource>
                <directory>static</directory>
                <targetPath>${project.build.directory}/classes/META-INF/spring-boot-admin-server-ui/extensions/arthas
                </targetPath>
                <filtering>false</filtering>
            </resource>
           

最終建構的 jar 中 META-INFO 中包含相關的檔案即可在 SBA 自帶的 tomcat 啟動後加載到相關的靜态資源,最後的 url 和自定義實作的 arthas console 配置的外部 URL 對應即可。

2. 外部連結配置

SBA 2.0 開始已經使用 vue 全家桶了,擴充內建均比較友善。其中,官方文檔給出了外嵌連接配接的配置方式:[Linking / Embedding External Pages]。

參考 sba example 工程的 application.yml 配置即可:

# tag::customization-external-views[]
    spring:
      boot:
        admin:
          ui:
            external-views:
              - label: "Arthas Console"
                url: http://21.129.49.153:8080/
                order: 1900
    # end::customization-external-views[]
           

3. 對應 Spring MVC controller 實作

參考引用原實作的 SBA 內建部分,該部分主要修改實作如下功能:

  • 實作 tunnel server 已經加載執行個體清單的重新整理并展示到前段 AgentID 框供選擇點選連結。
  • 實作自定義 IP 位址的重新整理(解決生産環境雙生産 IP 和運維段 IP 不一緻的問題)。

4. Arthas Spring Boot 插件修改和配置

參考引用原實作的 SBA 內建中插件修改以及用戶端配置 application.yml。

對原版 Spring boot 插件修改主要在于原有插件是通過 Spring的@ConditionalOnMissingBean 實作自動加載。

修改主要是通過修改這部分實作通過配置檔案預設不啟動,然後使用時通過遠端啟動相關 agent 線程。

5. 基于 Spring Actuator 的 JMX 實作

SBA client 在 maven 引入中會預設引入 jolokia-core.jar,如果沒有因為 SBA client 依賴可以自行引入該包,可以實作通過 actuator 開放基于 http 的 jmx 操作能力和 SBA 控制台的相關功能無縫配合。

application.yml 中開放 management 相關配置,根據自身環境情況,也可以開在用戶端側開啟 Spring security 認證,SBA 也可以很好的支援通過服務發現實作密碼保護 actuator 端點的通路。

#放開management
    management:
      endpoints:
        web:
          exposure:
            # 這裡用* 代表暴露所有端點隻是為了觀察效果,實際中按照需進行端點暴露
            include: "*"
            exclude: env
      endpoint:
        health:
          # 詳細資訊顯示給所有使用者。
          show-details: ALWAYS
      health:
        status:
          http-mapping:
            # 自定義健康檢查傳回狀态碼對應的 http 狀态碼
            FATAL:  503
           

JMX 實作參考原文中 EnvironmentChangeListener 的實作思路,基于 Spring 的 JMX 注解實作即可。

@Component
   @ManagedResource(objectName = "com.ArthasAgentManageMbean:name=ArthasMbean", description = "Arthas遠端管理Mbean")
   public class ArthasMbeanImpl {
   
       @Autowired
       private Map<String, String> arthasConfigMap;
   
       @Autowired
       private ArthasProperties arthasProperties;
   
       @Autowired
       private ApplicationContext applicationContext;
   
       /**
        * 初始化
        *
        * @return
        */
       private ArthasAgent arthasAgentInit() {
           arthasConfigMap = StringUtils.removeDashKey(arthasConfigMap);
           // 給配置全加上字首
           Map<String, String> mapWithPrefix = new HashMap<String, String>(arthasConfigMap.size());
           for (Map.Entry<String, String> entry : arthasConfigMap.entrySet()) {
               mapWithPrefix.put("arthas." + entry.getKey(), entry.getValue());
           }
           final ArthasAgent arthasAgent = new ArthasAgent(mapWithPrefix, arthasProperties.getHome(),
                   arthasProperties.isSlientInit(), null);
           arthasAgent.init();
           return arthasAgent;
       }
   
       @ManagedOperation(description = "擷取配置Arthas Tunnel Server位址")
       public String getArthasTunnelServerUrl() {
           return arthasProperties.getTunnelServer();
       }
   
       @ManagedOperation(description = "設定Arthas Tunnel Server位址,重新attach後生效")
       @ManagedOperationParameter(name = "tunnelServer", description = "example:ws://127.0.0.1:7777/ws")
       public Boolean setArthasTunnelServerUrl(String tunnelServer) {
           if (tunnelServer == null || tunnelServer.trim().equals("") || tunnelServer.indexOf("ws://") < 0) {
               return false;
           }
           arthasProperties.setTunnelServer(tunnelServer);
           return true;
       }
   
       @ManagedOperation(description = "擷取AgentID")
       public String getAgentId() {
           return arthasProperties.getAgentId();
       }
   
       @ManagedOperation(description = "擷取應用名稱")
       public String getAppName() {
           return arthasProperties.getAppName();
       }
   
       @ManagedOperation(description = "擷取ArthasConfigMap")
       public HashMap<String, String> getArthasConfigMap() {
           return (HashMap) arthasConfigMap;
       }
   
       @ManagedOperation(description = "傳回是否已經加載Arthas agent")
       public Boolean isArthasAttched() {
           DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
           String bean = "arthasAgent";
           if (defaultListableBeanFactory.containsBean(bean)) {
               return true;
           }
           return false;
       }
   
       @ManagedOperation(description = "啟動Arthas agent")
       public Boolean startArthasAgent() {
           DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
           String bean = "arthasAgent";
           if (defaultListableBeanFactory.containsBean(bean)) {
               ((ArthasAgent) defaultListableBeanFactory.getBean(bean)).init();
               return true;
           }
           defaultListableBeanFactory.registerSingleton(bean, arthasAgentInit());
           return true;
       }
   
       @ManagedOperation(description = "關閉Arthas agent,暫未實作")
       public Boolean stopArthasAgent() {
           // TODO 無法擷取自定義tmp檔案夾加載的classLoader,是以無法擷取到com.taobao.arthas.core.server.ArthasBootstrap類并調用destroy方法
           DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
           String bean = "arthasAgent";
           if (defaultListableBeanFactory.containsBean(bean)) {
               defaultListableBeanFactory.destroySingleton(bean);
               return true;
           } else {
               return false;
           }
       }
   }
           

實際使用

管理工程投産後,多次在生産環境用于問題排查和代碼熱修複。性能問題主要用于性能流控元件以及灰階釋出相關配置參數的線上驗證和 debug。

代碼熱加載相關初期通過 jad+mc 的方式進行操作,後續發現 jad 在部分代碼上因環境配置以及 jvm 問題産生反編譯代碼不一緻的情況,後續通過 maven 打包部署應用程式 source 壓縮包的方式解決,直接使用和應用 jar 同版本建構的 source 進行修改更加可靠。整體方案在管理較為嚴格的生産環境提供了有效的性能分析以及熱修複的能力。

遺留問題

現有官方提供的 com.taobao.arthas.agent.attach.ArthasAgent 中啟動 arthas agent 的用戶端使用的 arthasClassLoader 和 bootstrapClass 均為方法内的臨時變量,外部無法擷取相關句柄實作通過 bootstrapClass 關閉 arthas agent 的功能;臨時解決方案為通過 JMX 啟動後,在 web console 連接配接使用後,使用 stop 指令實作目标程序中 arthas agent 的關閉。

現有位元組碼加載工具可以很好的實作内部類,私有類的線上熱部署替換,同時經測試可以相容 SkyWalk8.x 版本的 javaagent 插件,但是在測試環境因為配置有 jacoco 覆寫度采集插件與 Arthas 位元組碼産生了不相容的情況,在部分環境使用時需要先關閉對應的 agent 後才能正常使用 arthas 的相關功能。

歡迎登陸 start.aliyun.com 知行動手實驗室體驗 Arthas 57 個動手實驗:

SpringBoot Admin2.0 內建 Java 診斷神器 Arthas 實踐

Arthas 實驗預覽

第 9 期 Arthas 有獎征文活動将于 4 月 1 日 - 4 月 30 日舉辦,點選檢視征文詳情。