分布式任務(wù)調(diào)度的解決方案

簡介

 隨著系統(tǒng)規(guī)模的發(fā)展,定時任務(wù)數(shù)量日益增多,任務(wù)也變得越來越復雜,尤其是在分布式環(huán)境下,存在多個業(yè)務(wù)系統(tǒng),每個業(yè)務(wù)系統(tǒng)都有定時任務(wù)的需求,如果都在自身系統(tǒng)中調(diào)度,一方面增加業(yè)務(wù)系統(tǒng)的復雜度,另一方面也不方便管理,因此需要有一個任務(wù)平臺對分散的任務(wù)進行統(tǒng)一管理調(diào)度,基于目前的情況,任務(wù)平臺需要支持以下幾個方面:

1、任務(wù)統(tǒng)一管理,提供圖形化界面對任務(wù)進行配置和調(diào)度。
2、任務(wù)并發(fā)控制,同一個任務(wù)在同一時間只能允許一個執(zhí)行。
3、任務(wù)彈性擴容,可根據(jù)繁忙情況動態(tài)增減服務(wù)器分攤壓力,對大任務(wù)進行分片處理。
4、任務(wù)依賴問題,能夠處理任務(wù)包含子任務(wù)的情況,前一個完成后觸發(fā)子任務(wù)執(zhí)行。
5、支持多類型的任務(wù),支持Spring Bean、Shell等。
6、任務(wù)節(jié)點高可用,任務(wù)節(jié)點異?;蛘叻泵r能夠轉(zhuǎn)移到其他節(jié)點執(zhí)行。
7、調(diào)度中心高可用,支持集群部署,避免出現(xiàn)單點故障。
8、執(zhí)行狀態(tài)監(jiān)控,方便查看任務(wù)執(zhí)行狀態(tài),異常情況告警,支持多渠道通知。

發(fā)展史

定時任務(wù)隨著技術(shù)發(fā)展,從單線程調(diào)度到多線程調(diào)度,從單機部署到集群部署,從獨立執(zhí)行到多任務(wù)協(xié)同執(zhí)行。
file

第一階段
單線程調(diào)度,在Java1.5之前,基于線程的等待(sleep或wait)機制定時執(zhí)行,需要開發(fā)者實現(xiàn)調(diào)度邏輯,單個線程(Thread)處理單個任務(wù)有些浪費,但是一個線程(Timer)處理多個任務(wù)容易因為某個任務(wù)繁忙導致其他任務(wù)阻塞。

第二階段
線程池調(diào)度,在Java1.5開始提供ScheduledExecutorService調(diào)度線程池,調(diào)度線程池支持固定的延時和固定間隔模式,對于需要在某天或者某月的時間點執(zhí)行就不大方便,需要計算時間間隔,轉(zhuǎn)換成啟動延時和固定間隔,處理起來比較麻煩。

第三階段
Spring任務(wù)調(diào)度,Spring簡化了任務(wù)調(diào)度,通過@Scheduled注解支持將某個Bean的方法定時執(zhí)行,除了支持固定延時和固定間隔模式外,還支持cron表達式,使得定時任務(wù)的開發(fā)變得極其簡單。

第四階段
Quartz任務(wù)調(diào)度,在任務(wù)服務(wù)集群部署下,Quartz通過數(shù)據(jù)庫鎖,實現(xiàn)任務(wù)的調(diào)度并發(fā)控制,避免同一個任務(wù)同時執(zhí)行的情況。Quartz通過Scheduler提供了任務(wù)調(diào)度API,開發(fā)可以基于此開發(fā)自己的任務(wù)調(diào)度管理平臺。

第五階段
分布式任務(wù)平臺,提供一個統(tǒng)一的平臺,無需再去做和調(diào)度相關(guān)的開發(fā),業(yè)務(wù)系統(tǒng)只需要實現(xiàn)具體的任務(wù)邏輯,自動注冊到任務(wù)調(diào)度平臺,在上面進行相關(guān)的配置就完成了定時任務(wù)的開發(fā)。

解決方案

現(xiàn)在分布式下任務(wù)調(diào)度有很多解決方案,可以基于Quartz開發(fā)任務(wù)管理平臺,也可以使用開源的任務(wù)調(diào)度平臺,比如xxl-job,elastic-job。

