1.收到运维同事消息,某台服务器磁盘io过高 2.使用pidstat 工具查询磁盘IO情况
# -p 指定进程号
# 4883 即进程号 紧跟 -p后面
# -t 展示进程下的线程资源占用情况
# -d 展示进程下的线程IO占用情况
# 1 每秒刷新1次
# 3 共刷新三次
[root]# pidstat -p 23218 -t -d 1 3
查询到是TID24151的线程数据异常(此处截图未包含异常线程信息)
3.使用jstack工具打印堆栈信息
# 4833 进程号
# > jstack.text 将堆栈信息打到 jstack.text 文件中
[root]# jstack 23218 > jstack.text
4.根据线程ID(24151),在jstack.text中查询对应的堆栈信息
注意 此处根据 pidstat获取的线程号是 十进制。但是 jstack打印的堆栈信息中的nid是 十六进制,因此需要做一层进制转换,24151转十六进制为5e57
判断是xxl-job TriggerCallbackThread 线程异常
"Thread-57" #151 daemon prio=5 os_prio=0 cpu=4531531.18ms elapsed=87881.21s tid=0x00007f79edd9f160 nid=0x5e57 waiting on condition [0x00007f79609aa000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep([email protected]/Native Method)
at java.lang.Thread.sleep([email protected]/Thread.java:337)
at java.util.concurrent.TimeUnit.sleep([email protected]/TimeUnit.java:446)
at com.xxl.job.core.thread.TriggerCallbackThread$2.run(TriggerCallbackThread.java:121)
at java.lang.Thread.run([email protected]/Thread.java:833
5.追踪xxl-job源码排查问题
// retry
triggerRetryCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
while(!toStop){
try {
// 出问题的方法
retryFailCallbackFile();
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
try {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, executor retry callback thread destory.");
}
});
doCallback()方法中,当 callbackRet = false 时,会写在日志目录下写callbackFile。
private void doCallback(List<HandleCallbackParam> callbackParamList){
boolean callbackRet = false;
// callback, will retry if error
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
try {
ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
if (callbackResult!=null && ReturnT.SUCCESS_CODE == callbackResult.getCode()) {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback finish.");
callbackRet = true;
break;
} else {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback fail, callbackResult:" + callbackResult);
}
} catch (Exception e) {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback error, errorMsg:" + e.getMessage());
}
}
if (!callbackRet) {
appendFailCallbackFile(callbackParamList);
}
}
然后retryFailCallbackFile(),会查找日志目录下的callbackFile,先删除,后重试doCallback()。
private void retryFailCallbackFile(){
// valid
File callbackLogPath = new File(failCallbackFilePath);
if (!callbackLogPath.exists()) {
return;
}
if (callbackLogPath.isFile()) {
callbackLogPath.delete();
}
if (!(callbackLogPath.isDirectory() && callbackLogPath.list()!=null && callbackLogPath.list().length>0)) {
return;
}
// load and clear file, retry
for (File callbaclLogFile: callbackLogPath.listFiles()) {
byte[] callbackParamList_bytes = FileUtil.readFileContent(callbaclLogFile);
// avoid empty file
if(callbackParamList_bytes == null || callbackParamList_bytes.length < 1){
callbaclLogFile.delete();
continue;
}
List<HandleCallbackParam> callbackParamList = (List<HandleCallbackParam>) JdkSerializeTool.deserialize(callbackParamList_bytes, List.class);
callbaclLogFile.delete();
doCallback(callbackParamList);
}
}
正常情况下没什么问题,但是callbackRet 一直为false时,就会一直积累callbackFile。文件越来越多,导致io异常。
6.排查故障原因
经过翻看xxl-job的文档,发现在2.3.0版本时,设置任务结束的方法变了。
java服务中用的依赖是2.3.0,而xxl-job-admin的版本是2.2.1.不兼容出现问题。
7.解决方法
(1).升级xxl-job-admin的版本
由于还有其他项目也注册在上面,没采纳
(2).降低java服务依赖到2.2.0
成功解决问题。
注意,方法入参出参数必须使用如下格式,否则xxl-job会format异常
public ReturnT<String> methodName(String param) {
...业务逻辑
return ReturnT.SUCCESS;
}