乐趣区

关于java:线上项目出BUG没法调试推荐这款阿里开源的诊断神器

SpringBoot 实战电商我的项目 mall(35k+star)地址:https://github.com/macrozheng/mall

摘要

线上我的项目遇到问题无奈调试,线下又无奈重现,难道只能加日志再从新公布么?有了这款神器,既能够线上调试,又能够实现热修复,举荐给大家!

Arthas 简介

Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者青睐。它采纳命令行交互模式,同时提供丰盛的 Tab 主动补全性能,进一步不便进行问题的定位和诊断。

装置

为了还原一个实在的线上环境,咱们将通过 Arthas 来对 Docker 容器中的 Java 程序进行诊断。

  • 应用arthas-boot,下载对应 jar 包,下载地址:https://alibaba.github.io/art…
  • 将咱们的 Spring Boot 利用 mall-tiny-arthas 应用 Docker 容器的形式启动起来,打包和运行脚本在我的项目的 src\main\docker 目录下;
  • arthas-boot.jar 拷贝到咱们利用容器的 \ 目录下;
docker container cp arthas-boot.jar mall-tiny-arthas:/
  • 进入容器并启动arthas-boot,间接当做 jar 包启动即可;
docker exec -it mall-tiny-arthas /bin/bash
java -jar arthas-boot.jar
  • 启动胜利后,抉择以后须要诊断的 Java 程序的序列号,这里是1,就能够开始诊断了;

  • 期间会下载一些所需的文件,实现后控制台打印信息如下,至此 Arthas 就装置启动实现了。

常用命令

咱们先来介绍一些 Arthas 的常用命令,会结合实际利用来解说,带大家理解下 Arthas 的应用。

dashboard

应用 dashboard 命令能够显示以后零碎的实时数据面板,包含线程信息、JVM 内存信息及 JVM 运行时参数。

thread

查看以后线程信息,查看线程的堆栈,能够找出以后最占 CPU 的线程。

常用命令:

# 打印以后最忙的 3 个线程的堆栈信息
thread -n 3
# 查看 ID 为 1 都线程的堆栈信息
thread 1
# 找出以后阻塞其余线程的线程
thread -b
# 查看指定状态的线程
thread -state WAITING

sysprop

查看以后 JVM 的零碎属性,比方当容器时区与宿主机不统一时,能够应用如下命令查看时区信息。

sysprop |grep timezone
user.timezone                  Asia/Shanghai

sysenv

查看 JVM 的环境属性,比方查看下咱们以后启用的是什么环境的 Spring Boot 配置。

logger

应用 logger 命令能够查看日志信息,并扭转日志级别,这个命令十分有用。

比方咱们在生产环境上个别是不会打印 DEBUG 级别的日志的,当咱们在线上排查问题时能够长期开启 DEBUG 级别的日志,帮忙咱们排查问题,上面介绍下如何操作。

  • 咱们的利用默认应用的是 INFO 级别的日志,应用 logger 命令能够查看;

  • 应用如下命令扭转日志级别为 DEBUG,须要应用-c 参数指定类加载器的 HASH 值;
logger -c 21b8d17c --name ROOT --level debug
  • 再应用 logger 命令查看,发现 ROOT 级别日志曾经更改;

  • 应用 docker logs -f mall-tiny-arthas 命令查看容器日志,发现曾经打印了 DEBUG 级别的日志;

  • 查看完日志当前记得要把日志级别再调回 INFO 级别。
logger -c 21b8d17c --name ROOT --level info

sc

查看 JVM 已加载的类信息,Search-Class的简写,搜寻出所有曾经加载到 JVM 中的类信息。

  • 搜寻 com.macro.mall 包下所有的类;
sc com.macro.mall.*

  • 打印类的详细信息,退出 -d 参数并指定全限定类名;
sc -d com.macro.mall.tiny.common.api.CommonResult

  • 打印出类的 Field 信息,应用 -f 参数。
sc -d -f com.macro.mall.tiny.common.api.CommonResult

sm

查看已加载类的办法信息,Search-Method的简写,搜寻出所有曾经加载的类的办法信息。

  • 查看类中的所有办法;
