分佈式任務調度系統:xxl-job

任務調度,通俗來說實際上就是“定時任務”,分佈式任務調度系統,翻譯一下就是“分佈式環境下定時任務系統”。

xxl-job一個分佈式任務調度平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。現已開放源代碼並接入多家公司線上產品線,開箱即用。

gitee地址:https://gitee.com/xuxueli0323/xxl-job

中文文檔地址:https://www.xuxueli.com/xxl-job/

文檔就已經說得足夠詳細,接下來就我的使用體驗上來說說使用方法和出現的問題(本篇文章基於2.2.0版本講解,2.2.0和最新的2.3.0版本使用上有些差異,可以參考源代碼中的example項目進行修改)。

一、爲何選用xxl-job

需求:服務有兩個實例,要求做一個定時任務,每隔一個小時更新一批數據。聽起來挺簡單的一個需求,但是要考慮的事情挺多

  • 如何保證兩個服務同時間只有一個實例在運行跑批程序
  • 如果程序一個小時未執行完任務,那到了下一次跑批的時間,改如何處理下一次跑批請求
  • 每次跑批是否需要隨機選擇一個實例運行跑批程序
  • 如果任務失敗了,該如何處理
  • ......

如果我們自己來做這個事情,可能需要redis或者數據庫鎖以保證同時間只有一個實例運行跑批程序;同時,如果鎖未釋放,表示跑批程序未執行完畢,如果這時候又來了一個跑批請求,可以選擇丟棄掉,也可以選擇將其放入跑批隊列,這時候可能需要一個消息隊列,可以選擇數據庫或者redis作爲存儲;如果沒有服務端協調處理跑批,那麼每次跑批如何選擇實例做跑批任務是比較困難的;如果跑批失敗,可以選擇重試或者不重試直接發送失敗郵件通知,或者兩者兼而有之。

上述解決方案是比較常用的第一時間能想到的解決方案,可以看到還是要做挺多開發任務的。現在有了xxl-job,這些工作都被它做掉了,使用者只需要關心跑批的業務邏輯即可。

二、運行xxl-job-admin

在當前時間2021-04-21時間點上,最新版本的xxl-job版本號是2.3.0,而公司使用的版本是2.2.0,所以這裏我使用2.2.0爲例進行說明。

首先下載完xxl-job的源代碼,可以看到該項目是maven項目

├── doc
│   ├── db
│   ├── images
│   ├── XXL-JOB官方文檔.md
│   ├── XXL-JOB架構圖.pptx
│   └── XXL-JOB-English-Documentation.md
├── LICENSE
├── NOTICE
├── pom.xml
├── README.md
├── xxl-job-admin
│   ├── Dockerfile
│   ├── Dockerfile1
│   ├── pom.xml
│   ├── src
│   └── target
├── xxl-job-core
│   ├── pom.xml
│   ├── src
│   └── target
└── xxl-job-executor-samples
    ├── pom.xml
    ├── xxl-job-executor-sample-frameless
    ├── xxl-job-executor-sample-jboot
    ├── xxl-job-executor-sample-jfinal
    ├── xxl-job-executor-sample-nutz
    ├── xxl-job-executor-sample-spring
    └── xxl-job-executor-sample-springboot

包含着三個模塊

  • xxl-job-admin:xxl-job服務端
  • xxl-job-core:xxl-job客戶端依賴
  • xxl-job-executor-samples:提供了一些使用樣例

xxl-job-admin的安裝很簡單,推薦使用docker安裝的方式

0.運行數據庫腳本

xxl-job-admin運行依賴於數據庫,先運行xxl-job/xxl-job/doc/db/tables_xxl_job.sql腳本,這個腳本創建了xxl_job數據庫以及一些表,這是xxl-job-admin運行的基礎。

1.下載docker鏡像並運行

https://www.xuxueli.com/xxl-job/#其他:Docker 鏡像方式搭建調度中心:

下載鏡像

docker pull xuxueli/xxl-job-admin:2.2.0

創建容器並運行

