日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Boot Admin 集成诊断利器 Arthas 实践

發布時間:2025/3/20 javascript 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Boot Admin 集成诊断利器 Arthas 实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 | 阿提說說
來源|阿里巴巴云原生公眾號

前言

Arthas 是 Alibaba 開源的 Java 診斷工具,具有實時查看系統的運行狀況;查看函數調用參數、返回值和異常;在線熱更新代碼;秒解決類沖突問題;定位類加載路徑;生成熱點;通過網頁診斷線上應用。如今在各大廠都有廣泛應用,也延伸出很多產品。

這里將介紹如何將 Arthas 集成進 Spring Boot 監控平臺中。

SpringBoot Admin

為了方便,SpringBoot Admin 簡稱為 SBA(版本:1.5.x)。

1.5 版本的 SBA 如果要開發插件比較麻煩,需要下載 SBA 的源碼包,再按照 Spring-boot-admin-server-ui-hystrix的形式 Copy 一份,由于 JS 使用的是 Angular,本人嘗試了很久,雖然掌握了如何開發插件,奈何不會 Angular,遂放棄💀

版本:2.x 2.x 版本的 SBA 插件開發,官網有介紹如何開發,JS 使用 Vue,方便很多,由于我們項目還在使用 1.5,所以并沒有使用該版本,請讀者自行嘗試。

不能使用 SBA 的插件進行集成,那還有什么辦法呢?😅

SBA 集成

鄙人的辦法是將 Arthas 的相關文件直接 Copy 到 Admin 服務中,這些文件都來自 Arthas-all 項目 Tunnel-server。

admin 目錄結構

1. Arthas 目錄

該包下存放的是所有 Arthas 的 Java 文件。

  • Endpoint 包下的文件可以都注釋掉,沒多大用。
  • ArthasController 這個文件是我自己新建的,用來獲取所有注冊到 Arthas 的客戶端,這在后面是有用的。
  • 其他文件直接 Copy 過來就行。
@RequestMapping("/api/arthas") @RestController public class ArthasController {@Autowiredprivate TunnelServer tunnelServer;@RequestMapping(value = "/clients", method = RequestMethod.GET)public Set<String> getClients() {Map<String, AgentInfo> agentInfoMap = tunnelServer.getAgentInfoMap();return agentInfoMap.keySet();} }

spring-boot-admin-server-ui

該文件建在 Resources.META-INF 下,Admin 會在啟動的時候加載該目錄下的文件。

2. Resources 目錄

  • index.html 覆蓋 SBA 原來的首頁,在其中添加一個 Arthas 導航

<!DOCTYPE html> <html class="no-js"> <head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Spring Boot Admin</title><meta name="description" content=""><meta name="viewport" content="width=device-width"><link rel="shortcut icon" type="image/x-icon" href="img/favicon.png"/><link rel="stylesheet" type="text/css" href="core.css"/><link rel="stylesheet" type="text/css" href="all-modules.css"/> </head> <body> <header class="navbar header--navbar desktop-only"><div class="navbar-inner"><div class="container-fluid"><div class="spring-logo--container"><a class="spring-logo" href="#"><span></span></a></div><div class="spring-logo--container"><a class="spring-boot-logo" href="#"><span></span></a></div><ul class="nav pull-right"><!--增加Arthas導航--><li class="navbar-link ng-scope"><a class="ng-binding" href="arthas/arthas.html">Arthas</a></li><li ng-repeat="view in mainViews" class="navbar-link" ng-class="{active: $state.includes(view.state)}"><a ui-sref="{{view.state}}" ng-bind-html="view.title"></a></li></ul></div></div> </header> <div ui-view></div> <footer class="footer"><ul class="inline"><li><a href="https://codecentric.github.io/spring-boot-admin/@project.version@" target="_blank">ReferenceGuide</a></li><li>-</li><li><a href="https://github.com/codecentric/spring-boot-admin" target="_blank">Sources</a></li><li>-</li><li>Code licensed under <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License2.0</a></li></ul> </footer> <script src="dependencies.js" type="text/javascript"></script> <script type="text/javascript">sbaModules = []; </script> <script src="core.js" type="text/javascript"></script> <script src="all-modules.js" type="text/javascript"></script> <script type="text/javascript">angular.element(document).ready(function () {angular.bootstrap(document, sbaModules.slice(0), {strictDi: true});}); </script> </body> </html>
  • Arthas.html

新建頁面,用于顯示 Arthas 控制臺頁面。

這個文件中有兩個隱藏文本域,這兩個用于連接 Arthas 服務端,在頁面加載的時候會自動將 Admin 的 Url 賦值給 Ip。

