利用神器BTrace 追踪线上 Spring Boot应用运行时信息
概述
生产环境中的服务可能会出现各种问题,但总不能让服务下线来专门排查错误,这时候最好有一些手段来获取程序运行时信息,比如 接口方法参数/返回值、外部调用情况 以及 函数执行时间等信息以便定位问题。传统的日志记录方式的确可以,但有时非常麻烦,甚至可能需要重启服务,因此代价太大,这时可以借助一个牛批的工具:BTrace !
BTrace 可用于动态跟踪正在运行的 Java程序,其原理是通过动态地检测目标应用程序的类并注入跟踪代码 ( “字节码跟踪” ),因此可以直接用于监控和追踪线上问题而无需修改业务代码并重启应用程序。
BTrace 的使用方式是用户自己编写符合 BTrace使用语法的脚本,并结合btrace命令,来获取应用的一切调用信息,就像下面这样:
<btrace>/bin/btrace <PID> <trace_script>
- 其中
<PID>为被监控 Java应用的 进程ID <trace_script>为 根据需要监控的信息 而自行编写的 Java脚本
本文就来实操一波 BTrace工具的使用,实验环境如下:
- OS:
CentOS 7.4 64bit - BTrace版本:
1.3.11.3 - 被追踪的 Java应用:
Spring Boot 2.1.1 应用,这里使用我的文章《Spring Boot应用缓存实践之:Ehcache加持》一文中的 Spring Boot工程
注: 本文首发于 My Personal Blog:CodeSheep·程序羊,欢迎光临 小站
BTrace 安装部署
- 下载 二进制文件并解压
这里我解压到目录:/home/btrace
- 配置系统环境变量
vim /etc/profile
BTRACE_HOME=/home/btrace
export BTRACE_HOME
export PATH=$PATH:$BTRACE_HOME/bin
- 验证 BTrace安装情况
btrace --version
编译 BTrace源码
- 克隆源码
git clone git@github.com:btraceio/btrace.git
- 编译源码
./gradlew build

- 构建完成的生成物路径位于:
build/libs目录下

我们取出构建生成的 jar包供下文使用。
利用btrace追踪 Spring Boot应用例析
首先我们得构造一个 Spring Boot的模拟业务 用于下文被追踪和分析,这里我就使用文章 《Spring Boot应用缓存实践之:Ehcache加持》中的实验工程。
我们在此工程里再添加一个 scripts包,用于放置 btrace 脚本文件:

由于 btrace脚本中需要用到 btrace相关的组件和函数库,因此我们还需要在工程的 pom.xml中引入 btrace的依赖,所使用的 jar包就是上文编译生成的 btrace-1.3.11.3.jar
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace</artifactId>
<version>1.3.11.3</version>
</dependency>
Talk is cheap ,Show you the code !接下来就用四五个实验来说明一切吧:
0x01 监控方法耗时情况
btrace 脚本:
@BTrace
public class BtraceTest2 {
@OnMethod(clazz = "cn.codesheep.springbt_brace.controller.UserController", method = "getUsersByName", location = @Location(Kind.RETURN))
public static void getFuncRunTime( @ProbeMethodName String pmn, @Duration long duration) {
println( "接口 " + pmn + strcat("的执行时间(ms)为: ", str(duration / 1000000)) ); //单位是纳秒,要转为毫秒
}
}
接下来开始运行 btrace脚本来拦截方法的参数,首先我们用 jps命令取到需要被监控的 Spring Boot应用的进程 Id为 27887,然后执行:
/home/btrace/bin/btrace 27887 BtraceTest2.java
这里我总共对 /getusersbyname接口发出了 12次 POST请求,情况如下:

接下来我们再看看利用btrace脚本监控到的 /getuserbyname接口的执行时间:

