概述

生产环境中的服务可能会出现各种问题,但总不能让服务下线来专门排查错误,这时候最好有一些手段来获取程序运行时信息,比如 接口方法参数/返回值、外部调用情况 以及 函数执行时间等信息以便定位问题。传统的日志记录方式的确可以,但有时非常麻烦,甚至可能需要重启服务,因此代价太大,这时可以借助一个牛批的工具:BTrace

BTrace 可用于动态跟踪正在运行的 Java程序,其原理是通过动态地检测目标应用程序的类并注入跟踪代码 ( “字节码跟踪” ),因此可以直接用于监控和追踪线上问题而无需修改业务代码并重启应用程序。

BTrace 的使用方式是用户自己编写符合 BTrace使用语法的脚本,并结合btrace命令,来获取应用的一切调用信息,就像下面这样:


<btrace>/bin/btrace <PID> <trace_script>
  • 其中 <PID>为被监控 Java应用的 进程ID
  • <trace_script> 为 根据需要监控的信息 而自行编写的 Java脚本

本文就来实操一波 BTrace工具的使用,实验环境如下:

注: 本文首发于 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


&lt;dependency&gt;
&lt;groupId&gt;com.sun.btrace&lt;/groupId&gt;
&lt;artifactId&gt;btrace&lt;/artifactId&gt;
&lt;version&gt;1.3.11.3&lt;/version&gt;
&lt;/dependency&gt;

Talk is cheap ,Show you the code !接下来就用四五个实验来说明一切吧:


0x01 监控方法耗时情况

btrace 脚本:


@BTrace
public class BtraceTest2 { @OnMethod(clazz = &quot;cn.codesheep.springbt_brace.controller.UserController&quot;, method = &quot;getUsersByName&quot;, location = @Location(Kind.RETURN))
public static void getFuncRunTime( @ProbeMethodName String pmn, @Duration long duration) {
println( &quot;接口 &quot; + pmn + strcat(&quot;的执行时间(ms)为: &quot;, 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 = &quot;cn.codesheep.springbt_brace.controller.UserController&quot;,
method = &quot;getUsersByName&quot;,
location = @Location(Kind.ENTRY)
)
public static void getFuncEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user ) { println(&quot;类名: &quot; + pcn);
println(&quot;方法名: &quot; + pmn); // 先打印入参实体整体信息
BTraceUtils.print(&quot;入参实体为: &quot;);
BTraceUtils.printFields(user); // 再打印入参实体每个属性的信息
Field oneFiled = BTraceUtils.field(&quot;cn.codesheep.springbt_brace.entity.User&quot;, &quot;userName&quot;);
println(&quot;userName字段为: &quot; + BTraceUtils.get(oneFiled, user)); oneFiled = BTraceUtils.field(&quot;cn.codesheep.springbt_brace.entity.User&quot;, &quot;userAge&quot;);
println(&quot;userAge字段为: &quot; + 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 = &quot;cn.codesheep.springbt_brace.controller.UserController&quot;,
method = &quot;getUsersByName&quot;,
location = @Location(Kind.RETURN) //函数返回的时候执行,如果不填,则在函数开始的时候执行
)
public static void getFuncReturn( @Return List&lt;User&gt; users ) {
println(&quot;返回值为: &quot;);
println(str(users));
}

运行 btrace命令后,继续请求想要被监控的业务接口,则可以得到类似如下的输出:


0x03 监控代码是否到达了某类的某一行

btrace 脚本如下:


@BTrace
public class BtraceTest3 { @OnMethod(
clazz=&quot;cn.codesheep.springbt_brace.service.UserService&quot;,
method=&quot;getUsersByName&quot;,
location=@Location(value= Kind.LINE, line=28) // 比如拦截第28行, 28行是从数据库取数据操作
)
public static void lineTest( @ProbeClassName String pcn, @ProbeMethodName String pmn, int line ) {
BTraceUtils.println(&quot;ClassName: &quot; + pcn);
BTraceUtils.println(&quot;MethodName: &quot; + pmn);
BTraceUtils.println(&quot;执行到的line行数: &quot; + line);
}
}

执行 btrace追踪命令


/home/btrace/bin/btrace 28927 BtraceTest3.java

接着用 POSTMAN工具连续发出了对 /getuserbyname接口的 十几次POST请求,由于只有第一次请求没有缓存时才会从数据库读,因此也才会执行到 UserService类的第 28行 !


0x04 监控指定函数中所有外部调用的耗时情况

btrace脚本如下:


@BTrace
public class BtraceTest5 { @OnMethod (clazz = &quot;cn.codesheep.springbt_brace.service.UserService&quot;,method = &quot;getUsersByName&quot;,
location=@Location(value= Kind.CALL, clazz=&quot;/.*/&quot;, method=&quot;/.*/&quot;, where = Where.AFTER) )
public static void printMethodRunTime(@Self Object self,@TargetInstance Object instance,@TargetMethodOrField String method, @Duration long duration) { if( duration &gt; 5000000 ){ //如果外部调用耗时大于 5ms 则打印出来
println( &quot;self: &quot; + self );
println( &quot;instance: &quot; + instance );
println( method + &quot;,cost:&quot; + duration/1000000 + &quot; ms&quot; );
}
} }

执行监控命令:


/home/btrace/bin/btrace 28927 BtraceTest5.java

然后再对接口 /getuserbyname发出POST请求,观察监控结果如下:

我们发现最耗时的外部调用来源于 MyBatis调用。


0x05 其他追踪与监控

除了上面四种典型的追踪场景之外,其他的 btrace追踪与监控场景还比如 查看谁调用了System.gc(),调用栈如何,则可以使用如下 btrace脚本进行监控


@BTrace
public class BtraceTest {
@OnMethod(clazz = &quot;java.lang.System&quot;, method = &quot;gc&quot;)
public static void onSystemGC() {
println(&quot;entered System.gc()&quot;);
jstack();
}
}

很明显,因为btrace 内置了一系列诸如 jstack等十分有用的监控命令。

当然最后需要说明的是 btrace内置了很多语法和命令,可以应对很多线上 Java应用监控场景,大家可以去研究一下官方文档


后记

由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!



原文链接:https://my.oschina.net/hansonwang99/blog/3002504

利用神器BTrace 追踪线上 Spring Boot应用运行时信息的更多相关文章

  1. 将Spring Boot项目运行在Docker上

    将Spring Boot项目运行在Docker上 一.使用Dockerfile构建Docker镜像 1.1Dockerfile常用指令 1.1.1ADD复制文件 1.1.2ARG设置构建参数 1.1. ...

  2. Spring Boot 整合mybatis时遇到的mapper接口不能注入的问题

    现实情况是这样的,因为在练习spring boot整合mybatis,所以自己新建了个项目做测试,可是在idea里面mapper接口注入报错,后来百度查询了下,把idea的注入等级设置为了warnin ...

  3. 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean

    让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...

  4. BTrace : Java 线上问题排查神器

    BTrace 是什么 BTrace 是检查和解决线上的问题的杀器,BTrace 可以通过编写脚本的方式,获取程序执行过程中的一切信息,并且,注意了,不用重启服务,是的,不用重启服务.写好脚本,直接用命 ...

  5. Intellij Idea上Spring Boot编译报错:Error:(3, 32) java: 程序包org.springframework.boot不存在

    很尴尬,为了使用Spring Boot的Initializr,特意下了个Intellij Idea,刚按提示新建一个Spring Boot的Maven项目后,就出现红叉叉了.因为IDE是新的,开始是M ...

  6. 利用Chrome浏览器调试线上代码

    前言 之前调试前端bug都是在开发环境中做完并多次测试没有问题之后发布测试环境,验收合格之后发布生产.但生产环境偏偏会有和开发和测试环境不一致的情况,例如测试环境需要加密,而开发环境先不加密,测试环境 ...

  7. 如何利用docker 构建golang线上部署环境

    公司最近开发了一个项目是用golang 写的,现在要部署到线上环境去,又不想在服务器上装单独的golang,决定用docker 封装下,直接打到镜像里面,然后就直接在hub.docker.com上面搜 ...

  8. XCode5环境下利用crash log调试线上Crash的流程

    1.前言 本文主要介绍在XCode5环境下,如何根据App自己生成的crashlog来调试真机上运行时产生的crash问题. 2. 步骤 (1)构造一段会crash的代码,并放到viewDidLoad ...

  9. Spring Boot在开发时实现热部署(开发时修改文件保存后自动重启应用)(spring-boot-devtools)

    热部署是什么 大家都知道在项目开发过程中,常常会改动页面数据或者修改数据结构,为了显示改动效果,往往需要重启应用查看改变效果,其实就是重新编译生成了新的Class文件,这个文件里记录着和代码等对应的各 ...

随机推荐

  1. Js 之获取对象key值

    var date = Object.keys(data); Object.keys( ) 会返回一个数组,数组中是这个对象的key值列表 所以只要Object.keys(a)[0], 就可以得只包含一 ...

  2. 史上最详细的C语言和Python的选择排序算法

    未经同意,请勿转载!如有收货,请留一赞,不胜感激! 同时欢迎加入我们的qq交流群:326079727 话不多说上代码: C语言: //选择排序走起 //原理:吃透原理再去实现,选择排序也是类似于冒泡排 ...

  3. golang list使用 双层 循环 删除 遍历

    queue队列: import ( "container/list" "sync" ) type Queue struct { l *list.List m s ...

  4. 多线程循环打印ABC

    主要是利用线程的wait()和notify()来实现 public class MyThread implements Runnable { private String name; private ...

  5. java -cp 用法介绍

    java -cp 和 -classpath 一样,是指定类运行所依赖其他类的路径,通常是类库,jar包之类,需要全路径到jar包,window上分号“;” 分隔,linux上是分号“:”分隔.不支持通 ...

  6. express利用nodemailer发送邮件(163邮箱)

    Nodemailer 是一个简单易用的Node.js邮件发送组件 首先安装这个组件 npm install nodemailer --save 安装之后,可以在某个get请求下,发送邮件,具体路由代码 ...

  7. Java同步数据结构之DelayQueue/DelayedWorkQueue

    前言 前面介绍了优先级队列PriorityBlockingQueue,顺带也说了一下PriorityQueue,两者的实现方式是一模一样的,都是采用基于数组的平衡二叉堆实现,不论入队的顺序怎么样,ta ...

  8. InsetDrawable

    表示把一个Drawable嵌入到另外一个Drawable的内部,并且在内部留一些间距, 类似与Drawable的padding属性,但padding表示的是Drawable的内容与Drawable本身 ...

  9. 无法登录到Windows云服务器怎么办?

    当您的云服务器无法远程登录时,我们首先建议您使用VNC方式登录. 是否可以通过控制台远程登录 远程登录失败时,请首先尝试能否通过管理控制台,使用VNC方式登录弹性云服务器. 登录管理控制台. 选择“计 ...

  10. Excel导入工具类

    项目需要从Excel导入数据,然后插入到数据库对应表中.设计了一个导入工具类,导入数据和导入结果如下图示: poi jar版本采用的3.15 导入工具类实现如下: package com.alphaj ...