这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战
背景
最近正式环境出现一起事故,业务端经过一些列的业务逻辑之后,使用wkhtmltopdf
工具进行html
转换为pdf
,但服务器地址出现生成完成之后(正常结束,并未发生异常)业务端再次获取这个pdf
文件时,出现文件不存在问题。
排查
经过一系列的排查,最终锁定http://192.168.1.1登录在wkhtmltopdf
工具是否正常生成pdf
文件,请看如下代码,这里通过wkhtmltopdf
提供的工具类pdf.saveAsDirect(ftpDir + "/" + path);
进行pdf
的生成,但并未对返回的结果进行判断,所以猜测这里根本没有生成pdf
文件。
private boolean uploadToPdfDirect(WrapperConfig config, String sourceString, String path) {
try {
Pdf pdf = new Pdf(config);
pdf.setAllowMissingAssets();
pdf.addPageFromString(sourceString);
pdf.addParam(pageInfo);
pdf.saveAsDirect(ftpDir + "/" + path);
} catch (IOException | InterruptedException e) {
log.error("wkHtmlToPdf发生异常:{}", e.getMessage());
// 捕获到InterruptedException异常后恢复中断状态
Thread.currentThread().interrupt();
throw new RuntimeException("wkHtmlToPdf发生异常:" + e.getMessage());
}
return true;
}
根据HTTP猜测希望将代码修jvm调优改为:(本想借助Arthas
将代码修改以下代码,但jvm调优由于线上一直产生数据,事故面积越来越大,来不及研究Arthas
的使用,只能javascript走繁琐的流程,进行发包替换正式环境的包)
private boolean uploadToPdfDirect(WrapperConfig config, String sourceString, String path) {
try {
Pdf pdf = new Pdf(config);
pdf.setAllowMissingAssets();
pdf.addPageFromString(sourceString);
pdf.addParam(pageInfo);
// 对生成的结果进行判断, 到底有没有生成对应文件
File file = pdf.saveAsDirect(ftpDir + "/" + path);
if(!file.exists()){
throw new BusinessException("文件生成失败");
}
} catch (IOException | InterruptedException e) {
log.error("wkHtmlToPdf发生异常:{}", e.getMessage());
// 捕获到InterruptedException异常后恢复中断状态
Thread.currentThread().interrupt();
throw new RuntimeException("wkHtmlToPdf发生异常:" + e.getMessage());
}
return true;
}
正题
经过漫长的发版流程等待,等到正式环境替换包已经是大半夜接口英文。经过测试,确实是pdf
文件未http协议生成(如果会使用Ar服务器操作系统银河麒麟thas
,这个时候应该就已经解决问题)httpclient。这里要说明下,到http 500目前为止只知道这种方式会生成失jvm面试题败,至于在什么情况下生成失败就不得而知了,因为开发环境、测试javascript环境、预发布环境都是正常生成。痛定思痛,决定学习一下Arthas
的使用。
1.使httpwatch用
简单的使用流程:
-
找到需要进行添加代码的类全路径后进行反编译到某个文件夹中。
jad –sou接口卡rce-only com.xxx.service.v3.xxxService >java模拟器 /tmp/xxxService.java
这里是由
.class
反编译为.java
文件,所以阅读上没有那么友好,其实刚出现问题的时候就想到使用Arthas
,但是被这个反编译出来的代码劝退了,反编译出接口卡来的代码甚至有指令重排,不太敢修改这个反编译文件。 -
修改这个反编译文件
vim /tmp/UserController.java
这里实际上是要先退出
arthas
,第一次使用的时候还以为arthas
命令行可以直接操作文件,实际上应该退出arthas
使用linux
命令的方式进行文件修改。 -
查找这个类对应的类加载器
sc -d *xxxServic服务器配置e | grep classLoadHash
classLJVMoadHa接口的作用sh 6bc26251
这里得到这个类的加载器的哈希值为
6bc26251
-
使用这个类加载器将修改后的文件编译为
.cla接口和抽象类的区别ss
文件mc -c 6bc26251 /tmp/xxxService.java -d /tmp
这时会在
/tmp
文件夹下生成一个以包结构为文件路径的.class
文件。 -
进行热更新
redefine /tmp/com/xxx/service/v3/xxxService.class
当看到提示
redefine success, size: 1
说明替换成功,就可以进行具服务器租用体的测试。
以上这种方式,出现问题的概率极大,因为要修改反编译文件,且反编译文服务器是什么件好像进行了一些指令重排,导致阅读上比较困难,实际上以上的前四步骤就是为了得到修改之后的.class
文件,那么实际上我们可以借助于idea
进行处理。
- 找到线上代码https和http的区别的标签,拉jvm性能调优取修改文件分支。
- 检出这个分支,进行特定文件的修改后,直接使用
idea
工具进行文件的编译。这时就可以通过target
文件夹获取到对应修改文件的.class
文件。 - 将这个
.class
文件上传到对应服务器。 - 直接热更新这个文件。(也就是上述的第5步)
2.javascript示例
- 在某个接口和抽象类的区别接口的服务层添加一个日志打印信息。
public RequireFileVo getFiles(ParamVo ParamVo, Integer pageNum, Integer pageSize) {
// 新增这行打印页码的日志
log.info("pageSize:{}", pageSize);
// 以下为很复杂的业务逻辑
}
-
通过
idea
对整个服务进行编译后,获取到jvm原理该文件的.class
文件。 -
上传到服务器中,由于服务是运行在
docker
中,所以上传到服务器后移动文件到服务器系统docker
与服务器内存条和普通内存条区别宿主机的挂载目录中。 -
从挂载目录移动到简单目录。(测试的时候使用的挂载目录路径比较长,就移动到简单一点的目录比如
/tmp
) -
使用
redefine
命令后看到redefine success, size: 1
表示成功 -
测试,通过
postman
调用2021-11-18 10:43:30.491 [TraceId=92aeb19c3e8ce62b,SpanId=92aeb19c3e8ce62b,ParentSpanId=] [http-nio-20065-exec-5] INFO c.b.g.b.s.v.s.xxxService-pageSize:10
总结
使http代理用arthas
对生产服务进行不停机的情况下排查问题,省去了一些繁琐的提测流程。并且arthaJavas
还提供了很多java怎么读其他的功能用于生产环境问题的排查,有内存情况服务器的作用的查看、jvm
的一些参数、接口的调用链、参数类型等等。