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

簡(jiǎn)介

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

1、任務(wù)統(tǒng)一管理毡鉴,提供圖形化界面對(duì)任務(wù)進(jìn)行配置和調(diào)度崔泵。
2、任務(wù)并發(fā)控制猪瞬,同一個(gè)任務(wù)在同一時(shí)間只能允許一個(gè)執(zhí)行憎瘸。
3、任務(wù)彈性擴(kuò)容陈瘦,可根據(jù)繁忙情況動(dòng)態(tài)增減服務(wù)器分?jǐn)倝毫细剩瑢?duì)大任務(wù)進(jìn)行分片處理。
4痊项、任務(wù)依賴問題锅风,能夠處理任務(wù)包含子任務(wù)的情況,前一個(gè)完成后觸發(fā)子任務(wù)執(zhí)行鞍泉。
5皱埠、支持多類型的任務(wù),支持Spring Bean咖驮、Shell等边器。
6、任務(wù)節(jié)點(diǎn)高可用托修,任務(wù)節(jié)點(diǎn)異惩桑或者繁忙時(shí)能夠轉(zhuǎn)移到其他節(jié)點(diǎn)執(zhí)行。
7诀黍、調(diào)度中心高可用袋坑,支持集群部署,避免出現(xiàn)單點(diǎn)故障眯勾。
8枣宫、執(zhí)行狀態(tài)監(jiān)控,方便查看任務(wù)執(zhí)行狀態(tài)吃环,異常情況告警也颤,支持多渠道通知。

發(fā)展史

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

第一階段
單線程調(diào)度竭沫,在Java1.5之前燥翅,基于線程的等待(sleep或wait)機(jī)制定時(shí)執(zhí)行,需要開發(fā)者實(shí)現(xiàn)調(diào)度邏輯蜕提,單個(gè)線程(Thread)處理單個(gè)任務(wù)有些浪費(fèi)森书,但是一個(gè)線程(Timer)處理多個(gè)任務(wù)容易因?yàn)槟硞€(gè)任務(wù)繁忙導(dǎo)致其他任務(wù)阻塞。

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

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

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

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

解決方案

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

XXL-JOB
大眾點(diǎn)評(píng)員工徐雪里于2015年發(fā)布的分布式任務(wù)調(diào)度平臺(tái)潜索,是一個(gè)輕量級(jí)分布式任務(wù)調(diào)度框架臭增,其核心設(shè)計(jì)目標(biāo)是開發(fā)迅速、學(xué)習(xí)簡(jiǎn)單竹习、輕量級(jí)誊抛、易擴(kuò)展。官方地址:https://www.xuxueli.com/xxl-job/

file

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

file

方案對(duì)比

file

集成示例

下面以集成xxl-job為例甥郑,xxl-job將定時(shí)任務(wù)分為兩個(gè)部分:1、調(diào)度中心荤西;2澜搅、執(zhí)行器。因此集成xxl-job需要分成兩個(gè)步驟邪锌,1勉躺、部署調(diào)度中心,2觅丰、業(yè)務(wù)系統(tǒng)對(duì)接(執(zhí)行器)饵溅。架構(gòu)圖如下:
file

部署調(diào)度中心

部署前先確定部署方案,測(cè)試環(huán)境可以使用1個(gè)調(diào)度中心 + 1個(gè)mysql服務(wù)妇萄,生產(chǎn)環(huán)境建議使用2個(gè)調(diào)度中心 + mysql主從服務(wù)蜕企,保證高可用。部署前需確保已經(jīng)準(zhǔn)備: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進(jìn)行配置,也可以在部署目錄里面單獨(dú)創(chuàng)建application.properties文件里面進(jìn)行配置(推薦根灯,spring boot優(yōu)先加載啟動(dòng)目錄下的配置径缅,可以避免以后更改數(shù)據(jù)庫等配置時(shí)還需要重新打包源碼)掺栅。