这样一对比很明显,从数据库取数据还是需要 花费十几毫秒的,但从缓存读取数据 几乎没有耗时,这就是为什么要让缓存加持于应用的原因!!!
0x02 拦截方法的 参数/返回值
btrace 脚本:
@OnMethod(
clazz = "cn.codesheep.springbt_brace.controller.UserController",
method = "getUsersByName",
location = @Location(Kind.ENTRY)
)
public static void getFuncEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user ) {
println("类名: " + pcn);
println("方法名: " + pmn);
// 先打印入参实体整体信息
BTraceUtils.print("入参实体为: ");
BTraceUtils.printFields(user);
// 再打印入参实体每个属性的信息
Field oneFiled = BTraceUtils.field("cn.codesheep.springbt_brace.entity.User", "userName");
println("userName字段为: " + BTraceUtils.get(oneFiled, user));
oneFiled = BTraceUtils.field("cn.codesheep.springbt_brace.entity.User", "userAge");
println("userAge字段为: " + BTraceUtils.get(oneFiled, user));
}
接下来开始运行 btrace脚本来拦截方法的参数,首先我们用 jps命令取到需要被监控的java应用的进程 Id为 27887,然后执行:
/home/btrace/bin/btrace -cp springbt_brace/target/classes 27887 BtraceTest4.java
此时正常带参数 {"userName":"codesheep.cn"} 去请求业务接口:POST /getusersbyname,会得到如下输出:

很明显请求参数已经被 btrace给拦截到了
同理,如果想拦截方法的返回值,可以使用如下 btrace脚本:
@OnMethod(
clazz = "cn.codesheep.springbt_brace.controller.UserController",
method = "getUsersByName",
location = @Location(Kind.RETURN) //函数返回的时候执行,如果不填,则在函数开始的时候执行
)
public static void getFuncReturn( @Return List<User> users ) {
println("返回值为: ");
println(str(users));
}
运行 btrace命令后,继续请求想要被监控的业务接口,则可以得到类似如下的输出:

0x03 监控代码是否到达了某类的某一行
btrace 脚本如下:
@BTrace
public class BtraceTest3 {
@OnMethod(
clazz="cn.codesheep.springbt_brace.service.UserService",
method="getUsersByName",
location=@Location(value= Kind.LINE, line=28) // 比如拦截第28行, 28行是从数据库取数据操作
)
public static void lineTest( @ProbeClassName String pcn, @ProbeMethodName String pmn, int line ) {
BTraceUtils.println("ClassName: " + pcn);
BTraceUtils.println("MethodName: " + pmn);
BTraceUtils.println("执行到的line行数: " + line);
}
}
执行 btrace追踪命令
/home/btrace/bin/btrace 28927 BtraceTest3.java
接着用 POSTMAN工具连续发出了对 /getuserbyname接口的 十几次POST请求,由于只有第一次请求没有缓存时才会从数据库读,因此也才会执行到 UserService类的第 28行 !
0x04 监控指定函数中所有外部调用的耗时情况
btrace脚本如下:
@BTrace
public class BtraceTest5 {
@OnMethod (clazz = "cn.codesheep.springbt_brace.service.UserService",method = "getUsersByName",
location=@Location(value= Kind.CALL, clazz="/.*/", method="/.*/", where = Where.AFTER) )
public static void printMethodRunTime(@Self Object self,@TargetInstance Object instance,@TargetMethodOrField String method, @Duration long duration) {
if( duration > 5000000 ){ //如果外部调用耗时大于 5ms 则打印出来
println( "self: " + self );
println( "instance: " + instance );
println( method + ",cost:" + duration/1000000 + " ms" );
}
}
}
执行监控命令:
/home/btrace/bin/btrace 28927 BtraceTest5.java
然后再对接口 /getuserbyname发出POST请求,观察监控结果如下:

我们发现最耗时的外部调用来源于 MyBatis调用。
0x05 其他追踪与监控
除了上面四种典型的追踪场景之外,其他的 btrace追踪与监控场景还比如 查看谁调用了System.gc(),调用栈如何,则可以使用如下 btrace脚本进行监控
@BTrace
public class BtraceTest {
@OnMethod(clazz = "java.lang.System", method = "gc")
public static void onSystemGC() {
println("entered System.gc()");
jstack();
}
}
很明显,因为btrace 内置了一系列诸如 jstack等十分有用的监控命令。
当然最后需要说明的是 btrace内置了很多语法和命令,可以应对很多线上 Java应用监控场景,大家可以去研究一下官方文档
后记
由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!
- My Personal Blog:CodeSheep 程序羊
- 程序羊的 2018年终总(gen)结(feng)
原文链接:https://my.oschina.net/hansonwang99/blog/3002504
利用神器BTrace 追踪线上 Spring Boot应用运行时信息的更多相关文章
- 将Spring Boot项目运行在Docker上
将Spring Boot项目运行在Docker上 一.使用Dockerfile构建Docker镜像 1.1Dockerfile常用指令 1.1.1ADD复制文件 1.1.2ARG设置构建参数 1.1. ...
- Spring Boot 整合mybatis时遇到的mapper接口不能注入的问题
现实情况是这样的,因为在练习spring boot整合mybatis,所以自己新建了个项目做测试,可是在idea里面mapper接口注入报错,后来百度查询了下,把idea的注入等级设置为了warnin ...
- 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean
让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...
- BTrace : Java 线上问题排查神器
BTrace 是什么 BTrace 是检查和解决线上的问题的杀器,BTrace 可以通过编写脚本的方式,获取程序执行过程中的一切信息,并且,注意了,不用重启服务,是的,不用重启服务.写好脚本,直接用命 ...
- Intellij Idea上Spring Boot编译报错:Error:(3, 32) java: 程序包org.springframework.boot不存在
很尴尬,为了使用Spring Boot的Initializr,特意下了个Intellij Idea,刚按提示新建一个Spring Boot的Maven项目后,就出现红叉叉了.因为IDE是新的,开始是M ...
- 利用Chrome浏览器调试线上代码
前言 之前调试前端bug都是在开发环境中做完并多次测试没有问题之后发布测试环境,验收合格之后发布生产.但生产环境偏偏会有和开发和测试环境不一致的情况,例如测试环境需要加密,而开发环境先不加密,测试环境 ...
- 如何利用docker 构建golang线上部署环境
公司最近开发了一个项目是用golang 写的,现在要部署到线上环境去,又不想在服务器上装单独的golang,决定用docker 封装下,直接打到镜像里面,然后就直接在hub.docker.com上面搜 ...
- XCode5环境下利用crash log调试线上Crash的流程
1.前言 本文主要介绍在XCode5环境下,如何根据App自己生成的crashlog来调试真机上运行时产生的crash问题. 2. 步骤 (1)构造一段会crash的代码,并放到viewDidLoad ...
- Spring Boot在开发时实现热部署(开发时修改文件保存后自动重启应用)(spring-boot-devtools)
热部署是什么 大家都知道在项目开发过程中,常常会改动页面数据或者修改数据结构,为了显示改动效果,往往需要重启应用查看改变效果,其实就是重新编译生成了新的Class文件,这个文件里记录着和代码等对应的各 ...
随机推荐
- linux中如何修改最大文件句柄数
1.使用ulimit -a可以查看,其中的open files后面的数就是最大文件句柄数 2.临时方法:使用ulimit -n size修改最大文件句柄数(这种方法只针对当前进程有效) 3.永久方法: ...
- Go语言 之TCP聊天室
服务端流程图如下: package main import ( "fmt" "net" ) // 客户端结构体 type Client struct { //用 ...
- 谷歌浏览器试调网页 多出font标签
突然发现一些按钮的点击功能失效,在控制台发现该a标签中多出个font标签,导致文字区域不能触发到a标签 就算a标签宽高设置百分百 也没用. 经测试不同的浏览器情况不一样 safari就不会出现这种情况 ...
- 动态连通性问题——算法union-find
问题定义:问题的输入是一列整数对,其中每个整数都表示一个某种类型的对象,一对整数p,q可以被理解为"p和q是相连的".我们假设“相连”是一种对等的关系. 这也意味着它具有: 1.自 ...
- 使用log4j使某些java类的日志信息输出到指定日志文件中
Log4j 是 Apache 的一个开放源代码项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI 组件.甚至是套接口服务器.NT 的事件记录器.UNIX Syslog ...
- Flutter移动电商实战 --(27)列表页_现有Bug修复和完善
小解决小bug 默认右侧的小类没有被加载 数据加载完成后,就list的第一个子对象传递给provide进行赋值,这样右侧的小类就刷新了数据 默认加载了第一个类别 调整颜色 对比图片调整下颜色 这里的参 ...
- LC 988. Smallest String Starting From Leaf
Given the root of a binary tree, each node has a value from 0 to 25 representing the letters 'a' to ...
- Java NIO学习笔记四 NIO选择器
Java NIO选择器 A Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备就绪,例如读取或写入.这样一个线程可以管理多个通道,从而管理多个网络连接. 为 ...
- mysql允许远程机器连接
mysql> use mysql; Reading table information for completion of table and column names You can turn ...
- IE浏览器URL中的查询条件中包含中文时报404的解决办法
情况是比如我输入如下URL到IE浏览器: http://localhost:8090/RPT_TYSH_JL_ZD_DETAIL.html?pageIndex=1&year=2018& ...