[XXL-JOB] 分佈式調度XXL-JOB快速上手

1.概述

1.1什麼是任務調度

我們可以思考一下下面業務場景的解決方案:

  • 某電商平臺需要每天上午10點,下午3點,晚上8點發放一批優惠券

  • 某銀行系統需要在信用卡到期還款日的前三天進行短信提醒

  • 某財務系統需要在每天凌晨0:10分結算前一天的財務數據,統計彙總

以上場景就是任務調度所需要解決的問題

任務調度是爲了自動完成特定任務,在約定的特定時刻去執行任務的過程

1.2 爲什麼需要分佈式調度

使用Spring中提供的註解@Scheduled,也能實現調度的功能

在業務類中方法中貼上這個註解,然後在啓動類上貼上@EnableScheduling註解

@Scheduled(cron = "0/20 * * * * ? ")
 public void doWork(){
     //doSomething   
 }

感覺Spring給我們提供的這個註解可以完成任務調度的功能,好像已經完美解決問題了,爲什麼還需要分佈式呢?

主要有如下這幾點原因:

  1. 高可用:單機版的定式任務調度只能在一臺機器上運行,如果程序或者系統出現異常就會導致功能不可用。

  2. 防止重複執行: 在單機模式下,定時任務是沒什麼問題的。但當我們部署了多臺服務,同時又每臺服務又有定時任務時,若不進行合理的控制在同一時間,只有一個定時任務啓動執行,這時,定時執行的結果就可能存在混亂和錯誤了

  3. 單機處理極限:原本1分鐘內需要處理1萬個訂單,但是現在需要1分鐘內處理10萬個訂單;原來一個統計需要1小時,現在業務方需要10分鐘就統計出來。你也許會說,你也可以多線程、單機多進程處理。的確,多線程並行處理可以提高單位時間的處理效率,但是單機能力畢竟有限(主要是CPU、內存和磁盤),始終會有單機處理不過來的情況。

1.3 XXL-JOB介紹

XXL-Job:是大衆點評的分佈式任務調度平臺,是一個輕量級分佈式任務調度平臺, 其核心設計目標是開發迅速、學習簡單、輕量級、易擴展

大衆點評目前已接入XXL-JOB,該系統在內部已調度約100萬次,表現優異。

目前已有多家公司接入xxl-job,包括比較知名的大衆點評,京東,優信二手車,360金融 (360),聯想集團 (聯想),易信 (網易)等等

官網地址 https://www.xuxueli.com/xxl-job/

系統架構圖

設計思想

將調度行爲抽象形成“調度中心”公共平臺,而平臺自身並不承擔業務邏輯,“調度中心”負責發起調度請求。

將任務抽象成分散的JobHandler,交由“執行器”統一管理,“執行器”負責接收調度請求並執行對應的JobHandler中業務邏輯。

因此,“調度”和“任務”兩部分可以相互解耦,提高系統整體穩定性和擴展性;

2.快速入門

2.1 下載源碼

源碼下載地址:

https://github.com/xuxueli/xxl-job

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

2.1 初始化調度數據庫

請下載項目源碼並解壓,獲取 “調度數據庫初始化SQL腳本” 並執行即可。

“調度數據庫初始化SQL腳本” 位置爲:

/xxl-job/doc/db/tables_xxl_job.sql

2.2 編譯源碼

解壓源碼,按照maven格式將源碼導入IDE, 使用maven進行編譯即可,源碼結構如下:

2.3 配置部署調度中心

2.3.1 調度中心配置

修改xxl-job-admin項目的配置文件application.properties,把數據庫賬號密碼配置上

### web
server.port=8080
server.servlet.context-path=/xxl-job-admin

### actuator
management.server.servlet.context-path=/actuator
management.health.mail.enabled=false

### resources
spring.mvc.servlet.load-on-startup=0
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/

### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.##########

### mybatis
mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
#mybatis.type-aliases-package=com.xxl.job.admin.core.model

### xxl-job, datasource
spring.datasource.url=jdbc:mysql://192.168.202.200:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=WolfCode_2017
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

### datasource-pool
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000

### xxl-job, email
spring.mail.host=smtp.qq.com
spring.mail.port=25
[email protected]
[email protected]
spring.mail.password=xxx
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

### xxl-job, access token
xxl.job.accessToken=default_token

### xxl-job, i18n (default is zh_CN, and you can choose "zh_CN", "zh_TC" and "en")
xxl.job.i18n=zh_CN

## xxl-job, triggerpool max size
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100

### xxl-job, log retention days
xxl.job.logretentiondays=30

2.3.2 部署項目

運行XxlJobAdminApplication程序即可.

調度中心訪問地址: http://localhost:8080/xxl-job-admin

