SpringBoot?Admin集成診斷利器Arthas示例實(shí)現(xiàn)
前言
Arthas 是 Alibaba開源的Java診斷工具,具有實(shí)時(shí)查看系統(tǒng)的運(yùn)行狀況,查看函數(shù)調(diào)用參數(shù)、返回值和異常,在線熱更新代碼,秒解決類沖突問題、定位類加載路徑,生成熱點(diǎn)圖,通過網(wǎng)頁診斷線上應(yīng)用。 如今在各大廠都有廣泛應(yīng)用,也延伸出很多產(chǎn)品。
這里將介紹如何將Arthas集成進(jìn)SpringBoot監(jiān)控平臺(tái)中。
SpringBoot Admin
為了方便SpringBoot Admin 簡稱為SBA
版本:1.5.x
1.5版本的SBA如果要開發(fā)插件比較麻煩,需要下載SBA的源碼包,再按照spring-boot-admin-server-ui-hystrix的形式copy一份,由于JS使用的是Angular,本人放棄了

版本:2.x
2.x版本的SBA插件開發(fā),官網(wǎng)有介紹如何開發(fā),JS使用Vue,方便很多,由于我們項(xiàng)目還在使用1.5,所以并沒有使用該版本,請(qǐng)讀者自行嘗試
不能使用SBA的插件進(jìn)行集成,那還有什么辦法呢?????
SBA 集成
鄙人的辦法是將Arthas的相關(guān)文件直接copy到admin服務(wù)中

arthas包該包下存放的是所有arthas的Java文件
- endpoint包下的文件可以都注釋掉,沒多大用
- ArthasController這個(gè)文件是我自己新建的,用來獲取所有注冊(cè)到Arthas的客戶端,這在后面是有用的
- 其他文件直接copy過來就行
@RequestMapping("/api/arthas")
@RestController
public class ArthasController {
@Autowired
private 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會(huì)在啟動(dòng)的時(shí)候加載該目錄下的文件

index.html 覆蓋SBA原來的首頁,在其中添加一個(gè)導(dǎo)航,首頁會(huì)是這樣

<!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" rel="external nofollow" />
<link rel="stylesheet" type="text/css" href="core.css" rel="external nofollow" />
<link rel="stylesheet" type="text/css" href="all-modules.css" rel="external nofollow" />
</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="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><span></span></a>
</div>
<div class="spring-logo--container">
<a class="spring-boot-logo" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><span></span></a>
</div>
<ul class="nav pull-right">
<!--增加Arthas導(dǎo)航-->
<li class="navbar-link ng-scope">
<a class="ng-binding" href="arthas/arthas.html" rel="external nofollow" >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 rel="external nofollow" target="_blank">Reference
Guide</a></li>
<li>-</li>
<li><a rel="external nofollow" target="_blank">Sources</a></li>
<li>-</li>
<li>Code licensed under <a rel="external nofollow" target="_blank">Apache License
2.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控制臺(tái)頁面
<!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" rel="external nofollow" />
<link rel="stylesheet" type="text/css" href="../core.css" rel="external nofollow" />
<link rel="stylesheet" type="text/css" href="../all-modules.css" rel="external nofollow" />
<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="external nofollow" 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="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><span></span></a>
</div>
<div class="spring-logo--container">
<a class="spring-boot-logo" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><span></span></a>
</div>
<ul class="nav pull-right">
<li class="navbar-link ng-scope">
<a class="ng-binding" href="arthas.html" rel="external nofollow" >Arthas</a>
</li>
<li class="navbar-link ng-scope">
<a class="ng-binding" href="../" rel="external nofollow" >Applications</a>
</li>
<li class="navbar-link ng-scope">
<a class="ng-binding" href="../#/turbine" rel="external nofollow" >Turbine</a>
</li>
<li class="navbar-link ng-scope">
<a class="ng-binding" href="../#/events" rel="external nofollow" >Journal</a>
</li>
<li class="navbar-link ng-scope">
<a class="ng-binding" href="../#/about" rel="external nofollow" >About</a>
</li>
<li class="navbar-link ng-scope">
<a class="ng-binding" href="../#/logout" rel="external nofollow" ><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 存儲(chǔ)頁面控制的js
var registerApplications = null;
var applications = null;
$(document).ready(function () {
reloadRegisterApplications();
reloadApplications();
});
/**
* 獲取注冊(cè)的arthas客戶端
*/
function reloadRegisterApplications() {
var result = reqSync("/api/arthas/clients", "get");
registerApplications = result;
initSelect("#selectServer", registerApplications, "");
}
/**
* 獲取注冊(cè)的應(yīng)用
*/
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;
}- 其他文件 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服務(wù)端域名
arthas.tunnel-server = ws://admin域名/ws
#客戶端id,應(yīng)用名@隨機(jī)值,js會(huì)截取前面的應(yīng)用名
arthas.agent-id = ${spring.application.name}@${random.value}
#arthas開關(guān),可以在需要調(diào)試的時(shí)候開啟,不需要的時(shí)候關(guān)閉
spring.arthas.enabled = false需要自動(dòng)Attach的應(yīng)用中引入arthas-spring-boot-starter 需要對(duì)starter進(jìn)行部分修改,要將注冊(cè)arthas的部分移除,下面是修改后的文件。
我這里是將修改后的文件重新打包成jar包,上傳到私服,但有些應(yīng)用會(huì)有無法加載arthasConfigMap的情況,可以將這兩個(gè)文件單獨(dú)放到項(xiàng)目的公共包中
@EnableConfigurationProperties({ ArthasProperties.class })
public class ArthasConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ArthasConfiguration.class);
@ConfigurationProperties(prefix = "arthas")
@ConditionalOnMissingBean
@Bean
public 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;
}
}實(shí)現(xiàn)開關(guān)效果
為了實(shí)現(xiàn)開關(guān)效果,還需要一個(gè)文件用來監(jiān)聽配置文件的改變
我這里使用的是在SBA中改變環(huán)境變量,對(duì)應(yīng)服務(wù)監(jiān)聽到變量改變,當(dāng)監(jiān)聽spring.arthas.enabled為true的時(shí)候,注冊(cè)arthas, 到下面是代碼
@Component
public class EnvironmentChangeListener implements ApplicationListener<EnvironmentChangeEvent> {
@Autowired
private Environment env;
@Autowired
private Map<String, String> arthasConfigMap;
@Autowired
private ArthasProperties arthasProperties;
@Autowired
private ApplicationContext applicationContext;
@Override
public 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;
}
}結(jié)束
到此可以愉快的在SBA中調(diào)試應(yīng)用了,看看最后的頁面

