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去做的。程式碼略長,就不貼出來了。具體實現邏輯如下:
-
通過applicationContext.getBeanNamesForType獲取全部bean
-
遍歷所有bean,找到有@XxlJob標記的方法,並判斷@XxlJob標記的name值是否有重複,如果重複則報錯
-
如果有配置init和destroy方法,則通過反射找到他們
-
將資訊組裝成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);
}
...
}
-
初始化執行器日誌路徑,預設 /data/applogs/xxl-job/jobhandler 。
這個XxlJobFileAppender是個單獨寫日誌檔案的工具類。在xxl-job-admin介面上,可以通過介面檢視定時任務排程執行的日誌。我們在業務程式碼中,也可以通過XxlJobHelper.log方法,寫自己的日誌(老版本是XxlJobLogger.log)
-
根據配置的adminAddresses地址,構造AdminBiz列表(後面註冊、調服務端介面等,會需要調到這個地址)
-
啟動一個daemon執行緒,每天定期清理排程日誌檔案(上述1步驟目錄下的檔案)
-
定義一個LinkedBlockingQueue,這個queue裡面放job執行結果。然後啟動triggerCallbackThread和triggerRetryCallbackThread 兩個執行緒,向job-admin反饋job執行結果。
這裡為啥是2個執行緒去給admin端反饋執行結果呢?
原來,正常情況下,只有triggerCallbackThread從queue裡面拿資料,提交到admin。
但是當它提交失敗的時候,triggerCallbackThread就會寫一個callbacklog檔案。再由triggerRetryCallbackThread讀取callbacklog檔案,並向admin提交執行結果。
-
構造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的程式碼