默認登錄賬號 “admin/123456”, 登錄後運行界面如下圖所示。

至此“調度中心”項目已經部署成功。

2.4 配置部署執行器項目

2.4.1 添加Maven依賴

創建SpringBoot項目並且添加如下依賴:

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

2.4.2 執行器配置

在配置文件中添加如下配置:

### 調度中心部署根地址 [選填]:如調度中心集羣部署存在多個地址則用逗號分隔。執行器將會使用該地址進行"執行器心跳註冊"和"任務結果回調";爲空則關閉自動註冊;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 執行器通訊TOKEN [選填]:非空時啓用;
xxl.job.accessToken=default_token
### 執行器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=127.0.0.1
### 執行器端口號 [選填]:小於等於0則自動獲取;默認端口爲9999,單機部署多個執行器時,注意要配置不同執行器端口;
xxl.job.executor.port=9999
### 執行器運行日誌文件存儲磁盤路徑 [選填] :需要對該路徑擁有讀寫權限;爲空則使用默認路徑;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 執行器日誌文件保存天數 [選填] : 過期日誌自動清理, 限制值大於等於3時生效; 否則, 如-1, 關閉自動清理功能;
xxl.job.executor.logretentiondays=30

2.4.3 添加執行器配置

創建XxlJobConfig配置對象:

@Configuration
public class XxlJobConfig {
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    @Value("${xxl.job.executor.appname}")
    private String appname;
    @Value("${xxl.job.executor.address}")
    private String address;
    @Value("${xxl.job.executor.ip}")
    private String ip;
    @Value("${xxl.job.executor.port}")
    private int port;
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @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;
    }
}

2.4.4 添加任務處理類

添加任務處理類,交給Spring容器管理,在處理方法上貼上@XxlJob註解

@Component
public class SimpleXxlJob {
    @XxlJob("demoJobHandler")
    public void demoJobHandler() throws Exception {
        System.out.println("執行定時任務,執行時間:"+new Date());
    }
}

2.5 運行HelloWorld程序

2.5.1 任務配置&觸發執行

登錄調度中心,在任務管理中新增任務,配置內容如下:

新增後界面如下:

 接着啓動定時調度任務

2.5.2 查看日誌

在調度中心的調度日誌中就可以看到,任務的執行結果.

 管控臺也可以看到任務的執行信息.

2.6 GLUE模式(Java)

任務以源碼方式維護在調度中心,支持通過Web IDE在線更新,實時編譯和生效,因此不需要指定JobHandler。

