问题描述
在线上运行的项目中遇到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的功能远远不止于此,应该说还是值得去考虑的。