<input type="hidden" id="ip" name="ip" value="127.0.0.1"> <input type="hidden" id="port" name="port" value="19898"> <!DOCTYPE html> <html class="no-js"> <head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Spring Boot Admin</title><meta name="description" content=""><meta name="viewport" content="width=device-width"><link rel="shortcut icon" type="image/x-icon" href="../img/favicon.png"/><link rel="stylesheet" type="text/css" href="../core.css"/><link rel="stylesheet" type="text/css" href="../all-modules.css"/><script src="js/jquery-3.3.1.min.js"></script><script src="js/popper-1.14.6.min.js"></script><script src="js/xterm.js"></script><script src="js/web-console.js"></script><script src="js/arthas.js"></script><link href="js/xterm.css" rel="stylesheet" /><script type="text/javascript">window.addEventListener('resize', function () {var terminalSize = getTerminalSize();ws.send(JSON.stringify({ action: 'resize', cols: terminalSize.cols, rows: terminalSize.rows }));xterm.resize(terminalSize.cols, terminalSize.rows);});</script> </head> <body> <header class="navbar header--navbar desktop-only"><div class="navbar-inner"><div class="container-fluid"><div class="spring-logo--container"><a class="spring-logo" href="#"><span></span></a></div><div class="spring-logo--container"><a class="spring-boot-logo" href="#"><span></span></a></div><ul class="nav pull-right"><li class="navbar-link ng-scope"><a class="ng-binding" href="arthas.html">Arthas</a></li><li class="navbar-link ng-scope"><a class="ng-binding" href="../">Applications</a></li><li class="navbar-link ng-scope"><a class="ng-binding" href="../#/turbine">Turbine</a></li><li class="navbar-link ng-scope"><a class="ng-binding" href="../#/events">Journal</a></li><li class="navbar-link ng-scope"><a class="ng-binding" href="../#/about">About</a></li><li class="navbar-link ng-scope"><a class="ng-binding" href="../#/logout"><i class="fa fa-2x fa-sign-out" aria-hidden="true"></i></a></li></ul></div></div> </header> <div ui-view><div class="container-fluid"><form class="form-inline"><input type="hidden" id="ip" name="ip" value="127.0.0.1"><input type="hidden" id="port" name="port" value="19898">Select Application:<select id="selectServer"></select><button class="btn" onclick="startConnect()" type="button"><i class="fa fa-connectdevelop"></i> Connect</button><button class="btn" onclick="disconnect()" type="button"><i class="fa fa-search-minus"></i> Disconnect</button><button class="btn" onclick="release()" type="button"><i class="fa fa-search-minus"></i> Release</button></form><div id="terminal-card"><div id="terminal"></div></div></div> </div> </body> </html>
  • Arthas.js 存儲頁面控制的 js
