基于arthas量化监控诊断java应用方法论与实践
所以量化一切监控体系之后,我们就需要搭建一个完善、安全、成熟的监控诊断体系来排查定位问题, 以笔者研发的java应用为例,主流的应用监控会通过。同时,考虑到当前应用体系下成百个服务,我们需要有一个统一的入口集中化监控管理,所以为了能够有一个统一的入口集中管理所有的agent,我们会专门用一台与外界隔离的服务器部署一个。所以,在此基础之上笔者也给出了一套基于自己常见的工具所衍生了一套带有监控诊断的监
浅谈监控的基本概念
故障的生命周期
故障的生命周期分为以下几个阶段:
- 故障开始
- 故障发现
- 故障定位
- 及时修复
- 系统恢复
这其中,故障发现和通知在市面上已经有一套相对成熟的体系参考,无论是运维还是研发总会有一套自己系统监控的理论,并在应用中完成运用实现监控和告警。 那么问题来了,从研发的角度,针对系统监控各项性能指标,如何明确定位到具体的错误?例如:
- 系统提示CPU飙升,面对宏观的性能指标,我们如何知晓故障线程?
- web请求全体夯死,系统监控全面告知线程处于
wating状态,我们如何定位到问题的根因并优化? - 程序堆内存飙升达到与之,我们如何定位到进程级别的具体问题对象从而定位到问题的栈帧?
如下图,这就是笔者面板中最常见的容器线程直线飙升导致大量请求夯死超时,按照现有体系中的grafana监控面板,面对成百上千的接口请求,即时我们能够准确的做到故障发现,也无法非常快速精准的做到故障定位:

缺少精准的定位动作,就看导致止损动作对故障恢复没有任何帮助,就需要进行重新定位,进而导致负责人员花费大量时间在故障发现和止损动作之间循环往复的执行。
笔者理想中的监控体系
监控是可以确保提前暴露被即时发现解决,同时也可以作为日常循环后系统调优的佐证。所以量化一切监控体系之后,我们就需要搭建一个完善、安全、成熟的监控诊断体系来排查定位问题, 以笔者研发的java应用为例,主流的应用监控会通过micrometer采集目标监控指标,并将其同步到prometheus并通过grafana进行增强渲染,在此基础之上通过Nightingale针对这些告急的监控及时下发企微消息或者短信告警让研发人员介入解决问题:

按照笔者上面的说法,这种做法存在如下几个问题:
- 市面仅仅指定常见系统级别监控指标,粒度在如今的应用系统中显得过于宽泛
- 团队指定指标往往会因为各种原因无法精准、明确,可快速定位检索各种异常和监控故障的应用级别监控指标
grafana和Nightingale是面向于运维和研发的通用,且着重于监控可视化和阈值告警,并不能很好的做到监控诊断。
所以,在此基础之上笔者也给出了一套基于自己常见的工具所衍生了一套带有监控诊断的监控运维体系架构,即在上述监控体系下集成强阿里系中强大的监控诊断工具arthas。
如下图,在这套监控体系下,通过arthas tunel远程统一管理所有被arthas agent代理的java应用程序,当其他监控运维工具感知异常时,我们就可以快速通过浏览器attach到存在故障的进程中,以超细粒度灵活的指令和表达式定位java程序中对象内存占用、CPU利用率甚至是栈帧的调用细节:

基于arhtas的应用远程监控实践
arthas监控诊断体系说明
基于上述的说明,我们大体了解的监控诊断的一套理想架构,而本文将针对该架构进行演示和实践,按照arthas官网的说明,对于spring boot项目,我们在项目中引入arthas-spring-boot-starter,其底层会启动一个arthas进程并attach到装配的应用上,由此构建出当前服务的arthas agent:

这一点,我们可以通过ArthasConfiguration的arthasAgent这个bean得以印证,可以看到它会拉取spring boot配置后,通过这个配置构建arthasAgent并调用init完成如下工作:
- 通过
Instrumentation注册字节码转换器即ClassFileTransformer,后续通过该技术实现类加载或者运行时字节码动态增强 - 拉取一个
artahs agent并attach到当前进程构成artahs服务端,监听客户端指令完成目标类增强:
对应笔者也给出这段源码的视线入口,读者可以自行参阅:
@ConditionalOnMissingBean @Bean public ArthasAgent arthasAgent(@Autowired Map<String, String> arthasConfigMap, @Autowired ArthasProperties arthasProperties) throws Throwable { arthasConfigMap = StringUtils.removeDashKey(arthasConfigMap); // 给配置全加上前缀 Map<String, String> mapWithPrefix = new HashMap<String, String>(arthasConfigMap.size()); for (Entry<String, String> entry : arthasConfigMap.entrySet()) { mapWithPrefix.put("arthas." + entry.getKey(), entry.getValue()); } //基于配置完成构建artahs agent,完成arthas agent服务端初始化,监听客户端指令完成字节码增强 final ArthasAgent arthasAgent = new ArthasAgent(mapWithPrefix, arthasProperties.getHome(), arthasProperties.isSlientInit(), null); arthasAgent.init(); logger.info("Arthas agent start success."); return arthasAgent; }
同时,考虑到当前应用体系下成百个服务,我们需要有一个统一的入口集中化监控管理,所以为了能够有一个统一的入口集中管理所有的agent,我们会专门用一台与外界隔离的服务器部署一个Arthas Tunnel,让所有的应用程序生成agent后统一与tunel建立连接,后续我们就可以通过tunel暴露的端口,统一管理监控诊断存在异常风险的应用程序:

项目中集成arthas
对于需要arthas agent的程序,我们首先需要引入arthas 脚手架依赖包:
<dependency> <groupId>com.taobao.arthas</groupId> <artifactId>arthas-spring-boot-starter</artifactId> <version>3.4.1</version> </dependency>
然后配置agent唯一id并指定tunel的注册地址(默认暴露端口为7777):
arthas.agent-id=hsehdfsfghhwertyfad arthas.tunnel-server=ws://127.0.0.1:7777/ws
随后我们到官网下载并启动tunel
java -jar arthas-tunnel-server-4.1.2-fatjar.jar
此时我们就可以启动我们的java程序了,控制台出现如下提示,则说明服务启动并成功注册到arthas了:
2025-12-07 22:09:21.370 INFO 68696 --- [ main] c.a.arthas.spring.ArthasConfiguration : Arthas agent start success. 2025-12-07 22:09:21.483 INFO 68696 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 18080 (http)
此时键入http://127.0.0.1:8080/即可进入tunel面板,键入刚刚的agentid即可直接进入监控面板:

这里补充说明一下感兴趣的读者可以到官网拉取arhtas的源码包,以笔者为例这里选择arhtas-all-3.6.0版本,并在配置文件中关闭redis监控,避免本机没有redis服务端连接导致自动装配阶段报错:
management.health.redis.enabled=false
通过运行ArthasTunnelApplication将tunel启动:

arthas tunel系统架构监控实践
回到最早的问题,面对大量飙升处于wating状态的线程因为监控工具和指标的局限性而发做到快速的监控诊断,有了arthas监控诊断体系架构,笔者用下面这样的一个接口模拟一个存在问题的分页查询并演示一下快速的解决步骤:
@GetMapping("/slow-request") public JSONObject handleUserPageQuery() { // 调用service的用户分页列表查询方法 return userService.getUserPageList(); }
查看UserService的getUserPageList,可以看到其内部用休眠模拟分页查询的长耗时:
public JSONObject getUserPageList() { log.info("开始查询用户分页列表"); // 模拟耗时的用户分页列表查询,休眠1分钟 ThreadUtil.sleep(1, TimeUnit.DAYS); // 构造分页列表结果 JSONObject result = new JSONObject(); result.set("code", 200); result.set("message", "success"); JSONObject data = new JSONObject(); data.set("total", 100); data.set("pageSize", 10); data.set("currentPage", 1); // 模拟用户列表数据 JSONObject user1 = new JSONObject(); user1.set("id", 1); user1.set("name", "张三"); user1.set("email", "zhangsan@example.com"); JSONObject user2 = new JSONObject(); user2.set("id", 2); user2.set("name", "李四"); user2.set("email", "lisi@example.com"); data.set("users", new JSONObject[]{user1, user2}); result.set("data", data); log.info("用户分页列表查询完成"); return result; }
通过上文的tunel入口,我们很快的进入到程序内部,简单快速的定位的tomcat那些存在time wating的问题线程:

非常简单干脆的定位到了问题线程的栈帧,很好的完成监控体系中的监控诊断这一步:

更多推荐



所有评论(0)