xxl-job分佈式任務調度的使用

 

爲什麼我們需要定時任務

很多業務場景需要我們某一特定的時刻去做某件任務,定時任務解決的就是這種業務場景。一般來說,系統可以使用消息傳遞代替部分定時任務,兩者有很多相似之處,可以相互替換場景。如,上面發貨成功發短信通知客戶的業務場景,我們可以在發貨成功後發送MQ消息到隊列,然後去消費mq消息,發送短信。
但在某些場景下不能互換:

a)時間驅動/事件驅動:內部系統一般可以通過時間來驅動,但涉及到外部系統,則只能使用時間驅動。如怕取外部網站價格,每小時爬一次
b)批量處理/逐條處理:批量處理堆積的數據更加高效,在不需要實時性的情況下比消息中間件更有優勢。而且有的業務邏輯只能批量處理。如移動每個月結算我們的話費
c)實時性/非實時性:消息中間件能夠做到實時處理數據,但是有些情況下並不需要實時,比如:vip升級
d)系統內部/系統解耦:定時任務調度一般是在系統內部,而消息中間件可用於兩個系統間

如果你的項目後期部署到集羣環境下,如果不做處理,就會出現意想不到的問題,原因:由於我們項目同時部署在多臺集羣機器上,因此到達指定的定時時間時,多臺機器上的定時器可能會同時啓動,造成重複數據或者程序異常等問題

java有哪些定時任務的框架

單機

  • timer:是一個定時器類,通過該類可以爲指定的定時任務進行配置。TimerTask類是一個定時任務類,該類實現了Runnable接口,缺點異常未檢查會中止線程
  • ScheduledExecutorService:相對延遲或者週期作爲定時任務調度,缺點沒有絕對的日期或者時間
  • spring定時框架:配置簡單功能較多,如果系統使用單機的話可以優先考慮spring定時器

分佈

  • Quartz:Java事實上的定時任務標準。但Quartz關注點在於定時任務而非數據,並無一套根據數據處理而定製化的流程。雖然Quartz可以基於數據庫實現作業的高可用,但缺少分佈式並行調度的功能
  • TBSchedule:阿里早期開源的分佈式任務調度系統。代碼略陳舊,使用timer而非線程池執行任務調度。衆所周知,timer在處理異常狀況時是有缺陷的。而且TBSchedule作業類型較爲單一,只能是獲取/處理數據一種模式。還有就是文檔缺失比較嚴重
  • elastic-job:噹噹開發的彈性分佈式任務調度系統,功能豐富強大,採用zookeeper實現分佈式協調,實現任務高可用以及分片,目前是版本2.15,並且可以支持雲開發
  • Saturn:是唯品會自主研發的分佈式的定時任務的調度平臺,基於噹噹的elastic-job 版本1開發,並且可以很好的部署到docker容器上。
  • xxl-job: 是大衆點評員工徐雪裏於2015年發佈的分佈式任務調度平臺,是一個輕量級分佈式任務調度框架,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。

 

elastic-job的上次更新時間是兩年前, 所以我們果斷使用xxl-job了.

 

  1. 下載xxl-job的源碼, http://www.xuxueli.com/xxl-job/

這次下載的是2.1.0的版本

  1. 解壓源碼, 導入源碼到idea中去
  2. 執行數據庫的文件

  1. 打開xxl-job-admin 項目,修改配置文件,修改application.properties中的數據庫的配置信息, 然後啓動xxl-job-admin 項目, 訪問 http://localhost:8080/xxl-job-admin 就可以了, 默認登錄賬號 "admin/123456"
  2. 把xxl-job-core 項目達成一個jar 包, 放入到一個新的springboot 暫時名字叫做max-demo項目中去, 添加依賴到pom文件中去,
<!-- xxl-job-core -->
           <dependency>
                    <groupId>com.xuxueli</groupId>
                    <artifactId>xxl-job-core</artifactId>
                   <version>2.1.0</version>
           </dependency>
  1. 修改max-demo的配置文件
# web port
server.port=8081

# log config
logging.config=classpath:logback.xml

### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin

### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip=
xxl.job.executor.port=9999

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

### xxl-job log path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler

### xxl-job log retention days
xxl.job.executor.logretentiondays=-1
  1. 添加日誌的配置文件: logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">

    <contextName>logback</contextName>
    <property name="log.path" value="/data/applogs/xxl-job/xxl-job-executor-sample-springboot.log"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </root>

</configuration>
  1. 添加配置文件:

 

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * xxl-job config
 *
 * @author xuxueli 2017-04-28
 */
@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.executor.appname}")
    private String appName;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean(initMethod = "start", destroyMethod = "destroy")
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppName(appName);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

    /**
     * 針對多網卡、容器內部署等情況,可藉助 "spring-cloud-commons" 提供的 "InetUtils" 組件靈活定製註冊IP;
     *
     *      1、引入依賴:
     *          <dependency>
     *             <groupId>org.springframework.cloud</groupId>
     *             <artifactId>spring-cloud-commons</artifactId>
     *             <version>${version}</version>
     *         </dependency>
     *
     *      2、配置文件,或者容器啓動變量
     *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
     *
     *      3、獲取IP
     *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
     */


}
  1. 添加自己的定時任務處理類
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import org.springframework.stereotype.Component;

/**
 * @author zk
 * @Description:
 * @date 2019-10-09 9:47
 */
@Component
@JobHandler("myJobhandler2")
public class MyJobhandler extends IJobHandler {


    @Override
    public ReturnT<String> execute(String param) throws Exception {
        System.out.println("這是自定義的第二個任務" + param);
        return ReturnT.SUCCESS;
    }
}
  1. 在網頁上新建自己的定時任務.

 

分別是cron 表達式, jobhandker, 就是自定義的一個jobhandler, 名字對應就可以了.

 

執行就是把這個任務執行一次, 啓動就是直接啓動這個定時任務了, 根據cron表達式去執行.

 

 

 

 

 

調度中心集羣

調度中心支持集羣部署,提升調度系統容災和可用性。

調度中心集羣部署時,幾點要求和建議:

  • DB配置保持一致;
  • 登陸賬號配置保持一致;
  • 集羣機器時鐘保持一致(單機集羣忽視);
  • 建議:推薦通過nginx爲調度中心集羣做負載均衡,分配域名。調度中心訪問、執行器回調配置、調用API服務等操作均通過該域名進行。

在原來服務的基礎上新建一個啓動文件, 註釋掉原項目中的端口信息,才用配置文件的形式.增加token,其他不改動.

 

 

 

就可以啓動兩個調度中心了.在任意一個調度中心上新建一個任務, 另一個都會有的.

執行器集羣

執行器支持集羣部署,提升調度系統可用性,同時提升任務處理能力。

執行器集羣部署時,幾點要求和建議:

  • 執行器回調地址(xxl.job.admin.addresses)需要保持一致;執行器根據該配置進行執行器自動註冊等操作。
  • 同一個執行器集羣內AppNamexxl.job.executor.appname)需要保持一致;調度中心根據該配置動態發現不同集羣的在線執行器列表。

 

Web 的端口號和executor的端口號都不能重複.

修改配置文件,添加多個調度中心的地址, 以及token,修改端口號,註釋掉端口號

 

新建一個springboot 啓動類,然後啓動兩個服務,開始執行定時任務,執行了3次任務, 都發到同一個客戶端執行了, 沒有問題,在關閉了一個客戶端之後,再次執行,任務失敗. 過了一會,再執行,任務成功, 已經自動切換到另外一臺應用上了,關閉一個調度中心也是沒有問題的.

 

 

 

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