记一次用Arthas线上debug实战

6,748 阅读4分钟

问题描述

在线上运行的项目中遇到bug。排查中怀疑是配置文件中的bean读取没有成功。通常我的做法是加一行日志,再重新上线代码。比如这样: log.info("WxDomainProperties:{}",wxDomainProperties);

来看一下这个对象的属性到底是怎么样的。

不过这显然是一种比较低效的方式。下面介绍一种用阿里开源的arthas来实现线上debug的方式。

先看下相关的代码

被怀疑出现问题的bean就是下面这个bean,主要是用来配置域名信息。通过配置文件配置的值。在不同环境中,比如dev,local,online会有不同值。

/**
 * 微信第三方平台域名配置
 *
 * @author yanghaolei
 * @date 2019/08/07 下午4:04
 */
@Data
@Component
@ConfigurationProperties(prefix = WxDomainProperties.PREFIX)
public class WxDomainProperties {

    public static final String PREFIX = "wx.domain";

    /**
     * 请求域名
     */
    private List<String> requestDomainList = Lists.newArrayList();

    /**
     * WebSocket域名
     */
    private List<String> wsRequestDomainList = Lists.newArrayList();

    /**
     * 上传域名
     */
    private List<String> uploadDomainList = Lists.newArrayList();

    /**
     * 下载域名
     */
    private List<String> downloadDomainList = Lists.newArrayList();

    /**
     * 业务域名
     */
    private List<String> webviewDomainList = Lists.newArrayList();
}

接下来我们注入这个bean到业务代码中。

@Slf4j
@AllArgsConstructor
@Service
public class MaDomainService {
    private final WxService wxService;
    private final WxDomainProperties wxDomainProperties;

在方法setDoamin中,bean储存的值会在默认情况下被自动载入。而我遇到的bug就是发现在默认情况下,载入的值都是null。所以我怀疑这个配置读取的值有问题。

 public WxOpenMaDomainResult setDomain(MaDomainSetDTO maDomainSetDTO) {
        JSONObject requestJson = new JSONObject();
        Integer status = maDomainSetDTO.getStatus();
        String appId = maDomainSetDTO.getAppId();

        // 1 启用默认配置 --> 强制覆盖成默认列表[初始化操作]
        if (StatusEnum.TRUE.getValue().equals(status)) {
            requestJson.put("action", SET_ACTION);
            requestJson.put("requestdomain", JSONArray.parse(JSON.toJSONString(wxDomainProperties.getRequestDomainList())));
            requestJson.put("wsrequestdomain", JSONArray.parseArray(JSON.toJSONString(wxDomainProperties.getWsRequestDomainList())));
            requestJson.put("uploaddomain", JSONArray.parseArray(JSON.toJSONString(wxDomainProperties.getUploadDomainList())));
            requestJson.put("downloaddomain", JSONArray.parseArray(JSON.toJSONString(wxDomainProperties.getDownloadDomainList())));
        }
        // 2 未启用默认配置 --> 可以add/delete/get[不允许自定义set]
        else if (StatusEnum.FALSE.getValue().equals(status)) {
            //不允许自定义覆盖
            if (SET_ACTION.equals(maDomainSetDTO.getAction())) {
                return new WxOpenMaDomainResult();
            }
            requestJson.put("requestdomain", getJsonArray(maDomainSetDTO.getRequestDomainList()));
            requestJson.put("wsrequestdomain", getJsonArray(maDomainSetDTO.getWsRequestDomainList()));
            requestJson.put("uploaddomain", getJsonArray(maDomainSetDTO.getUploadDomainList()));
            requestJson.put("downloaddomain", getJsonArray(maDomainSetDTO.getDownloadDomainList()));
        } else {
            return new WxOpenMaDomainResult();
        }

        try {
            String response = wxService.getMaService(appId).post(API_MODIFY_DOMAIN, requestJson.toJSONString());
            return JSON.parseObject(response, WxOpenMaDomainResult.class);
        } catch (Exception e) {
            log.error("Error message:{},Error stackTrace:{}", e.getMessage(), e.getStackTrace());
            return new WxOpenMaDomainResult();
        }
    }

为了验证我的怀疑没有问题,当然是需要debug去检查下这个值。但是这个是线上代码,像开始说的通过加日志重启检查值是比较麻烦的事情。所以想到了用arthas来做线上debug。

Arthas 实战

网上大部分资料对于arthas的介绍都停留于怎么安装和看一些控制台的信息。这里给出官网,Arthas 入门。我主要会讲我通过arthas验证我的怀疑和最后找到原因的过程。

我这里用到的命令是watch和trace。

watch命令执行数据观测,让我们能方便的观察到指定方法的调用情况。watch的格式是: watch + 类名表达式匹配 + 方法名 + 表达式 + 条件表达式。 我这里是想观察方法setDomain在调用过程中wxDomainProperties的值是否成功由配置文件读取。所以我的表达式是:watch + 类名[com.bjyt.bange.module.wx.middleware.MaDomainService] + 方法名[setDomain] + 表达式['target.wxDomainProperties' ]。这里target表示当前对象。条件表达式往往用来指定观察点和观察时间,这里不需要所以就没填。可以看到执行的结果:

可以看到我的怀疑是错的,这个bean是有值的。所以这也是为什么加日志效率低的原因,会浪费很多时间去验证一个错的怀疑。

接下来我用到了trace命令,希望通过跟踪方法内部到底是怎么跑的。trace命令可以跟踪方法内部调用路径,并输出方法路径上的每个节点上耗时。它与stack不一样的地方是stack输出的是当前方法的被调用路径。trace的格式同样是trace + 类名 + 方法名 + 表达式。下面是执行的结果:

这里可以明显看到倒数第二行的位置抛出了异常。经过分析发现是自己的代码写错了。。。最终根据arthas的结果我们完成了一次线上debug。

最后: 关于k8s或者docker中使用arthas

arthas是需要获取当前机器运行的jvm进程才可以工作的。在实际生产环境中,我们线上的机器应该都是部署在k8s或者docker中的。也就是说这些线上的机器同样需要安装arthas。阿里的官方指导中也特别提到了这一点,关于在容器中部署arthas。

实际上,我认为比较好的方式是开发人员在自己的本地安装arthas。然后通过arthas的webConsole连接到docker上的arthas。然后通过控制台进行远程线上的debug。关于这一点也在官方的user-case中找到了印证。记录如何使用arthas进行远程访问 #442。这样就能搭建一个更高效率的开发模式。而且还能大大减少代码中的诸如log.info这样的日志代码量。再考虑到arthas的功能远远不止于此,应该说还是值得去考虑的。