分布式定時調度:xxl-job 萬字詳解
一.定時任務概述
1.定時任務認識
1.1.什么是定時任務
定時任務是按照指定時間周期運行任務。使用場景為在某個固定時間點執行,或者周期性的去執行某個任務,比如:每天晚上24點做數據匯總,定時發送短信等。
1.2.常見定時任務方案
- While + Sleep : 通過循環加休眠的方式定時執行
- Timer和TimerTask實現 :JDK自帶的定時任務,可以實現簡單的間隔執行任務(在指定時間點執行某一任務,也能定時的周期性執行),無法實現按日歷去調度執行任務。
- ScheduledExecutorService : Java并發包下,JDK1.5出現,是比較理想的定時任務實現方案。Eureka就使用的是它
- QuartZ : 使用Quartz,它是一個異步任務調度框架,功能豐富,可以實現按日歷調度,支持持久化。
- 使用Spring Task,Spring 3.0后提供
Spring Task實現任務調度,支持按日歷調度,相比Quartz功能稍簡單,但是在開發基本夠用,支持注解編程方式。 - SpringBoot中的Schedule : 通過
@EnableScheduling+@Scheduled最實現定時任務,底層使用的是Spring Task
2.分布式定時任務
2.1.遇到什么問題
上述的定時任務都是集中式(單體項目使用)的定時任務,在分布式中將會面臨一些問題或不足
- 業務量大,單機性能瓶頸需要擴展
- 多臺機器部署如何保證定時任務不重復執行
- 定時任務時間需要可調整,可以暫停
- 機器發生故障down機,定時任務依然可用,如何實現故障轉移
- 定時任務,執行日志是否可監控
2.2.分布式定時任務xxl-job
XXL-JOB是一個分布式任務調度平臺,于2015問世,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。現已開放源代碼并接入多家公司線上產品線,開箱即用。其具備且不止如下能力
- 簡單:支持通過Web頁面對任務進行CRUD操作,操作簡單,一分鐘上手;
- 動態:支持動態修改任務狀態、啟動/停止任務,以及終止運行中任務,即時生效;
- 調度中心HA(中心式):調度采用中心式設計,“調度中心”基于集群Quartz實現并支持集群部署,可保證調度中心HA;執行器HA(分布式):任務分布式執行,任務"執行器"支持集群部署,可保證任務執行HA;
- 彈性擴容縮容:一旦有新執行器機器上線或者下線,下次調度時將會重新分配任務;
- 路由策略:執行器集群部署時提供豐富的路由策略,包括:第一個、最后一個、輪詢、隨機、一致性HASH、最不經常使用、最近最久未使用、故障轉移、忙碌轉移等;
- 故障轉移:任務路由策略選擇"故障轉移"情況下,如果執行器集群中某一臺機器故障,將會自動Failover切換到一臺正常的執行器發送調度請求。
- 任務失敗告警:默認提供郵件方式失敗告警,同時預留擴展接口,可方面的擴展短信、釘釘等告警方式;
具體見:https://github.com/xuxueli/xxl-job/tree/v2.0.0
二.XXL-JOB初體驗
1.xxl-job架構設計
1.1.設計思想
將調度行為抽象形成“調度中心”公共平臺,而平臺自身并不承擔業務邏輯,“調度中心”負責發起調度請求。
將任務抽象成分散的JobHandler,交由“執行器”統一管理,“執行器”負責接收調度請求并執行對應的JobHandler中業務邏輯。因此,“調度”和“任務”兩部分可以相互解耦,提高系統整體穩定性和擴展性;
1.2.架構設計圖
xxl-job分為 調度中心和執行器兩大模塊
- 調度模塊(調度中心)
負責管理調度信息,按照調度配置發出調度請求,自身不承擔業務代碼。調度系統與任務解耦,提高了系統可用性和穩定性,同時調度系統性能不再受限于任務模塊;
支持可視化、簡單且動態的管理調度信息,包括任務新建,更新,刪除,GLUE開發和任務報警等,所有上述操作都會實時生效,同時支持監控調度結果以及執行日志,支持執行器Failover(故障轉移)。
- 執行模塊(執行器)
負責接收調度請求并執行任務邏輯。任務模塊專注于任務的執行等操作,開發和維護更加簡單和高效;
接收“調度中心”的執行請求、終止請求和日志請求等。