sm com.macro.mall.tiny.common.api.CommonResult

  • 查看指定办法信息,应用 -d 参数并指定办法名称;
sm -d com.macro.mall.tiny.common.api.CommonResult getCode

jad

反编译已加载类的源码,感觉线上代码和预期不统一,能够反编译看看。

  • 查看启动类的相干信息,默认会带有 ClassLoader 信息;
jad com.macro.mall.tiny.MallTinyApplication

  • 应用 --source-only 参数能够只打印类信息。
jad --source-only com.macro.mall.tiny.MallTinyApplication

mc

内存编译器,Memory Compiler的缩写,编译 .java 文件生成.class

redefine

加载内部的 .class 文件,笼罩掉 JVM 中曾经加载的类。

monitor

实时监控办法执行信息,能够查看办法执行胜利此时、失败次数、均匀耗时等信息。

monitor -c 5 com.macro.mall.tiny.controller.PmsBrandController listBrand

watch

办法执行数据观测,能够察看办法执行过程中的参数和返回值。

应用如下命令察看办法执行参数和返回值,-x示意后果属性遍历深度。

watch com.macro.mall.tiny.service.impl.PmsBrandServiceImpl listBrand "{params,returnObj}" -x 2

热更新

只管在线上环境热更代码并不是一个很好的行为,但有的时候咱们真的很须要热更代码。上面介绍下如何应用 jad/mc/redefine 来热更新代码。

  • 首先咱们有一个商品详情的接口,当咱们传入 id<=0 时,会抛出IllegalArgumentException
/**
 * 品牌治理 Controller
 * Created by macro on 2019/4/19.
 */
@Api(tags = "PmsBrandController", description = "商品品牌治理")
@Controller
@RequestMapping("/brand")
public class PmsBrandController {
    @Autowired
    private PmsBrandService brandService;

    private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);

    @ApiOperation("获取指定 id 的品牌详情")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {if(id<=0){throw new IllegalArgumentException("id not excepted id:"+id);
        }
        return CommonResult.success(brandService.getBrand(id));
    }
}
  • 调用接口会返回如下信息,调用地址:http://192.168.5.94:8088/brand/0
{
  "timestamp": "2020-06-12T06:20:20.951+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "id not excepted id:0",
  "path": "/brand/0"
}
  • 咱们想对该问题进行修复,如果传入 id<=0 时,间接返回空数据的CommonResult,代码批改内容如下;
/**
 * 品牌治理 Controller
 * Created by macro on 2019/4/19.
 */
@Api(tags = "PmsBrandController", description = "商品品牌治理")
@Controller
@RequestMapping("/brand")
public class PmsBrandController {
    @Autowired
    private PmsBrandService brandService;

    private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);
    
    @ApiOperation("获取指定 id 的品牌详情")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {if(id<=0){//            throw new IllegalArgumentException("id not excepted id:"+id);
            return CommonResult.success(null);
        }
        return CommonResult.success(brandService.getBrand(id));
    }
}
  • 首先咱们须要对 PmsBrandController 类代码进行批改,接着上传到服务器,而后应用如下命令将 java 文件拷贝到容器的 /tmp 目录下;
docker container cp /tmp/PmsBrandController.java mall-tiny-arthas:/tmp/
  • 之后咱们须要查看该类的类加载器的 Hash 值;
sc -d *PmsBrandController | grep classLoaderHash

  • 之后应用内存编译器把改 .java 文件编译成 .class 文件,留神须要应用 -c 指定类加载器;
mc -c 21b8d17c /tmp/PmsBrandController.java -d /tmp

  • 最初应用 redefine 命令加载 .class 文件,将原来加载的类笼罩掉;
redefine -c 21b8d17c /tmp/com/macro/mall/tiny/controller/PmsBrandController.class

  • 咱们再次调用接口进行测试,发现曾经返回了预期的后果,调用地址:http://192.168.3.101:8088/brand/0
{
  "code": 200,
  "message": "操作胜利",
  "data": null
}

参考资料

官网文档:https://alibaba.github.io/art…

我的项目源码地址

https://github.com/macrozheng…

公众号

mall 我的项目全套学习教程连载中,关注公众号 第一工夫获取。

退出移动版