運(yùn)行管理平臺(tái)(請(qǐng)先確保已經(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;"

#啟動(dòng)
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)度中心啟動(dòng)成功登錄后如下氏堤,默認(rèn)用戶名admin沙绝,默認(rèn)密碼123456,密碼可在配置文件中更改鼠锈。

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

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

配置執(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;
    }
}

簡(jiǎn)化配置說明:
1、spring boot應(yīng)用基本都有appname同欠,默認(rèn)使用spring app name配置样傍。
2、ip地址在多網(wǎng)卡铺遂、容器的時(shí)候需要指定衫哥,否則的話,使用默認(rèn)就可以襟锐,spring-cloud-commons中提供了InetUtils工具類撤逢,可以幫助獲取IP
3、port可以默認(rèn)指定一個(gè)捌斧,如果多個(gè)服務(wù)部署在同一臺(tái)服務(wù)器上笛质,可以通過檢測(cè)獲取或者規(guī)劃分配。
4捞蚂、logpath最好指定在應(yīng)用目錄下妇押,最好不要使用絕對(duì)路徑,避免和其應(yīng)用沖突姓迅。
5敲霍、logretentiondays日志保留天數(shù)不用太大,根據(jù)需要設(shè)置丁存,默認(rèn)給一個(gè)較短的時(shí)間即可肩杈。

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

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

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

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

    /**
     * 1翻斟、簡(jiǎn)單任務(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ù):當(dāng)前分片序號(hào) = {}, 總分片數(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;
    }
}

配置定時(shí)任務(wù)

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


file

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


file

3济锄、啟動(dòng)或者執(zhí)行任務(wù)暑椰,查詢執(zhí)行日志、注冊(cè)節(jié)點(diǎn)等


file

集成踩坑記錄

1荐绝、任務(wù)服務(wù)器必須做時(shí)鐘同步一汽,執(zhí)行器時(shí)鐘不能調(diào)度中心180秒,否則將會(huì)導(dǎo)致調(diào)度失數吞病(RPC框架限制)
2召夹、調(diào)度任務(wù)的時(shí)間間隔低于實(shí)際執(zhí)行耗時(shí),導(dǎo)致產(chǎn)生較大的調(diào)度日志恕沫;
3监憎、盡量避免短任務(wù),比如秒級(jí)的任務(wù)會(huì)導(dǎo)致大量數(shù)據(jù)庫鎖影響性能婶溯;
4鲸阔、調(diào)度日志量偏大導(dǎo)致查詢慢,由于日志都記錄在數(shù)據(jù)庫迄委,需要定時(shí)清理褐筛;
5、自動(dòng)注冊(cè)時(shí)服務(wù)器多網(wǎng)卡導(dǎo)致調(diào)度失敗叙身,注冊(cè)時(shí)需指定網(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市信轿,隨后出現(xiàn)的幾起案子晃痴,更是在濱河造成了極大的恐慌残吩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愧旦,死亡現(xiàn)場(chǎng)離奇詭異世剖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笤虫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門旁瘫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琼蚯,你說我怎么就攤上這事酬凳。” “怎么了遭庶?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵宁仔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我峦睡,道長(zhǎng)翎苫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任榨了,我火速辦了婚禮煎谍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘龙屉。我一直安慰自己呐粘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布转捕。 她就那樣靜靜地躺著作岖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪五芝。 梳的紋絲不亂的頭發(fā)上痘儡,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音枢步,去河邊找鬼谤辜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛价捧,可吹牛的內(nèi)容都是我干的丑念。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼结蟋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脯倚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤推正,失蹤者是張志新(化名)和其女友劉穎恍涂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體植榕,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡再沧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尊残。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炒瘸。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哄陶,死狀恐怖橘忱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情袖订,我是刑警寧澤慰毅,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布隘截,位于F島的核電站,受9級(jí)特大地震影響汹胃,放射性物質(zhì)發(fā)生泄漏婶芭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一着饥、第九天 我趴在偏房一處隱蔽的房頂上張望雕擂。 院中可真熱鬧,春花似錦贱勃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至流部,卻和暖如春戚绕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枝冀。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國打工舞丛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人果漾。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓球切,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親绒障。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吨凑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容