XXL-JOB
大眾點評員工徐雪里于2015年發(fā)布的分布式任務(wù)調(diào)度平臺,是一個輕量級分布式任務(wù)調(diào)度框架,其核心設(shè)計目標是開發(fā)迅速、學習簡單、輕量級、易擴展。官方地址:https://www.xuxueli.com/xxl-job/

file

ELASTIC-JOB
當當開發(fā)的彈性分布式任務(wù)調(diào)度系統(tǒng),功能豐富強大,采用zookeeper實現(xiàn)分布式協(xié)調(diào),實現(xiàn)任務(wù)高可用以及分片,并且可以支持云開發(fā),由兩個相互獨立的子項目Elastic-Job-Lite和Elastic-Job-Cloud組成。官方地址:http://elasticjob.io/docs/elastic-job-lite/00-overview/

file

方案對比

file

集成示例

下面以集成xxl-job為例,xxl-job將定時任務(wù)分為兩個部分:1、調(diào)度中心;2、執(zhí)行器。因此集成xxl-job需要分成兩個步驟,1、部署調(diào)度中心,2、業(yè)務(wù)系統(tǒng)對接(執(zhí)行器)。架構(gòu)圖如下:
file

部署調(diào)度中心

部署前先確定部署方案,測試環(huán)境可以使用1個調(diào)度中心 + 1個mysql服務(wù),生產(chǎn)環(huán)境建議使用2個調(diào)度中心 + mysql主從服務(wù),保證高可用。部署前需確保已經(jīng)準備:Jdk1.8,Maven、mysql,部署步驟如下:

下載xxl-job源碼:http://gitee.com/xuxueli0323/xxl-job/,使用maven編譯打包,生成部署的xxl-job-admin.jar。

創(chuàng)建數(shù)據(jù)庫,并初始化相關(guān)的表,腳本參考源碼目錄doc/db/tables_xxl_job.sql

創(chuàng)建部署目錄,并配置數(shù)據(jù)庫等配置,可在打包之前,在源碼里面application.properties進行配置,也可以在部署目錄里面單獨創(chuàng)建application.properties文件里面進行配置(推薦,spring boot優(yōu)先加載啟動目錄下的配置,可以避免以后更改數(shù)據(jù)庫等配置時還需要重新打包源碼)。

運行管理平臺(請先確保已經(jīng)配置好Java執(zhí)行環(huán)境,Jdk1.8或者以上)

具體步驟參考許雪里博客:https://www.cnblogs.com/xuxueli/p/5021979.html,部署可參考以下腳本:

#下載源碼
cd /root/
wget https://github.com/xuxueli/xxl-job/archive/v2.0.1.zip
unzip v2.0.1.zip
mv xxl-job-2.0.1 xxl-job

#修改配置文件
vim /root/xxl-job/xxl-job-admin/src/main/resources/application.properties
## 修改mysql、郵件等配置

#編譯
cd /root/xxl-job
mvn clean package

# 創(chuàng)建部署目錄
mkdir -p /xxl-job
cd /root/xxl-job/xxl-job-admin/target/
cp /root/xxl-job/xxl-job-admin/target/xxl-job-admin-2.0.1.jar /xxl-job/xxl-job-admin-2.0.1.jar


#mysql 數(shù)據(jù)初始化(用戶名和密碼為root,數(shù)據(jù)庫編碼推薦使用utf8mb4)
mysql -u root -proot -e "source /root/xxl-job/doc/db/tables_xxl_job.sql;"

#啟動
nohup java -jar /xxl-job/xxl-job-admin-2.0.1.jar > /dev/null >& 1 &

#檢查 admin 123456
curl http://localhost:8080/xxl-job-admin

調(diào)度中心啟動成功登錄后如下,默認用戶名admin,默認密碼123456,密碼可在配置文件中更改。

業(yè)務(wù)系統(tǒng)對接

業(yè)務(wù)系統(tǒng)對接調(diào)度中心,需要根據(jù)當前項目的框架進行配置,可以參考源碼xxl-job-executor-samples下例子,下面以業(yè)務(wù)系統(tǒng)基于spring boot框架進行集成。

配置執(zhí)行器

@Configuration
public class XxlJobConfig {
    @Value("${spring.application.name:}")
    private String springAppName;

    @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:9999}")
    private int port;

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

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

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

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        if (StringUtils.isEmpty(appName)) {
            if (StringUtils.isEmpty(springAppName)) {
                throw new IllegalStateException("missing xxl-job appname config");
            }
            appName = springAppName;
        }
        xxlJobSpringExecutor.setAppName(appName);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
}

