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;
}