Arthas Trace/Watch 命令:线上方法调用链和参数的非侵入式追踪

各位朋友,大家好!今天我们来深入探讨一下 Arthas 中两个非常强大的命令:tracewatch。它们能够帮助我们在不修改代码、不重启应用的情况下,对线上运行的 Java 方法进行调用链追踪和参数/返回值/异常的实时监控,从而快速定位问题、分析性能瓶颈。

1. Arthas 简介与安装

Arthas 是一款由阿里巴巴开源的 Java 诊断工具,具备强大的在线诊断功能,包括线程分析、内存分析、类加载分析、热更新、方法调用追踪等等。它通过 Java Agent 技术实现,对应用没有任何侵入性,可以安全地用于线上环境。

首先,我们需要安装 Arthas。最简单的方式是使用官方提供的脚本:

curl -L https://arthas.aliyun.com/install.sh | sh

安装完成后,进入 Arthas 的安装目录,执行 as.sh 脚本启动 Arthas。

./as.sh

Arthas 会自动检测当前机器上运行的 Java 进程,并让你选择需要 attach 的进程。选择目标进程后,就可以使用 Arthas 的各种命令了。

2. Trace 命令:追踪方法调用链

trace 命令用于追踪指定方法的调用链,并输出每个节点的耗时。这对于分析方法调用链路和性能瓶颈非常有帮助。

2.1 基本用法

trace 命令的基本语法如下:

trace classname methodname

例如,要追踪 com.example.UserService.getUserById 方法的调用链,可以执行:

trace com.example.UserService getUserById

执行后,Arthas 会拦截对该方法的调用,并输出调用链信息。

2.2 详细输出

trace 命令的输出信息非常详细,包括:

  • Time: 调用发生的时间。
  • Thread: 执行调用的线程 ID。
  • cost(ms): 方法调用的耗时,单位是毫秒。
  • class@method: 调用链上的方法。

