全部產品
Search
文件中心

Microservices Engine:基於MSE XXL-JOB實現優雅下線

更新時間:Apr 24, 2025

MSE XXL-JOB在相容開源版本基礎上新增優雅下線功能:應用關閉前首先通知調度服務端摘流停止新任務派發,待存量任務執行完畢後再安全下線應用,實現業務無損重啟。本文旨在為您介紹如何基於阿里雲任務調度XXL-JOB版開啟優雅下線功能,協助您處理實際業務重啟或下線的情境。

定時任務優雅下線概述

在實際業務中,應用進程內的定時任務會持續按固定頻率執行。當應用發布重啟時,運行中的定時任務將被強制中斷,可能導致資料不完整和調度成功率驟降,最終出現業務資料受損。主要存在以下情況:

  • 任務執行中斷:任務正在運行中,應用進程停機業務處理中斷,可能會導致業務資料不完整。

  • 任務調度下跌:在發布重啟過程中,調度器將任務分發給停機節點,導致調度失敗影響整體處理效率。

因此,在使用定時任務調度的情境下,需要定時任務優雅下線,以實現滾動發布和重啟過程中的業務平滑運行。

基於開源XXL-JOB優雅下線實踐

開源XXL-JOB執行原理及優雅下線問題

目前開源XXL-Job在任務調度持續執行過程中,任務執行側還無法有效地實現優雅下線功能。因此,想通過開源版本實現優雅下線需要自訂改造。在改造開源XXL-JOB優雅下線之前,可以先對XXL-JOB任務分發和任務執行的整體鏈路進行剖析。整個鏈路涉及XXL-JOB AdminXXL-Job Executor兩個模組。

針對XXL-Job主要的互動執行鏈路進行解讀說明,並標註出對優雅下線的影響邏輯點,以便作為自訂改造的參考,目前主要存在以下問題:

問題一:下線節點摘流延遲

執行器在下線時未能及時完成調度和執行機器列表更新,會出現定時調度至下線節點導致調度失敗。

邏輯處理一:執行器註冊
  • 業務應用在依賴XXL-JOB SDK啟動後,會初始化ExecutorRegistryThread線程持續向調度中心彙報心跳。

  • 調度中心在接收到後,會通過JobRegistryHelper,將註冊上來的執行器資訊寫入資料庫表xxl_job_registry

  • JobRegistryHelper中存在一個線程,定期查詢更新xxl_job_group表的address_list(實際使用的列表)。

image

image

邏輯處理二:選擇線上執行機器
  • 任務在通過調度線程觸發後,會交給XxlJobTrigger完成觸發執行動作。

  • 在觸發執行前,會從xxl_job_groupaddress_list讀取可用的執行器列表。

  • 通過ExecutorRouter在上述機器列表中,按對應的路由策略選擇一台機器。

  • 完成機器選擇後,會通過RPC請求將任務分發至對應IP節點運行;此時如選擇一個已下線節點任務觸發會失敗。

image

總結說明:由於上述註冊執行器邏輯和觸發任務執行擷取列表資料不即時同步,是通過非同步定時更新,因此會產生可用線上機器列表重新整理延遲。

問題二:任務執行強制中斷

目前XXL-Job Executor在退出時會直接觸發任務運行線程中斷按失敗處理,且隊列中等待執行的任務執行請求會全部丟棄按失敗處理。

邏輯處理一:將任務分發至對應機器執行
  • 業務應用執行器接收到對應任務後,會根據任務ID為每個任務建立一個JobThread線程用於任務執行。

  • 該任務本次觸發執行請求,會被添加到當前任務線程待執行隊列中,任務不同阻塞策略會有不同處理。

  • JobThread該線程會持續迴圈讀取,隊列中的觸發記錄並執行對應的JobHandler完成商務邏輯處理。

  • 任務執行結束後,會將本次執行結果資訊提交給TriggerCallbackThread的執行應答隊列,繼續下一次執行。

  • 執行器在停止時會執行XxlJobExecutor.destroy方法,該方法會中斷啟動並執行線程及清理掉隊列中等待執行的調度請求。

邏輯處理二:任務執行結果反饋
  • TriggerCallbackThread會持續運行載入當前執行結果隊列,批量分發執行結果給調度中心。

  • 如向調度中心發送應答結果失敗,則會寫入本地檔案落盤,會安排重試。

  • 調度中心在接收到執行結果後會進行執行記錄的更新寫入庫。

image

總結說明:在上述下線處理過程中,removeJobThread底層會直接中斷運行中的任務線程,且線程隊列中等待執行的任務會直接忽略執行按失敗處理。

開源XXL-JOB實現優雅下線