var registerApplications = null; var applications = null; $(document).ready(function () {reloadRegisterApplications();reloadApplications(); }); /*** 獲取注冊的arthas客戶端*/ function reloadRegisterApplications() {var result = reqSync("/api/arthas/clients", "get");registerApplications = result;initSelect("#selectServer", registerApplications, ""); } /*** 獲取注冊的應用*/ function reloadApplications() {applications = reqSync("/api/applications", "get");console.log(applications) } /*** 初始化下拉選擇框*/ function initSelect(uiSelect, list, key) {$(uiSelect).html('');var server;for (var i = 0; i < list.length; i++) {server = list[i].toLowerCase().split("@");if ("phantom-admin" === server[0]) continue;$(uiSelect).append("<option value=" + list[i].toLowerCase() + ">" + server[0] + "</option>");} } /*** 重置配置文件*/ function release() {var currentServer = $("#selectServer").text();for (var i = 0; i < applications.length; i++) {serverId = applications[i].id;serverName = applications[i].name.toLowerCase();console.log(serverId + "/" + serverName);if (currentServer === serverName) {var result = reqSync("/api/applications/" +serverId+ "/env/reset", "post");alert("env reset success");}} } function reqSync(url, method) {var result = null;$.ajax({url: url,type: method,async: false, //使用同步的方式,true為異步方式headers: {'Content-Type': 'application/json;charset=utf8;',},success: function (data) {// console.log(data);result = data;},error: function (data) {console.log("error");}});return result; }
  • Web-console.js

修改了連接部分代碼,參考一下。

var ws; var xterm; /**有修改**/ $(function () {var url = window.location.href;var ip = getUrlParam('ip');var port = getUrlParam('port');var agentId = getUrlParam('agentId');if (ip != '' && ip != null) {$('#ip').val(ip);} else {$('#ip').val(window.location.hostname);}if (port != '' && port != null) {$('#port').val(port);}if (agentId != '' && agentId != null) {$('#selectServer').val(agentId);}// startConnect(true); }); /** get params in url **/ function getUrlParam (name, url) {if (!url) url = window.location.href;name = name.replace(/[\[\]]/g, '\\$&');var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),results = regex.exec(url);if (!results) return null;if (!results[2]) return '';return decodeURIComponent(results[2].replace(/\+/g, ' ')); } function getCharSize () {var tempDiv = $('<div />').attr({'role': 'listitem'});var tempSpan = $('<div />').html('qwertyuiopasdfghjklzxcvbnm');tempDiv.append(tempSpan);$("html body").append(tempDiv);var size = {width: tempSpan.outerWidth() / 26,height: tempSpan.outerHeight(),left: tempDiv.outerWidth() - tempSpan.outerWidth(),top: tempDiv.outerHeight() - tempSpan.outerHeight(),};tempDiv.remove();return size; } function getWindowSize () {var e = window;var a = 'inner';if (!('innerWidth' in window )) {a = 'client';e = document.documentElement || document.body;}var terminalDiv = document.getElementById("terminal-card");var terminalDivRect = terminalDiv.getBoundingClientRect();return {width: terminalDivRect.width,height: e[a + 'Height'] - terminalDivRect.top}; } function getTerminalSize () {var charSize = getCharSize();var windowSize = getWindowSize();console.log('charsize');console.log(charSize);console.log('windowSize');console.log(windowSize);return {cols: Math.floor((windowSize.width - charSize.left) / 10),rows: Math.floor((windowSize.height - charSize.top) / 17)}; } /** init websocket **/ function initWs (ip, port, agentId) {var protocol= location.protocol === 'https:' ? 'wss://' : 'ws://';var path = protocol + ip + ':' + port + '/ws?method=connectArthas&id=' + agentId;ws = new WebSocket(path); } /** init xterm **/ function initXterm (cols, rows) {xterm = new Terminal({cols: cols,rows: rows,screenReaderMode: true,rendererType: 'canvas',convertEol: true}); } /** 有修改 begin connect **/ function startConnect (silent) {var ip = $('#ip').val();var port = $('#port').val();var agentId = $('#selectServer').val();if (ip == '' || port == '') {alert('Ip or port can not be empty');return;}if (agentId == '') {if (silent) {return;}alert('AgentId can not be empty');return;}if (ws != null) {alert('Already connected');return;}// init webSocketinitWs(ip, port, agentId);ws.onerror = function () {ws.close();ws = null;!silent && alert('Connect error');};ws.onclose = function (message) {if (message.code === 2000) {alert(message.reason);}};ws.onopen = function () {console.log('open');$('#fullSc').show();var terminalSize = getTerminalSize()console.log('terminalSize')console.log(terminalSize)// init xterminitXterm(terminalSize.cols, terminalSize.rows)ws.onmessage = function (event) {if (event.type === 'message') {var data = event.data;xterm.write(data);}};xterm.open(document.getElementById('terminal'));xterm.on('data', function (data) {ws.send(JSON.stringify({action: 'read', data: data}))});ws.send(JSON.stringify({action: 'resize', cols: terminalSize.cols, rows: terminalSize.rows}));window.setInterval(function () {if (ws != null && ws.readyState === 1) {ws.send(JSON.stringify({action: 'read', data: ""}));}}, 30000);} } function disconnect () {try {ws.close();ws.onmessage = null;ws.onclose = null;ws = null;xterm.destroy();$('#fullSc').hide();alert('Connection was closed successfully!');} catch (e) {alert('No connection, please start connect first.');} } /** full screen show **/ function xtermFullScreen () {var ele = document.getElementById('terminal-card');requestFullScreen(ele); } function requestFullScreen (element) {var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullScreen;if (requestMethod) {requestMethod.call(element);} else if (typeof window.ActiveXObject !== "undefined") {var wscript = new ActiveXObject("WScript.Shell");if (wscript !== null) {wscript.SendKeys("{F11}");}} }
  • 其他文件

    • jquery-3.3.1.min.js 新加 Js
    • copy 過來的 js
    • popper-1.14.6.min.js
    • web-console.js
    • xterm.css
    • xterm.js
  • bootstrap.yml

# arthas端口 arthas:server:port: 9898

這樣子,admin 端的配置完成了。

客戶端配置

  • 在配置中心加入配置
#arthas服務端域名 arthas.tunnel-server = ws://admin域名/ws #客戶端id,應用名@隨機值,js會截取前面的應用名 arthas.agent-id = ${spring.application.name}@${random.value} #arthas開關,可以在需要調式的時候開啟,不需要的時候關閉 spring.arthas.enabled = false
  • 需要自動 Attach 的應用中引入 Arthas-spring-boot-starter 需要對 Starter 進行部分修改,要將注冊 Arthas 的部分移除,下面是修改后的文件。

這里是將修改后的文件重新打包成 Jar 包,上傳到私服,但有些應用會有無法加載 ArthasConfigMap 的情況,可以將這兩個文件單獨放到項目的公共包中。

@EnableConfigurationProperties({ ArthasProperties.class }) public class ArthasConfiguration {private static final Logger logger = LoggerFactory.getLogger(ArthasConfiguration.class);@ConfigurationProperties(prefix = "arthas")@ConditionalOnMissingBean@Beanpublic HashMap<String, String> arthasConfigMap() {return new HashMap<String, String>();} } @ConfigurationProperties(prefix = "arthas") public class ArthasProperties {private String ip;private int telnetPort;private int httpPort;private String tunnelServer;private String agentId;/*** report executed command*/private String statUrl;/*** session timeout seconds*/private long sessionTimeout;private String home;/*** when arthas agent init error will throw exception by default.*/private boolean slientInit = false;public String getHome() {return home;}public void setHome(String home) {this.home = home;}public boolean isSlientInit() {return slientInit;}public void setSlientInit(boolean slientInit) {this.slientInit = slientInit;}public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}public int getTelnetPort() {return telnetPort;}public void setTelnetPort(int telnetPort) {this.telnetPort = telnetPort;}public int getHttpPort() {return httpPort;}public void setHttpPort(int httpPort) {this.httpPort = httpPort;}public String getTunnelServer() {return tunnelServer;}public void setTunnelServer(String tunnelServer) {this.tunnelServer = tunnelServer;}public String getAgentId() {return agentId;}public void setAgentId(String agentId) {this.agentId = agentId;}public String getStatUrl() {return statUrl;}public void setStatUrl(String statUrl) {this.statUrl = statUrl;}public long getSessionTimeout() {return sessionTimeout;}public void setSessionTimeout(long sessionTimeout) {this.sessionTimeout = sessionTimeout;} }
  • 實現開關效果

為了實現開關效果,還需要一個文件用來監聽配置文件的改變。

我這里使用的是在 SBA 中改變環境變量,對應服務監聽到變量改變,當監聽 spring.arthas.enabled 為 true 的時候,注冊 Arthas,到下面是代碼。

@Component public class EnvironmentChangeListener implements ApplicationListener<EnvironmentChangeEvent> {@Autowiredprivate Environment env;@Autowiredprivate Map<String, String> arthasConfigMap;@Autowiredprivate ArthasProperties arthasProperties;@Autowiredprivate ApplicationContext applicationContext;@Overridepublic void onApplicationEvent(EnvironmentChangeEvent event) {Set<String> keys = event.getKeys();for (String key : keys) {if ("spring.arthas.enabled".equals(key)) {if ("true".equals(env.getProperty(key))) {registerArthas();}}}}private void registerArthas() {DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();String bean = "arthasAgent";if (defaultListableBeanFactory.containsBean(bean)) {((ArthasAgent)defaultListableBeanFactory.getBean(bean)).init();return;}defaultListableBeanFactory.registerSingleton(bean, arthasAgentInit());}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;} }

結束

到此可以愉快的在 SBA 中調式應用了,看看最后的頁面。

  • 調式流程

流程如下:

  • 開啟 Arthas
  • 在 Select Application 中選擇應用
  • Connect 連接應用
  • DisConnect 斷開應用
  • Release 釋放配置文件
  • 一些缺陷:

    • 使用 jar 包的方式引入應用,具有一定的侵略性,如果 Arthas 無法啟動,會導致應用也無法啟動。
    • 如果使用 Docker,需要適當調整 JVM 內存,防止開啟 Arthas、調試的時候,內存炸了。
    • 沒有使用 SBA 插件的方式集成如上集成僅供參考,請根據自己企業的情況來集成。

    Arthas 有獎征文正在進行中!

    為了讓更多開發者開始用上 Arthas 這個 Java 診斷神器,Arthas 社區聯合 JetBrains 推出?Arthas 有獎征文活動:**聊聊這些年你和 Arthas 之間的那些事兒。**活動仍在火熱進行中,點擊即可參與,歡迎大家踴躍投稿,參與即有可能獲獎!

    總結

    以上是生活随笔為你收集整理的Spring Boot Admin 集成诊断利器 Arthas 实践的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。