示例输出:

Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 10 ms.
`---ts=2023-10-27 10:00:00;thread_name=http-nio-8080-exec-1;id=21;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebAppContextClassLoader@12345678
    `---[0.001ms] com.example.UserService.getUserById()
        `---[0.001ms] com.example.UserDao.selectById()
            `---[0.001ms] org.apache.ibatis.session.SqlSession.selectOne()
            `---[0.001ms] org.apache.ibatis.executor.BaseExecutor.query()
            `---[0.001ms] org.apache.ibatis.executor.SimpleExecutor.doQuery()
            `---[0.001ms] com.mysql.cj.jdbc.JdbcConnection.prepareStatement()
            `---[0.001ms] com.mysql.cj.jdbc.JdbcPreparedStatement.executeQuery()
            `---[0.001ms] com.mysql.cj.jdbc.JdbcResultSet.next()

通过这个输出,我们可以清晰地看到 getUserById 方法的调用链,以及每个方法的耗时。

2.3 条件表达式

trace 命令支持使用条件表达式进行过滤,只追踪满足特定条件的调用。条件表达式使用 ognl 语法。

例如,只追踪 userId 为 123 的 getUserById 方法调用:

trace com.example.UserService getUserById '#params[0] == 123'

其中,#params[0] 表示方法的第一个参数。

2.4 选项

trace 命令提供了一些选项来定制输出:

  • -n <count>: 指定追踪的次数。 例如 -n 10 表示追踪 10 次后停止。
  • --skipJDKMethod: 跳过 JDK 方法的追踪,可以简化输出。
  • --exclude-class-pattern <pattern>: 排除指定类的追踪,可以使用通配符。
  • --deep: 追踪所有方法的调用,包括 private 方法。

例如,追踪 10 次 getUserById 方法的调用,并跳过 JDK 方法:

trace -n 10 --skipJDKMethod com.example.UserService getUserById
2.5 性能影响

trace 命令会对性能产生一定的影响,因为它需要在运行时拦截方法调用。 因此,在线上环境使用 trace 命令时,需要谨慎评估其对应用性能的影响。 建议:

  • 尽量使用条件表达式进行过滤,减少追踪的范围。
  • 设置追踪次数,避免长时间追踪。
  • 在非高峰时段进行追踪。

3. Watch 命令:实时监控方法参数/返回值/异常

watch 命令用于实时监控指定方法的参数、返回值和抛出的异常。这对于调试问题、了解方法执行状态非常有帮助。

3.1 基本用法

watch 命令的基本语法如下:

watch classname methodname {params|returnObj|throwExp}

例如,要监控 com.example.UserService.createUser 方法的参数,可以执行:

watch com.example.UserService createUser params

执行后,Arthas 会拦截对该方法的调用,并输出参数值。

3.2 监控对象

watch 命令可以监控以下对象:

  • params: 方法的参数。
  • returnObj: 方法的返回值。
  • throwExp: 方法抛出的异常。

例如,要监控 createUser 方法的返回值:

watch com.example.UserService createUser returnObj

要监控 createUser 方法抛出的异常:

watch com.example.UserService createUser throwExp
3.3 表达式

watch 命令支持使用 ognl 表达式来访问监控对象中的属性。

例如,要监控 createUser 方法的第一个参数的 name 属性:

watch com.example.UserService createUser '#params[0].name'
3.4 条件表达式

watch 命令也支持使用条件表达式进行过滤,只监控满足特定条件的调用。

例如,只监控 name 为 "arthas" 的 createUser 方法调用:

watch com.example.UserService createUser params '#params[0].name == "arthas"'
3.5 选项

watch 命令也提供了一些选项来定制输出:

  • -n <count>: 指定监控的次数。
  • -x <level>: 指定输出的深度,用于控制对象的展开程度。 默认为 1。 适用于复杂对象。

例如,监控 10 次 createUser 方法的参数,并展开到 2 级深度:

watch -n 10 -x 2 com.example.UserService createUser params
3.6 最佳实践

watch 命令的最佳实践包括:

  • 缩小监控范围: 使用条件表达式缩小监控范围,避免输出过多的信息。
  • 控制输出深度: 使用 -x 选项控制输出深度,避免输出过于复杂的信息。
  • 监控关键对象: 只监控对问题定位有帮助的关键对象,避免浪费资源。

4. 实际案例分析

接下来,我们通过几个实际案例来演示 tracewatch 命令的使用。

4.1 案例 1:分析接口响应慢

假设线上一个接口响应很慢,我们需要分析其原因。

首先,使用 trace 命令追踪该接口对应的方法调用链:

trace com.example.controller.UserController getUserInfo

通过 trace 命令的输出,我们发现 com.example.service.UserService.getUserById 方法耗时较长。

然后,继续使用 trace 命令追踪 getUserById 方法的调用链:

trace com.example.service.UserService getUserById

通过 trace 命令的输出,我们发现 com.example.dao.UserDao.selectById 方法执行了 SQL 查询,并且耗时较长。

最后,我们判断可能是数据库查询导致接口响应慢。 可以通过优化 SQL 语句、增加索引等方式来解决问题。

4.2 案例 2:调试用户注册失败

假设用户注册失败,我们需要调试其原因。

首先,使用 watch 命令监控 com.example.service.UserService.registerUser 方法的参数:

watch com.example.service.UserService registerUser params

通过 watch 命令的输出,我们发现用户输入的密码为空。

然后,我们修改前端代码,强制用户输入密码,再次尝试注册。

注册成功后,我们判断是前端校验缺失导致用户注册失败。

4.3 案例 3:排查空指针异常

假设线上出现空指针异常,我们需要排查其原因。

首先,通过日志或异常报告,找到抛出空指针异常的代码位置。 假设是 com.example.service.OrderService.processOrder 方法中。

然后,使用 watch 命令监控 processOrder 方法的参数和返回值:

watch com.example.service.OrderService processOrder '{params, returnObj}' -x 2

通过 watch 命令的输出,我们发现某个参数的值为 null,导致了空指针异常。

最后,我们修复代码,避免 null 值的出现,解决了空指针异常。

5. 注意事项与局限性

  • 性能影响: tracewatch 命令会对性能产生一定的影响,尤其是在高并发场景下。 因此,在线上环境使用时,需要谨慎评估其对应用性能的影响。
  • 安全问题: tracewatch 命令可以访问敏感信息,例如密码、密钥等。 因此,需要做好权限管理,避免未经授权的访问。
  • 适用范围: tracewatch 命令主要适用于分析 Java 方法的调用链和参数/返回值/异常。 对于复杂的系统,可能需要结合其他工具进行分析。
  • 代码混淆: 如果代码经过混淆,tracewatch 命令可能无法正常工作。

6. 用好工具,提升效率

tracewatch 是 Arthas 中非常强大的命令,可以帮助我们快速定位问题、分析性能瓶颈。 掌握这两个命令的使用方法,可以显著提升线上问题排查效率。 当然,要结合实际情况,合理使用这两个命令,避免对线上应用产生不必要的影响。

7. 工具的强大,需要理解与运用

Arthas 的 tracewatch 命令是定位线上问题的利器,掌握它们的基本用法和高级特性,能够帮助我们更高效地进行问题排查和性能分析。结合实际案例,可以更好地理解这两个命令的应用场景和最佳实践。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