通過開源XXL-JOB執行原理的過程分析,根據分析原理基於開原始碼實現優雅下線功能。應用優雅下線的核心三步驟是先摘流,再等待執行中業務完成,最後停機下線

XXL-JOB Core模組的com.xxl.job.core.executor.XxlJobExecutor#destroy,在SpringBoot模式下會在應用進程退出時自動進行回調處理,其中包含了應用執行器的一些下線回收動作,但目前此處的相關邏輯處理並不能完全實現優雅下線功能。因此,需結合上述的剖析進行以下步驟改造處理:

步驟一:應用節點摘流

  • 首先XxlJobExecutor#destroy方法中,存在stopEmbedServer()方法會停止心跳註冊並向調度中心發送registryRemove請求,以移除當前執行節點。

  • 調度服務端在接收請求後會將資料庫xxl_job_registry表中當前節點移除,但參考原理剖析說明實際使用的是xxl_job_groupaddress_list(並未被同步更新),此時並未真正完成摘流動作。

  • 需對調度服務端進行相應改造來實現摘流,改造點選擇(選其一):

    • JobRegistryHelper.registryRemove方法中添加後續處理,直接重新整理xxl_job_groupaddress_list,也可在freshGroupRegistryInfo實現重新整理邏輯。

    • 改造XxlJobTrigger#trigger()方法,調整groupaddressList的讀取方式,對於自動註冊直接從xxl_job_registry表讀取地址清單。

完成上述改造後,即可完成第一步摘流動作。