- 調度中心高可用
基于數據庫的集群方案,數據庫選用Mysql;集群分布式并發環境中進行定時任務調度時,會在各個節點會上報任務,存到數據庫中,執行時會從數據庫中取出觸發器來執行,如果觸發器的名稱和執行時間相同,則只有一個節點去執行此任務。
- 并行調度
調度采用線程池方式實現,避免單線程因阻塞而引起任務調度延遲。XXL-JOB調度模塊默認采用并行機制,在多線程調度的情況下,調度模塊被阻塞的幾率很低,大大提高了調度系統的承載量。
XXL-JOB的不同任務之間并行調度、并行執行。XXL-JOB的單個任務,針對多個執行器是并行運行的,針對單個執行器是串行執行的。同時支持任務終止。
- 執行器(任務)高可用
執行器如若集群部署,調度中心將會感知到在線的所有執行器,如“127.0.0.1:9997, 127.0.0.1:9998, 127.0.0.1:9999”。多個執行器可以選擇“路由策略”來采用輪詢,隨機等方式進行多機器調度。
當任務”路由策略”選擇”故障轉移(FAILOVER)”時,當調度中心每次發起調度請求時,會按照順序對執行器發出心跳檢測請求,第一個檢測為存活狀態的執行器將會被選定并發送調度請求。調度成功后,可在日志監控界面查看“調度備注”
2.xxl-job安裝
2.1.下載源碼
請下載項目源碼并解壓,使用IDEA工具導入項目
源碼倉庫地址
- https://github.com/xuxueli/xxl-job
- http://gitee.com/xuxueli0323/xxl-job
項目代碼結構如下

- doc :文檔,即SQL腳本所在目錄
- db : “調度數據庫”建表腳本
- xxl-job-admin : 調度中心項目源碼
- xxl-job-core : 核心模塊,公共Jar依賴
- xxl-job-executor-samples : 執行器,Sample示例項目(大家可以在該項目上進行開發,也可以將現有項目改造生成執行器項目)
2.2.導入數據庫
打開項目代碼,獲取 “調度數據庫初始化SQL腳本” 并執行即可。“調度數據庫初始化SQL腳本” 位置為: /xxl-job/doc/db/tables_xxl_job.sql ,數據庫名:xxl_job

數據庫如下