簡化配置說明:
1、spring boot應(yīng)用基本都有appname,默認使用spring app name配置。
2、ip地址在多網(wǎng)卡、容器的時候需要指定,否則的話,使用默認就可以,spring-cloud-commons中提供了InetUtils工具類,可以幫助獲取IP
3、port可以默認指定一個,如果多個服務(wù)部署在同一臺服務(wù)器上,可以通過檢測獲取或者規(guī)劃分配。
4、logpath最好指定在應(yīng)用目錄下,最好不要使用絕對路徑,避免和其應(yīng)用沖突。
5、logretentiondays日志保留天數(shù)不用太大,根據(jù)需要設(shè)置,默認給一個較短的時間即可。

開發(fā)定時任務(wù)

定義定時任務(wù)有兩種方式:1、2.1.2或者之后版本可以直接在方法上加@XxlJob來聲明任務(wù);2、2.1.2之前版本每個任務(wù)需要單獨開發(fā)一個Bean,實現(xiàn)IJobHandler接口,并且在類上加@JobHandler注解。第二種方式較麻煩,推薦使用第一種方式(目前還沒穩(wěn)定版)。

基于@XxlJob注解代碼方式(建議制定名稱,和調(diào)度中心配置保持一致)

@Component
public class SampleXxlJob {
    private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);

    /**
     * 1、簡單任務(wù)示例(Bean模式)
     */
    @XxlJob("demoJobHandler")
    public ReturnT<String> demoJobHandler(String param) throws Exception {
        XxlJobLogger.log("XXL-JOB, Hello World.");
        for (int i = 0; i < 5; i++) {
            XxlJobLogger.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        return ReturnT.SUCCESS;
    }

    /**
     * 2、分片廣播任務(wù)
     */
    @XxlJob("shardingJobHandler")
    public ReturnT<String> shardingJobHandler(String param) throws Exception {
        // 分片參數(shù)
        ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
        XxlJobLogger.log("分片參數(shù):當前分片序號 = {}, 總分片數(shù) = {}", shardingVO.getIndex(), shardingVO.getTotal());
        // 業(yè)務(wù)邏輯
        for (int i = 0; i < shardingVO.getTotal(); i++) {
            if (i == shardingVO.getIndex()) {
                XxlJobLogger.log("第 {} 片, 命中分片開始處理", i);
            } else {
                XxlJobLogger.log("第 {} 片, 忽略", i);
            }
        }
        return ReturnT.SUCCESS;
    }
}
基于@JobHandler代碼方式

@JobHandler(value="demoJobHandler")
@Component
public class DemoJobHandler extends IJobHandler {
    @Override
    public ReturnT<String> execute(String param) throws Exception {
        XxlJobLogger.log("XXL-JOB, Hello World.");
        for (int i = 0; i < 5; i++) {
            XxlJobLogger.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        return SUCCESS;
    }
}

配置定時任務(wù)

1、配置定時任務(wù),需要先配置執(zhí)行器,推薦使用自動注冊方式,避免集群部署時還需要調(diào)整機器地址,添加界面如下(注意appname要和業(yè)務(wù)系統(tǒng)中配置一致):


file

2、添加完執(zhí)行器后,添加任務(wù),JobHandler要和代碼中配置的名稱一致,執(zhí)行器集群部署可以通過配置路由方式來控制執(zhí)行,xxl-job調(diào)度只支持cron表達式。


file

3、啟動或者執(zhí)行任務(wù),查詢執(zhí)行日志、注冊節(jié)點等


file

集成踩坑記錄

1、任務(wù)服務(wù)器必須做時鐘同步,執(zhí)行器時鐘不能調(diào)度中心180秒,否則將會導致調(diào)度失?。≧PC框架限制)
2、調(diào)度任務(wù)的時間間隔低于實際執(zhí)行耗時,導致產(chǎn)生較大的調(diào)度日志;
3、盡量避免短任務(wù),比如秒級的任務(wù)會導致大量數(shù)據(jù)庫鎖影響性能;
4、調(diào)度日志量偏大導致查詢慢,由于日志都記錄在數(shù)據(jù)庫,需要定時清理;
5、自動注冊時服務(wù)器多網(wǎng)卡導致調(diào)度失敗,注冊時需指定網(wǎng)卡IP;

參考:
http://blog.freshfood.cn/article/39
https://www.cnblogs.com/xuxueli/p/5021979.html
https://www.xuxueli.com/page/projects.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容