XXl-JOB優(yōu)雅的停機

背景

Spring boot 2.3 之后引入了優(yōu)雅的停機之后,http 請求可以優(yōu)雅的上下線,但是定時任務(wù)xxl-job,并不能優(yōu)雅的上下線,具體問題如下

問題1

服務(wù)收到關(guān)閉信號之后,xxl-job 還繼續(xù)為節(jié)點分配任務(wù),節(jié)點并沒有及時上報狀態(tài)

問題2

關(guān)閉的途中,有正在跑的job,此時關(guān)閉,比如客戶的升降級,比如客戶資產(chǎn)的快照等等,這些問題,如果不能及時的發(fā)現(xiàn),就有可能引起故障

如何解決問題

1. git clone xxl-job, 修改xxl-job-core的源碼

分析源碼

看下 com.xxl.job.core.executor.XxlJobExecutor#destroy() 的源碼


    public void destroy() {
        // destory executor-server
        stopEmbedServer(); 

        // destory jobThreadRepository
        if (jobThreadRepository.size() > 0) {
            for (Map.Entry<Integer, JobThread> item : jobThreadRepository.entrySet()) {
                JobThread oldJobThread = removeJobThread(item.getKey(), "web container destroy and kill the job.");
                // wait for job thread push result to callback queue
                if (oldJobThread != null) {
                    try {
                        oldJobThread.join();
                    } catch (InterruptedException e) {
                        logger.error(">>>>>>>>>>> xxl-job, JobThread destroy(join) error, jobId:{}", item.getKey(), e);
                    }
                }
            }
            jobThreadRepository.clear();
        }
        jobHandlerRepository.clear();


        // destory JobLogFileCleanThread
        JobLogFileCleanThread.getInstance().toStop();

        // destory TriggerCallbackThread
        TriggerCallbackThread.getInstance().toStop();

    }

其中 stopEmbedServer(), 就是停止服務(wù),取消注冊,可以滿足問題1
com.xxl.job.core.server.EmbedServer#stop

    public void stop() throws Exception {
        // destroy server thread
        if (thread!=null && thread.isAlive()) {
            thread.interrupt();
        }
        // stop registry
        stopRegistry();
        logger.info(">>>>>>>>>>> xxl-job remoting server destroy success.");
    }

其中com.xxl.job.core.executor.XxlJobExecutor#removeJobThread
是直接打斷job的,不能滿足問題2


    public static JobThread removeJobThread(int jobId, String removeOldReason){
        JobThread oldJobThread = jobThreadRepository.remove(jobId);
        if (oldJobThread != null) {
            oldJobThread.toStop(removeOldReason);
            oldJobThread.interrupt();

            return oldJobThread;
        }
        return null;
    }

修改源碼

增加一個優(yōu)雅停機xxl-job的方法gracefulDestroy

   public void gracefulDestroy() {
        logger.info("開始取消注冊job");
        stopEmbedServer();
        logger.info("等等job執(zhí)行完畢");

        // 一直等待job執(zhí)行完畢,
      //可以設(shè)置一個白名單,只對某些job,檢查是否執(zhí)行完畢,也可以設(shè)置一個最多等待時間
        while (true) {


            List<JobThread> collect = jobThreadRepository.values().stream().filter(JobThread::isRunningOrHasQueue).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(collect)) {
                break;
            }

            //打印具體的為執(zhí)行完的job
            logger.info("job:{},還未執(zhí)行完畢", collect.stream().filter(t -> t.getHandler() instanceof MethodJobHandler).map(t -> ((MethodJobHandler) t.getHandler()).toString()).collect(Collectors.joining(",")));



            // 休眠一秒
            try {
                // 也可以設(shè)置一個最多等待時間
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        logger.info("job執(zhí)行完畢!");
        // destory JobLogFileCleanThread
        JobLogFileCleanThread.getInstance().toStop();

        // destory TriggerCallbackThread
        TriggerCallbackThread.getInstance().toStop();
    }

節(jié)點修改

修改配置文件application.yaml

將shutdown方式改為graceful
關(guān)閉超時最大改為2分鐘,具體根據(jù)情況而定,注意超過此時間,就會立即關(guān)閉spring容器

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 120s

關(guān)閉時調(diào)用gracefulDestroy()

首先我們查看下Bean的生命周期銷毀

image.png

從圖中可見,我們不能通過指定 @Bean(destroyMethod = "gracefulDestroy") 方法,
因為此時可能MethodJobHandler,所依賴的bean有可能已經(jīng)銷毀了,比如數(shù)據(jù)庫DataSource已經(jīng)關(guān)閉。
實現(xiàn)DisposableBean 和注解PreDestroy,也是同樣的問題

那么 只有ContextClosedEvent了,具體代碼如下

@Component
@Log4j2
public class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {

    @Resource
    private XxlJobSpringExecutor xxlJobSpringExecutor;



    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        log.info("getDisplayName:{}", event.getApplicationContext().getDisplayName());
        xxlJobSpringExecutor.destroy();
   
    }
}

后記

1 . 關(guān)閉進程的時候,不能kill -9 pid ,否側(cè)上面都是無效,有兩個方式,
一種

kill -15 pid 

另一種
設(shè)置

management:
    shutdown:
      enabled: true

通過 curl 命令關(guān)閉

 curl -X POST  http://127.0.0.1:40001/actuator/shutdown
  1. 優(yōu)雅關(guān)機系列中,還有很多
  • request請求(spring 2.3之后,設(shè)置shutdown=graceful,就可以了)
  • mq
  • 線程池的任務(wù),比如request 中,使用了線程池
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容