步驟二:等待執行中業務完成

  • 改造XxlJobExecutor#destroy方法中的下一步,需要等待所有待執行任務處理完成,參考如下代碼:

    public void destroy(){
    
        // destroy executor-server
        stopEmbedServer();
    
        // destroy jobThreadRepository
        if (jobThreadRepository.size() > 0) {
            List keyList = new ArrayList(jobThreadRepository.keySet());
            for (int i=0; i < keyList.size(); i++) {
                JobThread jobThread = jobThreadRepository.get(keyList.get(i));
                // 等待所有任務隊列執行完成
                while (jobThread != null && jobThread.isRunningOrHasQueue()) {
                    try {
                        TimeUnit.SECONDS.sleep(1L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        jobHandlerRepository.clear();
    
        // destroy JobLogFileCleanThread
        JobLogFileCleanThread.getInstance().toStop();
    
        // destroy TriggerCallbackThread
        TriggerCallbackThread.getInstance().toStop();
    
    }
  • 等待答應結果隊列反饋所有執行結果,目前開源實現的TriggerCallbackThread.getInstance().toStop()方法在中斷應答結果線程後會最終同步一次處理結果,因此可不做額外處理。

至此,等待運行中任務處理即可完成。另外,您可自行發揮對不同任務類型做個人化的下線處理。

步驟三:應用進程停機

  • 應用停止建議在發布部署指令碼中通過kill -15來觸發上述JVM的Hook回調,可根據業務需要再控制逾時強制停止。

  • 可以通過SpringBoot Actuator功能將任務調度優雅下線整合,再通過/actuator/shutdown介面下線應用。

前提條件

  • 引擎版本需2.1.0及以上。版本詳情,請參見XXL-JOB引擎版本

  • 用戶端需接入SchedulerX plugin包。版本詳情,請參見XXL-JOB外掛程式版本

    <dependency>
      <groupId>com.aliyun.schedulerx</groupId>
      <artifactId>schedulerx3-plugin-xxljob</artifactId>
      <version>最新版本</version>
    </dependency>

如何啟用優雅下線

在不同的業務形態和部署情境中,完整配置並啟用優雅下線方案。該過程主要包括兩個步驟:

步驟一:執行器各架構初始化整合

業務應用不同的部署形態,需要按不同方式進行初始化整合。

形態一:SpringBoot業務應用(推薦)

如果業務應用採用了SpringBoot方式整合XXL-Job的執行器,那麼可自動完成相應的優雅下線能力初始化整合。只需完成如下兩個步驟即可:

  1. 添加SchedulerX外掛程式Maven依賴。版本詳情,請參見XXL-JOB外掛程式版本

    <dependency>
      <groupId>com.aliyun.schedulerx</groupId>
      <artifactId>schedulerx3-plugin-xxljob</artifactId>
      <version>最新版本</version>
    </dependency>
  2. 添加應用配置參數,開啟優雅下線。詳細參數說明,請參見配置參數說明

    # 配置優雅下線
    xxl.job.executor.shutdownMode=WAIT_ALL

形態二:Spring業務應用

如果業務應用是通過Spring架構啟動的Web應用,除了添加POM依賴應用啟動參數(參考形態一:SpringBoot業務應用(推薦)),還需通過初始化配置XxlJobExecutorEnhancerInitializer,在web.xml中添加如下配置:

<web-app>
  <context-param>
        <!-- Spring ApplicationContextInitializer 增強xxljob executor功能 -->
        <param-name>globalInitializerClasses</param-name>
        <param-value>com.aliyun.schedulerx.xxljob.enhance.XxlJobExecutorEnhancerInitializer</param-value>
    </context-param>
</web-app>

形態三:Frameless Java業務應用

如果業務應用採用xxl-job executor案例中的Frameless方式,通過純Java編碼方式啟動業務應用時,可通過自訂編碼完成優雅下線功能初始化整合。首先業務應用還是需要添加POM依賴應用啟動參數(參考形態一:SpringBoot業務應用(推薦)),相關參考案例如下:

  • Executor啟動前,添加:EnhancerLoader.load(xxlJobProp),完成功能增強載入。

  • Executor啟動前,添加:Runtime.getRuntime().addShutdownHook(...),為當前應用添加下線Hook實現。

範例程式碼

public static void main(String[] args) {
    try {
        // load executor prop
        Properties xxlJobProp = FrameLessXxlJobConfig.loadProperties("xxl-job-executor.properties");

        // 初始化增加xxl-job executor載入
        EnhancerLoader.load(xxlJobProp);

        // start xxl-job executor
        FrameLessXxlJobConfig.getInstance().initXxlJobExecutor(xxlJobProp);

        // 添加系統優雅線下hook
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                FrameLessXxlJobConfig.getInstance().destroyXxlJobExecutor();
            }
        });
        // Blocks until interrupted
        while (true) {
            try {
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException e) {
                break;
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    } finally {
        // destroy
        FrameLessXxlJobConfig.getInstance().destroyXxlJobExecutor();
    }
}

步驟二:應用停機下線處理

自建部署,通過kill -15停機

在自建CD流程中通常會有一個應用進程停止的節點,該節點可通過構建一個stop.sh 指令碼用於應用進程停止退出。指令碼內容需包含應用優雅下線的相關邏輯處理,參考如下停機指令碼。

應用進程停機指令碼案例:

# 應用啟用成功後進程ID資訊會寫入app.pid檔案
PID="{應用部署路徑}/app.pid"
FORCE=1
if [ -f ${PID} ]; then
  TARGET_PID=`cat ${PID}`
  kill -15 ${TARGET_PID}
  loop=1
  while(( $loop<=5 ))
  do
    ## health 檢查當前應用進程確實已經結束,可根據應用特徵自訂
    health
    if [ $? == 0 ]; then
      echo "check $loop times, current app has not stop yet."
      sleep 5s
      let "loop++"
    else
      FORCE=0
      break
    fi
  done
  if [ $FORCE -eq 1 ]; then
  	echo "App(pid:${TARGET_PID}) stop timeout, forced termination."
    kill -9 ${TARGET_PID}
  if
  rm -rf ${PID}
  echo "App(pid:${TARGET_PID}) stopped successful."
fi

K8s容器化部署,通過PreStop停機

利用k8s pod的生命週期管理可預設實現優雅下線。同時還可以使用preStop hook,通過exec執行指令碼和HTTP請求方式來實現優雅下線邏輯處理。

  • 預設無效修改:如果業務應用進程為容器中的主進程PID 1,則預設會先給主進程發送SIGTERM訊號進行優雅下線。

  • 自訂preStop:如容器內為複雜的多進程關係,可通過配置preStop指令碼,自訂通過kill -15 PID停止應用進程,或者調用提前預設的stop.sh指令碼實現應用進程退出。

重要

方案中Pod的terminationGracePeriodSeconds參數會控制優雅下線的整體最長等待時間(預設30s),需要按業務需要合理配置。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: my-app-image:latest
        lifecycle:
          preStop:
            exec:
              # command: ["/bin/sh", "-c", "kill -15 PID && sleep 30"]
              command: ["/bin/sh", "-c", "指令碼路徑/stop.sh"]

阿里雲上應用發布平台自動整合

敬請期待。

配置參數說明

在業務應用中需要配置如下參數開啟優雅下線功能,並且支援通過該參數配置兩種不同的優雅下線策略。

# 優雅下線模式,WAIT_ALL:等待全部; WAIT_RUNNING:等待運行中。
# 該參數不配置,則表示保持XXL-JOB原始邏輯不變(預設不開啟優雅下線)。
xxl.job.executor.shutdownMode=WAIT_ALL

下線模式

描述

等待全部(WAIT_ALL

(推薦)該模式下,待所有已接收的任務(正在運行及隊列中等待的任務)執行完成後,應用才退出。

等待運行中(WAIT_RUNNING

該模式下,應用在退出時,將等待已指派線程並在處理中的執行記錄完成,隊列中的任務將被放棄執行。