Apache Dubbo是Alibaba开源的高性能RPC框架,在国内有非常多的用户。
Arthas是Alibaba开源的应用诊断利器,9月份开源以来,Github Star数三个月超过6000。
- Github: https://github.com/alibaba/arthas
- 文档:https://alibaba.github.io/arthas/
- Arthas开源交流QQ群: 916328269
- Arthas开源交流钉钉群: 21965291
当Dubbo遇上Arthas,会碰撞出什么样的火花呢?下面来分享Arthas排查Dubbo问题的一些经验。
dubbo-arthas-demo
下面的排查分享基于这个dubbo-arthas-demo
,非常简单的一个应用,浏览器请求从Spring MVC到Dubbo Client,再发送到Dubbo Server。
Demo里有两个spring boot应用,可以先启动server-demo
,再启动client-demo
。
1 | /user/{id} -> UserService -> UserServiceImpl |
Client端:
1 |
|
Server端:
1 | "1.0.0") (version = |
Arthas快速开始
1 | $ wget https://alibaba.github.io/arthas/arthas-boot.jar |
启动后,会列出所有的java进程,选择1,然后回车,就会连接上ServerDemoApplication
1 | $ java -jar arthas-boot.jar |
Dubbo线上服务抛出异常,怎么获取调用参数?
当线上服务抛出异常时,最着急的是什么参数导致了抛异常?
在demo里,访问 http://localhost:8080/user/0 ,UserServiceImpl
就会抛出一个异常,因为user id不合法。
在Arthas里执行 watch com.example.UserService * -e -x 2 '{params,throwExp}'
,然后再次访问,就可以看到watch命令把参数和异常都打印出来了。
1 | $ watch com.example.UserService * -e -x 2 '{params,throwExp}' |
怎样线上调试Dubbo服务代码?
在本地开发时,可能会用到热部署工具,直接改代码,不需要重启应用。但是在线上环境,有没有办法直接动态调试代码?比如增加日志。
在Arthas里,可以通过redefine
命令来达到线上不重启,动态更新代码的效果。
比如我们修改下UserServiceImpl
,用System.out
打印出具体的User
对象来:
1 | public User findUser(int id) { |
本地编绎后,把server-demo/target/classes/com/example/UserServiceImpl.class
传到线上服务器,然后用redefine
命令来更新代码:
1 | $ redefine -p /tmp/UserServiceImpl.class |
这样子更新成功之后,访问 http://localhost:8080/user/1 ,在ServerDemoApplication
的控制台里就可以看到打印出了user信息。
怎样动态修改Dubbo的logger级别?
- https://alibaba.github.io/arthas/ognl.html
- https://alibaba.github.io/arthas/sc.html
- https://commons.apache.org/proper/commons-ognl/language-guide.html
在排查问题时,需要查看到更多的信息,如果可以把logger级别修改为DEBUG
,就非常有帮助。
ognl
是apache开源的一个轻量级表达式引擎。下面通过Arthas里的ognl
命令来动态修改logger级别。
首先获取Dubbo里TraceFilter
的一个logger对象,看下它的实现类,可以发现是log4j。
1 | $ ognl '@com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter@logger.logger' |
再用sc
命令来查看具体从哪个jar包里加载的:
1 | $ sc -d org.apache.log4j.Logger |
可以看到log4j是通过slf4j代理的。
那么通过org.slf4j.LoggerFactory
获取root
logger,再修改它的level:
1 | $ ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)' |
可以看到修改之后,root
logger的level变为DEBUG
。
怎样减少测试小姐姐重复发请求的麻烦?
在平时开发时,可能需要测试小姐姐发请求过来联调,但是我们在debug时,可能不小心直接跳过去了。这样子就尴尬了,需要测试小姐姐再发请求过来。
Arthas里提供了tt
命令,可以减少这种麻烦,可以直接重放请求。
1 | $ tt -t com.example.UserServiceImpl findUser |
上面的tt -t
命令捕获到了3个请求。然后通过tt --play
可以重放请求:
1 | $ tt --play -i 1000 |
Dubbo运行时有哪些Filter? 耗时是多少?
Dubbo运行时会加载很多的Filter,那么一个请求会经过哪些Filter处理,Filter里的耗时又是多少呢?
通过Arthas的trace
命令,可以很方便地知道Filter的信息,可以看到详细的调用栈和耗时。
1 | $ trace com.alibaba.dubbo.rpc.Filter * |
Dubbo动态代理是怎样实现的?
- https://alibaba.github.io/arthas/jad.html
- com.alibaba.dubbo.common.bytecode.Wrapper
通过Arthas的jad
命令,可以看到Dubbo通过javaassist动态生成的Wrappr类的代码:
1 | $ jad com.alibaba.dubbo.common.bytecode.Wrapper1 |
获取Spring context
除了上面介绍的一些排查技巧,下面分享一个获取Spring Context,然后“为所欲为”的例子。
在Dubbo里有一个扩展com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
,把Spring Context保存到了里面。
因此,我们可以通过ognl
命令获取到。
1 | $ ognl '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, #context.getBean("userServiceImpl").findUser(1)' |
- `SpringExtensionFactory@contexts.iterator.next
获取到
SpringExtensionFactory`里保存的spring context对象 #context.getBean("userServiceImpl").findUser(1)
获取到userServiceImpl
再执行一次调用
只要充分发挥想像力,组合Arthas里的各种命令,可以发挥出神奇的效果。
总结
本篇文章来自杭州Dubbo Meetup的分享《当DUBBO遇上Arthas - 排查问题的实践》,希望对大家线上排查Dubbo问题有帮助。
分享的PDF可以在 https://github.com/alibaba/arthas/issues/327 里下载。