- xxl_job_lock:任務調度鎖表;
- xxl_job_group:執行器信息表,維護任務執行器信息;
- xxl_job_info:調度擴展信息表:用于保存XXL-JOB調度任務的擴展信息,如任務分組、任務名、機器地址、執行器、執行入參和報警郵件等等;
- xxl_job_log:調度日志表:用于保存XXL-JOB任務調度的歷史信息,如調度結果、執行結果、調度入參、調度機器和執行器等等;
- xxl_job_log_report:調度日志報表:用戶存儲XXL-JOB任務調度日志的報表,調度中心報表功能頁面會用到;
- xxl_job_logglue:任務GLUE日志:用于保存GLUE更新歷史,用于支持GLUE的版本回溯功能;
- xxl_job_registry:執行器注冊表,維護在線的執行器和調度中心機器地址信息;
- xxl_job_user:系統用戶表;
2.3.啟動調度中心
打開 xxl-job-admin 的配置文件,/xxl-job/xxl-job-admin/src/main/resources/application.properties 對調度中心進行配置,重要配置如下
- server.port : 根據情況修改端口
- spring.datasource.url :指向剛才準備的數據庫
- spring.datasource.password : 記得修改成自己的數據庫密碼
- spring.mail.username :配置自己的郵件賬號
- spring.mail.password :郵件的授權碼,我下面是以qq郵箱為例
下面根據自己的情況進行修改,不要直接復制
### 調度中心JDBC鏈接:鏈接地址請保持和 2.1章節 所創建的調度數據庫的地址一致 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=root_pwd spring.datasource.driver-class-name=com.mysql.jdbc.Driver ### 報警郵箱 spring.mail.host=smtp.qq.com spring.mail.port=25 spring.mail.username=xxx@qq.com spring.mail.password=郵箱授權碼,不是登錄密碼 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory ### 調度中心通訊TOKEN [選填]:非空時啟用; xxl.job.accessToken= ### 調度中心國際化配置 [必填]: 默認為 "zh_CN"/中文簡體, 可選范圍為 "zh_CN"/中文簡體, "zh_TC"/中文繁體 and "en"/英文; xxl.job.i18n=zh_CN ## 調度線程池最大線程配置【必填】 xxl.job.triggerpool.fast.max=200 xxl.job.triggerpool.slow.max=100 ### 調度中心日志表數據保存天數 [必填]:過期日志自動清理;限制大于等于7時生效,否則, 如-1,關閉自動清理功能; xxl.job.logretentiondays=30
然后啟動調度中心 ,執行 XxlJobAdminApplication#main 方法 , 啟動之后,瀏覽器訪問 http://localhost:18080/xxl-job-admin/jobinfo?jobGroup=2 ;注意URL中有個上下文路徑。默認登錄賬號 “admin/123456”, 登錄后運行界面如下圖所示。