docker run -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d xuxueli/xxl-job-admin:{指定版本}
/**
* 如需自定義 mysql 等配置,可通過 "-e PARAMS" 指定,參數格式 PARAMS="--key=value  --key2=value2" ;
* 配置項參考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties
* 如需自定義 JVM內存參數 等配置,可通過 "-e JAVA_OPTS" 指定,參數格式 JAVA_OPTS="-Xmx512m" ;
*/
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d xuxueli/xxl-job-admin:2.2.0

2.自己打包docker鏡像

下載完xxl-job-admin源代碼後,切換到2.2.0 的tag

git checkout 2.2.0

然後切換到xxl-job-admin根目錄,執行打包命令

mvn clean package

修改xxl-job-admin目錄下的Dockerfile文件,添加PARAMS參數

ENV PARAMS="--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=passwod"

在Dockerfile所在目錄執行以下打鏡像命令

docker build . -t xxl-job-admin:v1

這樣就打包好了鏡像,創建容器的命令也很簡單

docker run -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d xxl-job-admin:v1

運行成功後,瀏覽器輸入http://127.0.0.1:8080/xxl-job-admin/ 鏈接,進入登錄頁,使用admin/123456賬號密碼即可登錄成功。

三、xxl-job的基本概念

運行xxl-job之後,打開 http://127.0.0.1:8080/xxl-job-admin/ 鏈接,可以進入登錄頁,輸入admin/123456登錄成功後看到以下頁面

image-20210421131207154

1.用戶管理

可以創建修改、刪除用戶並且可以授權可以管理哪些執行器

image-20210421131441301

2.執行器管理

所謂的執行器,就是客戶端,任務調度要執行器也就是客戶端去執行具體的任務,執行器可以通過自動註冊和手動註冊兩種方式註冊到xxl-job-admin

image-20210421131722181

3.任務管理

image-20210421132000824

任務管理頁面管理着所有調度任務,每個任務都屬於某個執行器,在這裏可以對任務進行CRUD操作,接下來單獨說下新建任務頁面

四、新建任務

image-20210421132229359

在任務管理頁面點擊新建會跳出該頁面。

1.執行器

該任務屬於哪個執行器,在新建任務前就要存在。

2.任務描述

3.路由策略

image-20210421132520515

路由策略有很多,最經常使用的是第一個、輪詢、隨機策略

4.阻塞處理策略

image-20210421132636090

單擊串行表示隊列阻塞,前一個未完成則先放到隊列中;丟棄後續調度表示前一個任務未完成,如果新的調度任務又開啓了,則丟棄新的任務調度。

我最經常使用的是丟棄後續調度這個阻塞處理策略,一般跑批都沒有嚴格的實時性要求,多一次少一次都無妨。

5.Cron

參考linux下crontab的寫法。

6.JobHandler

執行器執行的handler,需要和java客戶端的jobName保持一致。

7.運行模式

這裏有很多中運行模式,但是最經常使用的是BEAN模式,這種模式下可以指定JobHandler,其它模式下均不可以。

五、實戰

需求:每隔一個小時執行一次任務,更新所有用戶的信息

1.創建執行器

執行器也就是客戶端,這裏假設有個服務update-server作爲執行器

則appName使用update-server,名稱則使用更新服務,註冊方式使用自動註冊

image-20210421135803159

2.新建任務

Cron,每個小時的零分零秒執行任務:0 0 * * * ?

運行模式:BEAN

JobHandler:UpdateUserHandler

阻塞處理策略:丟棄後續調度

image-20210421141706263

3.客戶端配置

也就是執行器的配置了

客戶端一般是java客戶端,如何使用呢,在 源代碼中有個sample模塊,可以參考裏面的使用方法,比如我在springboot中的使用,就可以參考xxl-job-executor-sample-springboot 模塊。

3.1 配置文件配置xxl-job-admin

配置文件格式如下:

xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
    accessToken:
    executor:
      appname: update-server
      logpath: ./logs
      logretentiondays: 30

如何讀取配置文件可以參考我的另外一篇文章:SpringBoot自定義配置以及IDEA配置提示

3.2添加maven依賴

        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
			<version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