調(diào)試流程
開啟Arthas

在Select Application中選擇應(yīng)用
- Connect 連接應(yīng)用
- DisConnect 斷開應(yīng)用
- Release 釋放配置文件
這種集成方式有一些缺陷:
- 使用jar包的方式引入應(yīng)用,具有一定的侵略性,如果arthas無法啟動(dòng),會(huì)導(dǎo)致應(yīng)用也無法啟動(dòng)
- 如果使用docker,需要適當(dāng)調(diào)整JVM內(nèi)存,防止開啟arthas的時(shí)候,內(nèi)存炸了
- 沒有使用SBA插件的方式集成
- 如上集成僅供參考,還是需要根據(jù)自己企業(yè)的情況來做
以上就是SpringBoot Admin集成診斷利器Arthas示例實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Admin集成Arthas的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Win10系統(tǒng)下配置Java環(huán)境變量
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著Win10系統(tǒng)下配置Java環(huán)境變量展開,文中有非常詳細(xì)的介紹及圖文示例,需要的朋友可以參考下2021-06-06
idea中Maven鏡像源詳細(xì)配置步驟記錄(對(duì)所有項(xiàng)目)
Maven是一個(gè)能使我們的java程序開發(fā)節(jié)省時(shí)間和精力,是開發(fā)變得相對(duì)簡單,還能使開發(fā)規(guī)范化的工具,下面這篇文章主要給大家介紹了關(guān)于idea中Maven鏡像源詳細(xì)配置(對(duì)所有項(xiàng)目)的相關(guān)資料,需要的朋友可以參考下2023-05-05
Java countDownLatch如何實(shí)現(xiàn)多線程任務(wù)阻塞等待
這篇文章主要介紹了Java countDownLatch如何實(shí)現(xiàn)多線程任務(wù)阻塞等待,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
詳解Javaweb狀態(tài)管理的Session和Cookie
這篇文章主要介紹了Javaweb狀態(tài)管理的Session和Cookie,將瀏覽器與web服務(wù)器之間多次交互當(dāng)做一個(gè)整體來處理,并且多次交互所涉及的數(shù)據(jù)(狀態(tài))保存下來,需要的朋友可以參考下2023-05-05
詳解spring開發(fā)_JDBC操作MySQL數(shù)據(jù)庫
本篇文章主要介紹了spring開發(fā)_JDBC操作MySQL數(shù)據(jù)庫,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12
springboot封裝響應(yīng)實(shí)體的實(shí)例代碼
這篇文章主要介紹了springboot封裝響應(yīng)實(shí)體,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
spring boot高并發(fā)下耗時(shí)操作的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于spring boot高并發(fā)下耗時(shí)操作的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
使用maven創(chuàng)建普通項(xiàng)目命令行程序詳解
大部分使用maven創(chuàng)建的是web項(xiàng)目,這里使用maven創(chuàng)建一個(gè)命令行程序,目的是讓大家了解maven特點(diǎn)和使用方式,有需要的朋友可以借鑒參考下2021-10-10

