xxl-job原始碼閱讀一(客戶端)

yejg1212發表於2021-05-21

1、原始碼入口

使用xxl-job的時候,需要引入一個jar,然後還需要往Spring容器注入XxlJobSpringExecutor

<dependency>
	<groupId>com.xuxueli</groupId>
	<artifactId>xxl-job-core</artifactId>
	<version>2.3.0</version>
</dependency>

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        return xxlJobSpringExecutor;
    }

我們就可以順著這個XxlJobSpringExecutor,分析下這個xxl-job-core做了些什麼。

2、執行器啟動

XxlJobSpringExecutor程式碼比較簡潔,大致框架如下:

public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
    
	@Override
    public void afterSingletonsInstantiated() {
        // init JobHandler Repository (for method)
        initJobHandlerMethodRepository(applicationContext);

        // refresh GlueFactory
        GlueFactory.refreshInstance(1);

        // super start
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
  	@Override
    public void destroy() {
        super.destroy();
    }  
    
    // ......

}

1、實現了SmartInitializingSingleton介面,在專案啟動的時候,會調到afterSingletonsInstantiated方法。這個方法又可以作為進一步閱讀的下個入口了;

2、實現了DisposableBean介面,在系統停止的時候,呼叫destroy方法。

3、實現ApplicationContextAware,只是為了獲取applicationContext物件,這個沒啥好說的。

先來看看初始化

2.1、解析Xxl-job註解

在使用xxl-job的時候,我們會寫@XxlJob註解,來告訴xxl-job這是個定時任務的方法。

那麼xxl-job就得解析這個註解並做一系列處理。對吧?

這個事情,就是在afterSingletonsInstantiated方法裡面調initJobHandlerMethodRepository去做的。程式碼略長,就不貼出來了。具體實現邏輯如下:

  1. 通過applicationContext.getBeanNamesForType獲取全部bean

  2. 遍歷所有bean,找到有@XxlJob標記的方法,並判斷@XxlJob標記的name值是否有重複,如果重複則報錯

  3. 如果有配置init和destroy方法,則通過反射找到他們

  4. 將資訊組裝成MethodJobHandler物件,儲存到ConcurrentHashMap中

    registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

2.2、處理glue模式

一般基於Spring使用的時候,都是bean模式,即

任務以JobHandler方式維護在執行器端;需要結合 "JobHandler" 屬性匹配執行器中任務;

而glue模式是指

任務以原始碼方式維護在排程中心;

這裡初始化了一個SpringGlueFactory

2.3、啟動

這裡呼叫父類XxlJobExecutor的start方法。方法主要執行下面幾個大的步驟:

public class XxlJobExecutor  {
	...
    public void start() throws Exception {

        // init logpath
        XxlJobFileAppender.initLogPath(logPath);

        // init invoker, admin-client
        initAdminBizList(adminAddresses, accessToken);


        // init JobLogFileCleanThread
        JobLogFileCleanThread.getInstance().start(logRetentionDays);

        // init TriggerCallbackThread
        TriggerCallbackThread.getInstance().start();

        // init executor-server
        initEmbedServer(address, ip, port, appname, accessToken);
    }
    ...
}
  1. 初始化執行器日誌路徑,預設 /data/applogs/xxl-job/jobhandler 。

    這個XxlJobFileAppender是個單獨寫日誌檔案的工具類。在xxl-job-admin介面上,可以通過介面檢視定時任務排程執行的日誌。我們在業務程式碼中,也可以通過XxlJobHelper.log方法,寫自己的日誌(老版本是XxlJobLogger.log)

  2. 根據配置的adminAddresses地址,構造AdminBiz列表(後面註冊、調服務端介面等,會需要調到這個地址)

  3. 啟動一個daemon執行緒,每天定期清理排程日誌檔案(上述1步驟目錄下的檔案)

  4. 定義一個LinkedBlockingQueue,這個queue裡面放job執行結果。然後啟動triggerCallbackThread和triggerRetryCallbackThread 兩個執行緒,向job-admin反饋job執行結果。

    這裡為啥是2個執行緒去給admin端反饋執行結果呢?

    原來,正常情況下,只有triggerCallbackThread從queue裡面拿資料,提交到admin。

    但是當它提交失敗的時候,triggerCallbackThread就會寫一個callbacklog檔案。再由triggerRetryCallbackThread讀取callbacklog檔案,並向admin提交執行結果。

  5. 構造EmbedServer並啟動

    構造EmbedServer需要url,這裡涉及到三個配置。

    ### 執行器註冊 [選填]:優先使用該配置作為註冊地址,為空時使用內嵌服務 ”IP:PORT“ 作為註冊地址。從而更靈活的支援容器型別執行器動態IP和動態對映埠問題。
    xxl.job.executor.address=
    ### 執行器IP [選填]:預設為空表示自動獲取IP,多網路卡時可手動設定指定IP,該IP不會繫結Host僅作為通訊實用;地址資訊用於 "執行器註冊" 和 "排程中心請求並觸發任務";
    xxl.job.executor.ip=
    ### 執行器埠號 [選填]:小於等於0則自動獲取;預設埠為9999,單機部署多個執行器時,注意要配置不同執行器埠;
    xxl.job.executor.port=9999
    

    EmbedServer是基於netty實現的一個服務,監聽9999埠,並主要響應xxl-job-admin的排程請求。從EmbedHttpServerHandler中可以看出,admin排程中心往執行器發的請求,主要有以下5個:

    beat:排程中心檢測執行器是否線上時使用
    idleBeat:排程中心檢測 指定執行器 上 指定任務 是否忙碌(執行中)時使用 (注意這裡2個“指定”)
    run:觸發任務執行
    kill:終止任務
    	這裡是根據jobId找到對應的jobThread,再改變執行標記後,調interrupt方法。
    	(所以如果你想優雅地停止一個執行緒,也可以通過執行緒標記+interrupt方式)
    log:檢視執行日誌
    

    執行器什麼時候往呼叫中心註冊的呢?

    答案是:在EmbedServer的start方法中,啟動了一個thread,在其內部調了【startRegistry(appname, address);】

    而這行程式碼裡面,又啟動了一個registryThread,每30秒註冊當前執行器。

至此,xxl-job客戶端的邏輯大致分析清楚了,下一節再看看admin的程式碼

相關文章