3.3創建XxlJobSpringExecutor單例對象

@Configuration
@Slf4j
@AllArgsConstructor
public class XxlJobConfiguration {

    private InetUtils inetUtils;

    private Environment environment;

    private static final String PROFILE_DEV = "dev";

    @Bean
    public XxlJobSpringExecutor myXxlJobExecutor(XxlJobProperty xxlJobProperty) {
        log.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(xxlJobProperty.getAdmin().getAddresses());
        xxlJobSpringExecutor.setAppname(xxlJobProperty.getExecutor().getAppname());
        xxlJobSpringExecutor.setAccessToken(xxlJobProperty.getAccessToken());
        xxlJobSpringExecutor.setLogPath(xxlJobProperty.getExecutor().getLogpath());
        xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperty.getExecutor().getLogretentiondays());
        if(isDevEnv()){
            String ipAddress = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
            xxlJobSpringExecutor.setIp(ipAddress);
            if(Objects.nonNull(xxlJobProperty.getExecutor().getPort())){
                xxlJobSpringExecutor.setPort(xxlJobProperty.getExecutor().getPort());
            }else{
                xxlJobSpringExecutor.setPort(9999);
            }
        }
        return xxlJobSpringExecutor;
    }

    private boolean isDevEnv() {
        String[] activeProfiles = environment.getActiveProfiles();
        return Arrays.asList(activeProfiles).contains(PROFILE_DEV);
    }
}

3.4 創建任務

這裏的XxlJob註解中的value值要和xxl-job-admin中創建的Job名字保持一致

@Component
@Slf4j
public class CronTest {
    @XxlJob("UpdateUserHandler")
    public ReturnT<String> test(String param) throws Exception {
        log.info("Hlelo,world");
        XxlJobLogger.log("跑批結束,本次跑批共新增10條數據");
        return ReturnT.SUCCESS;
    }
}

4.測試

完成上述操作之後啓動服務,如果沒有報錯信息,則到xxl-job-admin查看執行器註冊情況

image-20210421142642349

可以看到已經註冊成功

在任務管理中執行一次任務調度

image-20210421142735604

在客戶端可以看到執行日誌

image-20210421143425195

同時,在xxl-job-admin端也能看到執行結果日誌

image-20210421143529444

image-20210421143546400

六、xxl-job的不足之處

xxl-job在客戶端會單獨開一個接口給xxl-job-admin使用,默認是9999端口號,如果9999端口號被佔用,端口號會依次+1重試。我認爲這裏單獨開一個端口號是完全沒有必要的,浪費執行器資源先不談,開兩個端口號感覺就挺扯,像是swagger ui集成到spring-boot程序中也沒有單獨開一個端口號啊。。

最重要的是多開一個端口號沒問題,問題是這個端口號都是9999,這裏假設幾個場景,看看怎麼做

  1. 所有服務都使用了xxl-job,部署在同一個ECS機器上。每個服務都想佔用默認的9999端口號,第一個佔用成功了,第二個端口號10000,第三個依次增加1。。。。在這個場景下沒問題,每個java程序共享ECS資源,可以探知端口號佔用情況,無端口號衝突。
  2. 所有服務都使用了xxl-job,都使用docker部署,部署在同一個ECS機器上。這時候就不好辦了,運行在docker中的java程序無法知道其他docker中的java程序運行情況,因爲docker把環境隔離了,只能由docker開放指定端口號和容器內運行的java程序端口號映射。這就極大增加了運維成本。

我認爲正確的做法就是複用原來的端口號,這樣一個端口號就能解決問題。

實際上已經有人提了PR:改造在SpringBoot環境下,直接使用SpringBoot端口 但是遲遲沒有被合併,實際上這個開源項目下的issue已經多達五百多個,PR數量也已經近四十個,其實這個項目還是有人繼續維護的,最近的2.3.0版本release在兩個月以前,但是這麼多issue和pr都沒人管,說明作者實際上不關心使用者的感受,只能這麼認爲了。

如果想定製化某些功能,那就去修改源代碼吧,God bless you~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章