SpringBoot Admin2.0 集成 Java 診斷神器 Arthas 實踐

簡介: 項目最初使用 Arthas 主要有兩個目的: 1. 經過 arthas 解決實現測試環境、性能測試環境以及生產環境性能問題分析工具的問題。 2. 經過使用 jad、mc、redefine 功能組合實現生產環境部分節點代碼熱更新的能力。
image.png前端

做者 | sparrow
來源 | 阿里巴巴雲原生公衆號vue

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

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

經過 arthas 解決實現測試環境、性能測試環境以及生產環境性能問題分析工具的問題。
經過使用 jad、mc、redefine 功能組合實現生產環境部分節點代碼熱更新的能力。
技術選型相關
由於公司還未能創建起較爲統一的生產微服務配置以及狀態管理的能力,各自系統的研發運維較爲獨立。如今項目使用了 Spring Cloud 以及 Eureka 的框架結構,和 SBA 的基礎支撐能力較爲匹配,同時,SBA 已經能夠提供服務感知,日誌級別配置管理,以及基於 actuator 的 JVM、Spring 容器的衆多管理插件,能夠知足基礎使用的需求。github

在調研期間,Arthas 總體版本爲 3.4.5,提供了基於 Webconsole 的 Tunner Server 模式,經過前面連接文章已經實踐,與SBA已經能夠實現集成。由於項目自己沒有歷史包袱,在實際集成的過程當中採用了 SBA 2.0 版本以提供更多的管理功能和圖形界面能力。其餘優勢:web

web console 界面嵌入 SBA 總體密碼登陸和網頁權限管理,實現登錄 SBA 後才能夠使用相關 arthas web console 的功能。
基於SBA 客戶端依賴的 jolokia-core 開放目標服務進程的 jmx 管理,經過實現 jmx 接口複用 SBA 的相關操做界面,減小前端界面開發能力的要求。
總體結構
image.pngspring

幾個關鍵點,使用 JVM 內置 Arthas Spring Boot 插件,參考工商銀行的模式創建完善的客戶端下載以及修改腳本實現遠程控制。內置方案工做開發量小,只須要集成相關的開源組件便可實現相關的遠程使用的模式併兼顧安全。工銀的方案大而全適合總體架構規劃後配置專有研發團隊之城。內置方案同時包含經過 JMX 的啓停操做(基於 3.4.5 的 Spring Boot 插件沒法得到相關句柄,暫時沒法實現),默認不啓動。經過遠程 JMX 開通後,JVM 新增相關線程 8 個,新增虛擬機內存 30MB 左右,和本文參考的 SBA1.0 方案相同,須要考慮在線開啓前 JVM 內存是否能夠支持。bootstrap

實現效果
SBA 2.0 最大的方便就是提供了配置化連接外部網頁的能力,同時若是網頁實如今當前 JVM 進程,能夠實現 Spring-Security 的本地權限管理,在生產環境下只有在登陸 SBA 後才能使用相關集成的 arthas 功能。tomcat

登陸界面
image.png安全

外嵌鏈接位置
image.png

JMX 的使用
image.png

image.png

跳轉 arthas web console
image.png

改造方案
參考原文 -SpringBoot Admin 集成 Arthas 實踐中實現的幾個步驟。

  1. 總體工程結構
    image.png

總體工程修改自 SBA 開源項目的 example 工程,具體使用 custom-ui 的工程連接爲:[_[spring-boot-admin-sample-custom-ui]_](https://github.com/codecentri... 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 對應便可。

  1. 外部連接配置
    SBA 2.0 開始已經使用 vue 全家桶了,擴展集成均比較方便。其中,官方文檔給出了外嵌鏈接的配置方式:[_[Linking / Embedding External Pages]_](https://codecentric.github.io...

參考 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[]
  1. 對應 Spring MVC controller 實現
    參考引用原實現的 SBA 集成部分,該部分主要修改實現以下功能:

實現 tunnel server 已經加載實例列表的刷新並展現到前段 AgentID 框供選擇點擊連接。
實現自定義 IP 地址的刷新(解決生產環境雙生產 IP 和運維段 IP 不一致的問題)。

  1. Arthas Spring Boot 插件修改和配置
    參考引用原實現的 SBA 集成中插件修改以及客戶端配置 application.yml。

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

修改主要是經過修改這部分實現經過配置文件默認不啓動,而後使用時經過遠程啓動相關 agent 線程。

  1. 基於 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 個動手實驗:
image.png

Arthas 實驗預覽
原文連接本文爲阿里雲原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索