从 BTrace
到 Greys
,工具上的一小步,Java工程师问题诊断道路上的一大步,随着软件系统规模越来越大,拓扑结构越来越多样化,问题诊断复杂度和门槛变得越来越高,一个看似简单的问题可能会消耗掉数日的时间。截止目前,掌门在线运行着 数百+ 服务,数千+ 服务实例,在一系列多维度监控系统落地之后,诊断工具的需求变得尤为强烈,其中 Arthas@alibaba
和衍生工具 Bistoury@qunar
的研究和落地是我们实践的主要方向。
站在需求方的角度来讲, Arthas
大部分解决了功能层面的核心问题,衍生工具 Bistoury
将工具工程化解决便利性层面,包括服务和应用级别概念的覆盖、历史监控数据的落地、应用服务环境权限侵入式问题等等。大部分场景下 Arthas
足以支撑现有的诊断需求,针对掌门自身的特性,选择 Bistoury
有自身的一系列考量。
本篇分享主要从需求角度出发分享一些使用的经验,反推诊断工具能给我们带来哪些便利, Arthas
和 Bistoury
在其自身的介绍中非常详细地描述了作为诊断工具自身能够解决哪些问题。
Arthas:https://github.com/alibaba/arthas
Bistoury:https://github.com/qunarcorp/bistoury
作为较为底层的工具,门槛和安全性自始至终都是无法规避的问题,如果对场景进行归纳(内存问题、CPU问题、Bug问题、必现、偶发),形成一些面向场景需求的最佳使用案例,从问题的暴露到问题的定位,渐进式让诊断工具在业务方良好着陆。这些现实中所有案例的排查几乎都是一组命令利器的各种排列组合得到最终定位,使得我们设想通过对案例进行场景归纳来验证在线诊断工具如何给诊断工作带来便利。
交互式场景很多情况下解决了实时性需求,图形化降低了交互场景下的学习门槛,如果 Arthas
、 Bistoury
是一系列的医疗诊断器械,我们试图利用它解开那些目前没有覆盖到的谜团,甚者为我们提供应用内存信息和开发人员沟通桥梁,比如业务级别的服务信息。
接下来,本篇分别通过 压测
、 在线Debug
、 性能优化
、 Class异常问题
4个场景的案例来介绍,这些案例均来源于日常工作中。
一次压测一般由测试和开发两个角色参与,输出压测报告和性能分析数据,压测既需要监控到数据的吞吐量,同时也会对数据的准确性进行监控。
选择动态监控
对需要监控的方法加上断点
执行压测
查看监控结果
(案例来源于课堂云实时互动测试团队,业务和运营测试团队也有类似压测案例对接)
下述展示的是针对加断点动态监控的方法在历史某个时间段的执行次数和QPS数据,并可支持多个断点监控。
(图1-方法断点动态监控)
也可以在首页执行 monitor -c {x} {xxxx.class} {method} 查看x秒为单位的实时性方法情况。
(图2 - 某个类方法的执行监控命令)
如果需要关注内存问题,内存dump分析结果一目了然(可以在完成压测后执行)。
(图3 - 对象统计)
如果需要关注CPU问题,async-profiler插件可以帮助你,该采样可以得到所有类方法占用cpu的比例,根据比例大小快速定位高CPU问题。
(图4 - CPU性能分析)
说明:火焰图里,横条越长,代表使用的越多,从下到上是调用堆栈信息
(图5 - CPU火焰图)
按照上述的说明,可以看出这个采样时间段基于PrometheusHttpServer在收集JMX采样过程中消耗的CPU占了较大比例。
Arthas
交互式 Debug集成Skywalking引起的Socket服务熔断案例
(该案例来源课堂云中台,基础架构完成修复PR给SW社区,并使用 Arthas
命令复盘,感谢基础架构@曹同学供稿)
在给正常运行的服务新增 Skywalking
全链路监控时,系统提醒 Hystrix
接口发生熔断,初步判断 Skywalking
v6.4.0内部存在 bug。
Skywalking
在对 Redisson
插件进行增强拦截 RedissonClient
读写方法时,创建前置拦截器的过程。
使用 Arthas
watch 定位到对应的方法,查看该入参结构。
因 Skywalking
对 Redisson
方法进行增强,使用 Arthas
stack查看调用链上层结构
此处 peer 为 null ,导致在org.apache.skywalking.apm.agent.core.context.ContextManager#createExitSpan(java.lang.String, java.lang.String)该类中,创建了AbstractTracerContext并与thread进行了绑定。但是在org.apache.skywalking.apm.agent.core.context.AbstractTracerContext#createExitSpan过程中并未 成功创建AbstractSpan,所以在此后的 org.apache.skywalking.apm.agent.core.context.ContextManager#stopSpan()方法中也抛出异常,导致在该线程上进⾏行行ContextManager.isActive()判断为true,但是调⽤用ContextManager.capture()就发⽣生异常。
修改 SkywalkingRedissonPlugin
相应源码, Skywalking
造成的熔断问题解决。
Bistoury
图形化 DebugBistoury
将 Arthas
中多个命令组合成基于字节码增强的非阻塞式的图形化在线 debug 功能,同时也融入了其自身的反编译功能,一次 WebSocket
的连接在下面这个对象中,可以收集到的信息包括 成员变量、局部变量、执行调用链信息。
(图6 - 模糊匹配内存中加载的类)
(图7 - 断点触发显示内容)
在线 Debug 界面显示的内容中部分无法转化为可显示的内容对象会提示红色标注内容。
调用链使用 Arthas
trace命令,trace对性能影响较大,在问题排查追溯场景可以放心使用,trace目前不支持CompletableFuture相关行为调用链。
在最新的Bistoury中在线Debug和监控功能的反编译基于JetBrean IDEA的Fernflower。
日常使用的阻塞式debug属于JDPA范畴提供的jdb工具或者IDE工具。
在线代码编辑在日常开发中可以快速解决无法发版本进行新增 Log 打印、细微 bug 的修复等工作。
使用 Arthas
对 SpringCloud
低版本的 Loadbalance
算法进行 Bug 修复。
(该案例来源于AI团队,使用 Arthas
复盘复现)
现有代码在线热更原理基于 JVMInstrumentation.retransformClasses
触发,需要满足 JVM 自身的规则规范,接下来通过 Arthas
命令 jad/mc/redefine 线上热更新一条龙服务。
代码在线编辑属于高级玩法,属于 Arthas
命令控制部分(在 Bistoury
首页可以选择应用进入Shell交互界面),会与其他字节码增强的命令冲突,会话多开问题同样会在Arthas逻辑中存在一定的增强覆盖问题,按需谨慎使用。
jad --source-only com.netflix.loadbalancer.AbstractServerPredicate> /tmp/AbstractServerPredicate.java
jad获取到字节码原理:
注册一个 ClassFileTransformer
通过 Instrumentation.retransformClasses 触发回调
在回调的 transform 函数里获取到字节码
删掉注册的 ClassFileTransformer
关注下其中这个方法(ribbon-loadbalancer-2.2.4.jar)
privateint incrementAndGetModulo(int modulo) {
for(;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
//在部分场景下会存在异常
if(nextServerCyclicCounter.compareAndSet(current, next))
return current;
}
}
在更高版本中(ribbon-loadbalancer-2.3.0.jar)
privateint incrementAndGetModulo(int modulo) {
for(;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
//高版本中进行了条件补充
if(nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
找到对应的类加载器对象ID
sc -d *AbstractServerPredicate| grep classLoaderHash
classLoaderHash 1be6f5c3
redefine热更新代码
再使用redefine命令重新加载新编译好的AbstractServerPredicate.class:
$ redefine /tmp/com/netflix/loadbalancer/AbstractServerPredicate.class
redefine success, size: 1
至此,一个小的bug无需重启得到修复,问题在上述环境中得到解决(本过程根据实际案例改编)。在使用该功能的时候,笔者留意了一下抽象类的增强行为是否会批量生效。在系统运行过程中,一个抽象类会被多个实现类继承,抽象类的增强如何确保每个实现都能完成本次bug的修复。
[arthas@51366]$ sc *AbstractTest
com.xxx.AbstractTest
com.xxx.Test
com.xxx.Test1
publicabstractclassAbstractTest{
publicvoid test() {
doTest();
}
privatevoid doTest() {
int i = 1;
System.out.println(2/i);
}
}
查看 Arthas
的源码,发现 Arthas
使用的是批量增强逻辑,如果不使用批量增强,可以修改 options batch-re-transformfalse
。
//com.taobao.arthas.core.advisor.Enhancer
// 构建增强器
finalEnhancer enhancer = newEnhancer(adviceId, isTracing, skipJDKTrace, enhanceClassSet, methodNameMatcher, affect);
try{
inst.addTransformer(enhancer, true);
// 批量增强
if(GlobalOptions.isBatchReTransform) {
finalint size = enhanceClassSet.size();
finalClass<?>[] classArray = newClass<?>[size];
arraycopy(enhanceClassSet.toArray(), 0, classArray, 0, size);
if(classArray.length > 0) {
inst.retransformClasses(classArray);
logger.info("Success to batch transform classes: "+ Arrays.toString(classArray));
}
} else{
// for each 增强
for(Class<?> clazz : enhanceClassSet) {
try{
inst.retransformClasses(clazz);
logger.info("Success to transform class: "+ clazz);
} catch(Throwable t) {
logger.warn("retransform {} failed.", clazz, t);
if(t instanceofUnmodifiableClassException) {
throw(UnmodifiableClassException) t;
} elseif(t instanceofRuntimeException) {
throw(RuntimeException) t;
} else{
thrownewRuntimeException(t);
}
}
}
}
} finally{
inst.removeTransformer(enhancer);
}
部分场景下,通过 jar 包引入的代码无法通过简单的 bugfix 流程进行修复,比如 Maven
仓库中引入的第三方包,RPC场景下其他团队提供的二方包等等。在线热更给了工程师更多可以选择的空间。
注意事项:
redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),参考jdk本身的文档。
1.不允许新增加field/method
2.正在跑的函数,没有退出不能生效
Arthas在整个交互式命令会话过程中,有一些Options参数可以配置,影响范围为命令执行期间的一些可选性配置。
Class 相关类别异常问题通常发生在类加载阶段,JVM 虚拟机将类加载分成七个阶段,定义了类加载的行为顺序(不是所有 JVM 虚拟机都保持相同的顺序),使用 Spring 容器类加载时机绝大部分发生在启动时机,懒加载模式的类加载行为发生在运行时间,对于 Arthas
来讲,使用 attach pid 操作意味着难以捕捉启动时机相关问题排查时机(常见的类加载时机,启动慢时机),常用的一些操作可能包括 jdb 断点或者强制增加 main 方法的阻塞行为,给 attach 行为一个时间上的缓冲期。
Class 异常问题是日常开发过程中非常常见的问题,造成问题出现的原因可能多种多样,常见的一些异常可以使用关键字在搜索引擎获取相关建议(虽然大部分都是复制黏贴文案),建议先通过搜索尝试快速解决,这里分享一个 NoSuchMethodError
的真实案例,如何通过 Arthas
命令定位排查该问题出现的原因。
Arthas 在下一个大版本4.0+会增加启动时诊断机制,目前版本下笔者尝试了一下jdb + Arthas 的进阶玩法,有兴趣可以参考笔者公众号
SocketSide
NoSuchMethodError
在线异常追踪
(案例来源于课堂云多云部署)
打了一个包在一个环境正常运行并发布到新的环境部署时,显示 NoSuchMethodError
异常。
第一感觉这里可能存在jar包冲突问题,使用 Arthas
进行验证,首先对main方法进行简单调整。
publicstaticvoid main(String[] args) throwsIOException{
try{
SpringApplication.run(SocketApplication.class, args);
} catch(Throwable e) {
e.printStackTrace();
}
// block 保证进程不退出
System.in.read();
}
通过 jad
检查内存中被加载器加载的对象类
[arthas@3157]$ jad io.netty.handler.codec.compression.ZlibCodecFactory
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@7f424ec2
Location:
/Users/xxxx/.m2/repository/io/netty/netty-codec/4.0.26.Final/netty-codec-4.0.26.Final.jar
由此比对在线上运行的netty版本是4.1.36,jar包冲突得到验证。
定位到了冲突类的来源,我们可以显示指定 classpath jar 包的顺序,指定类加载的顺序。但这只是暂时解决问题。本质上依赖冲突的问题,还是需要深层次排除的。
另外针对冲突jar包在不同环境加载时,虚拟机加载的顺序也会有不一样的处理方式。
类似的异常可能还有:
ClassNotFoundException
NoClassDefFoundError
ClassCastException
上述案例同样可以在启动时添加JVM启动参数 +TraceClassLoading
, 通过类加载描述日志查看(对于解决jar文件冲突很有用) 。
性能优化场景可能包含程序算法优化、逻辑优化、gc调优等等,该类场景需要大量的数据进行优化前后对照,Bistoury可以提供一系列的实时和历史数据为优化提供数据支撑,因篇幅原因,这里仅列举相关可以作为数据参考的功能点。
线程分析
堆分析
CPU分析
方法级监控
Arthas
以及 Bistoury
能解决的问题远不止上述的问题,诊断工具的作用是洞察程序的动态行为。听诊器是一种简单工具,却给医生的工作带来了革命,它让内科医生能有效地监控病人的身体。对应用了解的越多,越容易解决问题。
开发人员总是可以尝试在 test/staging 环境中重现相同的问题。然而,这是很棘手的,因为有些问题无法在不同的环境中轻松重现,甚至在重新启动后消失。
如果您正在考虑向代码中添加一些日志以帮助解决问题,则必须经历以下生命周期:test、staging,然后再进入生产阶段。时间就是金钱!这种方法效率低下!此外,如上所述,一旦JVM重新启动,这个问题就可能无法重现。
Arthas
就是为了解决这些问题而建立的。开发人员可以随时解决生产问题。没有 JVM 重启,没有额外的代码更改。Arthas
是一个观察者,它永远不会暂停你现有的线程。@Arthas官方介绍
Arthas
和 Bistoury
部署和落地仅仅完成了诊断工具在掌门技术栈可行性验证,随着业务规模越来越大,多云、容器的实施,部署环境越来越多样化,掌门诊断工具(Solar-Analyzer)团队由基础架构、课堂云中台及DevOps共同发起,致力于在更加复杂的系统环境为技术团队提供诊断利器,提升定位和解决问题效率,在诊断工具的扩展性工作上还有较多需要解决的问题。扩展更多偏向于功能侧,如何进一步降低用户使用门槛,如何将交互式逻辑组合使用。如何能够通过现有功能提供一些更高级且有价值的扩展玩法,我们也期望得到或者探索更多有价值的案例需求。
未来诊断工具会向 系统化
和 智能化
两个维度展开。
工具系统化进一步提升工具安全性,降低工具使用门槛,为技术人员提供更多便利性操作。
宏
( Macro
)是一种抽象概念,UI场景下 宏
的概念非常广泛, 宏
定义的支持未来会作为核心功能给技术人员提供快捷抽象概念,将大部分重复性动作在一步解决。
自助诊断将应用中多达几百上千个性能和监控指标进一步归纳分析,从多个维度时刻监测应用的健康指标,按需输出监控报告和分析依据。
Arthas
在4.0将支持启动时配置 agent性能调优是多方向的,热点方法,CPU,内存,GC 等
纯字节码修改的方案,只能在热点方法上有作为。但是修改得越多字节码,对性能就越多影响。
Arthas
只能对特定的一些类做 trace
Arthas
& Bistoury
对应用虚拟容器和应用本身都存在一定的资源侵占和字节码增强侵入性,在部署上由应用Owner按需、按时进行启用停用。下一步可以进行一些简单的改造,每个人只能操作有权限的服务和实例,通过员工账号和服务的绑定关系。
(图8 - CD发布系统集成)
了解原理可以更好的理解和平衡诊断工具本身给应用 Owner
带来的收益,诊断过程是一个复杂的全黑盒过程,最佳使用案例往往和背后的执行逻辑有着密切的关系,这其中和应用存在直接共存关系的 Agent
层是整个诊断工具的核心大脑。
(图9 - Bistoury部署和通信架构图)
这个架构实时连通图和掌门 Socket 互动中台三层通信结构设计大同小异, Bistoury
少了一层负载寻址服务,整体架构给安全、扩展等控制提供了很好的思路,目前阶段 Bistoury
的 Proxy
、 UI
侧的逻辑实现层面还有待完善,未来会根据掌门集群规模、基础架构自身技术栈等因素进行二次改造,涉及到的内容包括注册中心、负载均衡逻辑等等。
Agent
基于类似 Sidecar
的部署模式,在需要的时候 attach 到 application 上进行通信,JVM Attach 的进程间通信使用了 os 层面的管道进行通信。
应用进行启用状态的 Agent
,并且开启了实时监测功能,会在虚拟机保存默认3天内的监控数据,可以给既往诊断过程提供数据支撑。
Bistoury
交互式诊断命令覆盖了 Arthas
和一些内置的命令,因 Arthas
内部实现原理,涉及到增强部分命令在多用户同时对应用进行操作可能会因为时序覆盖前面所执行的逻辑而得不到期望的结果, Arthas
未来版本会逐步更新至允许叠加增强效果,同时 Bistoury
也可以通过简单改造对正在执行的会话用户进行冲突告知。
Bistoury
集成了 arthas@alibaba
和 VJtools@vip
的功能,毫无例外,这些较为底层的玩法给诊断工作提供了较高的便利性,如何在降低诊断带来的副作用(多重互斥增强、阻塞式植入、 STW
、 Crash
),甚至是一次术后的风险都让诊断工具团队不断完善迭代以平衡风险。即便是最有经验的主刀医师,拿起手术刀也意味着巨大的风险,快速排查问题依赖于便利的工具,但核心因素还是人的思路,现代医疗的阶梯化诊断同样适应于计算机世界的很多场景,如何降低人的因素门槛,尽量减少有创诊断比例,通过无创、低创手段快速定位发现问题等等,未来诊断工具团队会联合业务方团队不断完善,其中课堂云实时中台在落地过程中依靠 Arthas
从问题排查、性能调优等若干场景给团队带来了极大时间上的收益,在中台的建设过程中界定业务与中台的性能和异常边界提供了有力的数据支撑,随着 Bistoury
的落地以及不断完善,诊断工具必定能成为掌门技术团队手中一把利器。
徐敏,开源爱好者,10年+互联网开发经验,现任掌门课堂云架构师,主要负责课堂云实时交互中台和4层网关架构设计开发工作。
个人邮箱:xumin.wlt@gmail.com
Github:https://github.com/xuminwlt/
公众号: SocketSide
王瑞显,开源爱好者,4年互联网开发经验,现任掌门基础架构部研发工程师,主要负责掌门全链路监控系统和应用诊断工具开发工作。
个人邮箱:ax1anrise@gmail.com
Github:https://github.com/Ax1an
---------- END ----------
加入掌门
欢迎大佬们加入掌门教育大家庭,一起畅谈技术,分享交流。在招职位有技术架构师( Web
前端/ Java
/音视频/ iOS
)、测试工程师(大数据方向)、音视频总监、大数据总监、测试经理、运维开发工程师、大数据运维工程师、算法工程师(用户增长方向)、BI工程师、逆向工程师。
投递信箱:zeying.shi@zhangmen.com 施老师。
往期好文