2.3.配置部署“執行器項目
“執行器”項目:xxl-job-executor-sample-springboot (提供多種版本執行器供選擇,現以 springboot 版本為例,可直接使用,也可以參考其并將現有項目改造成執行器)
作用:負責接收“調度中心”的調度并執行;可直接部署執行器,也可以將執行器集成到現有業務項目中。
修改配置:/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties
- xxl.job.admin.addresses : 調度中心的地址,如果調度中心修改過端口,這里也要對應修改
### 調度中心部署跟地址 [選填]:如調度中心集群部署存在多個地址則用逗號分隔。執行器將會使用該地址進行"執行器心跳注冊"和"任務結果回調";為空則關閉自動注冊; xxl.job.admin.addresses=http://127.0.0.1:18080/xxl-job-admin ### 執行器通訊TOKEN [選填]:非空時啟用; xxl.job.accessToken= ### 執行器AppName [選填]:執行器心跳注冊分組依據;為空則關閉自動注冊 xxl.job.executor.appname=xxl-job-executor-sample ### 執行器注冊 [選填]:優先使用該配置作為注冊地址,為空時使用內嵌服務 ”IP:PORT“ 作為注冊地址。從而更靈活的支持容器類型執行器動態IP和動態映射端口問題。 xxl.job.executor.address= ### 執行器IP [選填]:默認為空表示自動獲取IP,多網卡時可手動設置指定IP,該IP不會綁定Host僅作為通訊實用;地址信息用于 "執行器注冊" 和 "調度中心請求并觸發任務"; xxl.job.executor.ip= ### 執行器端口號 [選填]:小于等于0則自動獲取;默認端口為9999,單機部署多個執行器時,注意要配置不同執行器端口; xxl.job.executor.port=9999 ### 執行器運行日志文件存儲磁盤路徑 [選填] :需要對該路徑擁有讀寫權限;為空則使用默認路徑; xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler ### 執行器日志文件保存天數 [選填] : 過期日志自動清理, 限制值大于等于3時生效; 否則, 如-1, 關閉自動清理功能; xxl.job.executor.logretentiondays=30
上面配置是為了在Spring容器中創建一個 XxlJobSpringExecutor 執行器Bean,見:com.xxl.job.executor.core.config.XxlJobConfig#xxlJobExecutor
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
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;
}
在com.xxl.job.executor.service.jobhandler.SampleXxlJob中提供了簡單的定時任務實例
為方便用戶參考與快速實用,示例執行器內原生提供多個Bean模式任務Handler,可以直接配置實用,如下:
- demoJobHandler:簡單示例任務,任務內部模擬耗時任務邏輯,用戶可在線體驗
Rolling Log等功能; - shardingJobHandler:分片示例任務,任務內部模擬處理分片參數,可參考熟悉分片任務;
- httpJobHandler:通用HTTP任務Handler;業務方只需要提供HTTP鏈接等信息即可,不限制語言、平臺。示例任務入參如下:
/**
* XxlJob開發示例(Bean模式)
*
* 開發步驟:
* 1、在Spring Bean實例中,開發Job方法,方式格式要求為 "public ReturnT execute(String param)"
* 2、為Job方法添加注解 "@XxlJob(value="自定義jobhandler名稱", init = "JobHandler初始化方法", destroy = "JobHandler銷毀方法")",注解value值對應的是調度中心新建任務的JobHandler屬性的值。
* 3、執行日志:需要通過 "XxlJobLogger.log" 打印執行日志;
*
* @author xuxueli 2019-12-11 21:52:51
*/
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
/**
* 1、簡單任務示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public ReturnT demoJobHandler(String param) throws Exception {
logger.info("XXL-JOB, Hello World. param={}",param);
return ReturnT.SUCCESS;
}
//...省略...
}
【重要】 如果我們要寫自己的定時任務,參照上面方法,在方法上注解一個@XxlJob("任務名字") ,方法可以接受一個字符串參數,方法需要返回ReturnT格式。最后啟動執行器項目.
3.配置定時任務
3.1.執行器創建
打開調度中心可視化界面,在執行器管理界面,添加新增執行器

- appName : 執行器的名字,可以任意填寫
- 名稱:任意填寫
- 注冊方式:調度中心是通過RPC的方式對執行器發起調度,所以這里需要的是執行器項目的
ip:port,注意,該端口不是執行器項目的server.port,而是:xxl.job.executor.port端口。你可以選擇自動注冊,也可以手動錄入。
3.2.創建任務
在 任務管理 界面,新增任務

- 路由策略:有輪詢,隨機,故障轉移等等策略,是用在集群模式下的調度方式。
- cron : 定時任務的執行時間規則,時間表達式
- JobHandler : 這個是要對應 “執行器項目”中
@XxlJob("demoJobHandler")注解中的名字 - 運行模式 :Bean ,使用內置代碼方式,也可以執行在線執行代碼方式
- 報警郵件 :如果定時任務失敗,會發送報警郵件到郵箱
- 任務參數:這個參數可以傳遞給
@XxlJob("demoJobHandler")所在方法的參數。
創建好任務之后就可以執行了

調度日志

IDEA工具控制臺效果
15:47:33.017 logback [Thread-16] INFO c.x.j.e.s.jobhandler.SampleXxlJob - XXL-JOB, Hello World. param= 15:47:34.007 logback [Thread-16] INFO c.x.j.e.s.jobhandler.SampleXxlJob - XXL-JOB, Hello World. param= 15:47:35.008 logback [Thread-16] INFO c.x.j.e.s.jobhandler.SampleXxlJob - XXL-JOB, Hello World. param= ...省略...
4.GLUE模式(Java)
4.1.添加任務
該模式支持在線編輯定時任務的內容,立刻執行,無需再開發工具中編輯代碼,也無需重啟項目。
請點擊任務右側 “GLUE” 按鈕,進入 “GLUE編輯器開發界面” ,見下圖。“GLUE模式(Java)” 運行模式的任務默認已經初始化了示例任務代碼,即打印Hello World。

任務以源碼方式維護在調度中心,支持通過Web IDE在線更新,實時編譯和生效,因此不需要指定JobHandler
4.2.編寫代碼
保存之后可以在操作按鈕里面去編寫任務

(“GLUE模式(Java)” 運行模式的任務實際上是一段繼承自IJobHandler的Java類代碼,它在執行器項目中運行,可使用@Resource/@Autowire注入執行器里中的其他服務),比如我的定時任務如下,編輯好之后點擊保存

保存好之后,啟動定時任務,效果如下

三.XXL-JOB集群部署
1.調度中心集群
1.1.問題概述
調度中心支持集群部署,提升調度系統容災和可用性。調度中心集群部署時,幾點要求和建議:
- DB配置保持一致;
- 集群機器時鐘保持一致(單機集群忽視);
- 當啟動多個調度器時,執行器配置調度中心部署跟地址可以用逗號分隔。執行器將會使用該地址進行"執行器心跳注冊"和"任務結果回調";為空則關閉自動注冊;
但是建議:推薦通過nginx為調度中心集群做負載均衡,分配域名。調度中心訪問、執行器回調配置、調用API服務等操作均通過該域名進行。
1.2.啟動多個調度中心
修改調度中心端口,啟動多個調度中心,我這里啟動兩個如
- http://localhost:18080/xxl-job-admin/
- http://localhost:18081/xxl-job-admin/
1.3.配置Nginx負載均衡
當啟動多個調度器時,執行器配置調度中心部署跟地址可以用逗號分隔。執行器將會使用該地址進行“執行器心跳注冊”和“任務結果回調”;為空則關閉自動注冊;
但是建議:推薦通過nginx為調度中心集群做負載均衡,分配域名。調度中心訪問、執行器回調配置、調用API服務等操作均通過該域名進行。
我們啟動了2個調度中心,那么我的執行器項目該注冊到哪個調度中心呢?我們通過Nginx來解決這個問題,原理如下圖:

我們再hosts配置 www.jobs.com作為nginx的主機域名,然后反向代理到多個調度中心,這樣一來執行器就只需要注冊到www.jobs.com Nginx即可。
修改 C:\Windows\System32\drivers\etc\hosts增加配置如下
127.0.0.1 www.jobs.com
Nginx配置如下
#調度中心
upstream jobs{
server localhost:18080;
server localhost:18081;
}
server {
listen 80;
#使用域名
server_name www.jobs.com;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
#調度中心反向代理配置
proxy_pass http://jobs/;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
啟動Nginx,通過瀏覽器訪問 http://www.jobs.com/xxl-job-admin/ ,可以訪問到調度中心的管理界面
2.執行器項目集群
執行器支持集群部署,提升調度系統可用性,同時提升任務處理能力。
執行器集群部署時,幾點要求和建議:
- 執行器回調地址(
xxl.job.admin.addresses)需要保持一致;執行器根據該配置進行執行器自動注冊等操作。 - 同一個執行器集群內AppName(
xxl.job.executor.appname)需要保持一致;調度中心根據該配置動態發現不同集群的在線執行器列表。
2.1.啟動多個執行器項目
現在對執行器項目做集群,修改xxl-job-executor-sample-springboot配置文件application.properties
- server.port : 既然是做集群,項目端口需要修改
- xxl.job.admin.addresses : 調度中心地址需要修改成
www.jobs.com,多個執行器配置同一個地址。 - xxl.job.executor.port : RPC通信端口也要修改,多個執行器該端口需要不一樣
第一個實例配置
server.port=19090 xxl.job.admin.addresses=http://www.jobs.com/xxl-job-admin #對應Nginx地址 xxl.job.executor.port=9999
第二個實例配置
server.port=19091 xxl.job.admin.addresses=http://www.jobs.com/xxl-job-admin xxl.job.executor.port=9998
在 Configurations中配置,允許啟動多個實例

啟動實例如下

2.2.配置定時任務
通過http://www.jobs.com/xxl-job-admin 訪問調度中心管理界面,在執行器管理中可以看到多臺執行器實例

在任務管理中,可以編輯任務,然后選擇路由策略,比如:選擇輪詢,然后啟動任務,就會看到兩個執行器項目輪著執行定時任務。

說在最后
xxl-job確實很強大,功能也很全,經過該文章學習相信你可以把xxl-job給用起來了,但是如果你的項目是一個小體量的單體,我不太建議使用它,Quzrtz或者SpringBoot Task就足夠 ,對于xxl-job個人還是有些笨重。