( “GLUE模式(Java)” 運行模式的任務實際上是一段繼承自IJobHandler的Java類代碼,它在執行器項目中運行,可使用@Resource/@Autowire注入執行器裏中的其他服務.

添加Service

@Service
public class HelloService {
    public void methodA(){
        System.out.println("執行MethodA的方法");
    }
    public void methodB(){
        System.out.println("執行MethodB的方法");
    }
}

添加任務配置

 通過GLUE IDE在線編輯代碼

 編寫內容如下:

package com.xxl.job.service.handler;

import cn.wolfcode.xxljobdemo.service.HelloService;
import com.xxl.job.core.handler.IJobHandler;
import org.springframework.beans.factory.annotation.Autowired;

public class DemoGlueJobHandler extends IJobHandler {
    @Autowired
    private HelloService helloService;
    @Override
    public void execute() throws Exception {
        helloService.methodA();
    }
}

啓動並執行程序

2.6 執行器集羣

2.6.1 集羣環境搭建

在IDEA中設置SpringBoot項目運行開啓多個集羣

 

啓動兩個SpringBoot程序,需要修改Tomcat端口和執行器端口

  • Tomcat端口8090程序的命令行參數如下:
-Dserver.port=8090 -Dxxl.job.executor.port=9998
  • Tomcat端口8090程序的命令行參數如下:
-Dserver.port=8091 -Dxxl.job.executor.port=9999

在任務管理中,修改路由策略,修改成輪詢

 重新啓動,我們可以看到效果是,定時任務會在這兩臺機器中進行輪詢的執行

  • 8090端口的控制檯日誌如下:

  •  8091端口的控制檯日誌如下:

 

2.6.2 調度路由算法講解

當執行器集羣部署時,提供豐富的路由策略,包括:

  1. FIRST(第一個):固定選擇第一個機器

  2. LAST(最後一個):固定選擇最後一個機器;

  3. ROUND(輪詢):依次的選擇在線的機器發起調度

  4. RANDOM(隨機):隨機選擇在線的機器;

  5. CONSISTENT_HASH(一致性HASH):

    每個任務按照Hash算法固定選擇某一臺機器,且所有任務均勻散列在不同機器上。

  6. LEAST_FREQUENTLY_USED(最不經常使用):使用頻率最低的機器優先被選舉;

  7. LEAST_RECENTLY_USED(最近最久未使用):最久未使用的機器優先被選舉;

  8. FAILOVER(故障轉移):按照順序依次進行心跳檢測,第一個心跳檢測成功的機器選定爲目標執行器併發起調度;

  9. BUSYOVER(忙碌轉移):按照順序依次進行空閒檢測,第一個空閒檢測成功的機器選定爲目標執行器併發起調度;

  10. SHARDING_BROADCAST(分片廣播):

    廣播觸發對應集羣中所有機器執行一次任務,同時系統自動傳遞分片參數;可根據分片參數開發分片任務;

3. 分片功能講解

3.1 案例需求講解

需求:我們現在實現這樣的需求,在指定節假日,需要給平臺的所有用戶去發送祝福的短信.

3.1.1 初始化數據

在數據庫中導入xxl_job_demo.sql數據

3.1.2 集成Druid&MyBatis

添加依賴

<!--MyBatis驅動-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.2.0</version>
</dependency>
<!--mysql驅動-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok依賴-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>

添加配置

spring.datasource.url=jdbc:mysql://localhost:3306/xxl_job_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=WolfCode_2017

添加實體類

@Setter
@Getter
public class UserMobilePlan {
    private Long id;//主鍵
    private String username;//用戶名
    private String nickname;//暱稱
    private String phone;//手機號碼
    private String info;//備註
}

添加Mapper處理類

@Mapper
public interface UserMobilePlanMapper {
    @Select("select * from t_user_mobile_plan")
    List<UserMobilePlan> selectAll();
}

3.1.3 業務功能實現

任務處理方法實現

@XxlJob("sendMsgHandler")
public void sendMsgHandler() throws Exception{
    List<UserMobilePlan> userMobilePlans = userMobilePlanMapper.selectAll();
    System.out.println("任務開始時間:"+new Date()+",處理任務數量:"+userMobilePlans.size());
    Long startTime = System.currentTimeMillis();
    userMobilePlans.forEach(item->{
        try {
            //模擬發送短信動作
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    System.out.println("任務結束時間:"+new Date());
    System.out.println("任務耗時:"+(System.currentTimeMillis()-startTime)+"毫秒");
}

任務配置信息

3.2 分片概念講解

比如我們的案例中有2000+條數據,如果不採取分片形式的話,任務只會在一臺機器上執行,這樣的話需要20+秒才能執行完任務.

如果採取分片廣播的形式的話,一次任務調度將會廣播觸發對應集羣中所有執行器執行一次任務,同時系統自動傳遞分片參數;可根據分片參數開發分片任務;

獲取分片參數方式:

// 可參考Sample示例執行器中的示例任務"ShardingJobHandler"瞭解試用 
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();

通過這兩個參數,我們可以通過求模取餘的方式,分別查詢,分別執行,這樣的話就可以提高處理的速度.

之前2000+條數據只在一臺機器上執行需要20+秒才能完成任務,分片後,有兩臺機器可以共同完成2000+條數據,每臺機器處理1000+條數據,這樣的話只需要10+秒就能完成任務

3.3 案例改造成任務分片

Mapper增加查詢方法

@Mapper
public interface UserMobilePlanMapper {
    @Select("select * from t_user_mobile_plan where mod(id,#{shardingTotal})=#{shardingIndex}")
    List<UserMobilePlan> selectByMod(@Param("shardingIndex") Integer shardingIndex,@Param("shardingTotal")Integer shardingTotal);
    @Select("select * from t_user_mobile_plan")
    List<UserMobilePlan> selectAll();
}

任務類方法

@XxlJob("sendMsgShardingHandler")
public void sendMsgShardingHandler() throws Exception{
    System.out.println("任務開始時間:"+new Date());
    int shardTotal = XxlJobHelper.getShardTotal();
    int shardIndex = XxlJobHelper.getShardIndex();
    List<UserMobilePlan> userMobilePlans = null;
    if(shardTotal==1){
        //如果沒有分片就直接查詢所有數據
        userMobilePlans = userMobilePlanMapper.selectAll();
    }else{
        userMobilePlans = userMobilePlanMapper.selectByMod(shardIndex,shardTotal);
    }
    System.out.println("處理任務數量:"+userMobilePlans.size());
    Long startTime = System.currentTimeMillis();
    userMobilePlans.forEach(item->{
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    System.out.println("任務結束時間:"+new Date());
    System.out.println("任務耗時:"+(System.currentTimeMillis()-startTime)+"毫秒");
}

任務設置

 

 

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