Elastic-job分布式任務調度框架

本文將從以下幾個方面展開介紹elastic-job

簡介

Elastic-job是由當當網架構師張亮,曹昊和江樹建基于Zookepper、Quartz開發并開源的一個Java分布式定時任務,由兩個相互獨立的子項目Elastic-Job-Lite和Elastic-Job-Cloud組成。

Elastic-job-lite:
定位為輕量級無中心化解決方案,使用jar包的形式提供分布式任務的協調服務。
僅關注點分布式調度、協調以及分片等核心功能

Elastic-Job-Cloud :
提供一體化私有云服務,將分布式調度、作業部署、資源分配、監控、日志處理等提供完善的解決方案。
功能更加完善,但使用復雜度較高。使用Mesos + Docker(TBD)的解決方案,額外提供資源治理、應用分發以及進程隔離等服務

功能特點:

功能 Elastic-job-lite Elastic-Job-Cloud
分布式調度協調
彈性擴容縮容
失效轉移
錯過執行作業重觸發
作業分片一致性,保證同一分片在分布式環境中僅一個執行實例
支持并行調度
支持作業生命周期操作
豐富的作業類型
Spring整合以及命名空間提供
運維平臺
自診斷并修復分布式不穩定造成的問題
應用自動分發
基于Fenzo的彈性資源分配
基于Docker的進程隔離(TBD)

開發指南

基于目前流行的springboot框架來講解如何在springboot中集成elastic-job,所以在進行下面的操作之前我們先得創建一個springboot項目,至于如何創建springboot項目這里就不做介紹了,訪問這個網站:https://start.spring.io/

  1. 引入maven依賴

    <dependency> 
        <groupId>com.dangdang</groupId> 
        <artifactId>elastic-job-lite-core</artifactId> 
        <version>2.1.5</version> 
    </dependency>
    
  2. 作業開發

    Elastic-Job提供Simple、Dataflow和Script 3種作業類型。 方法參數shardingContext包含作業配置、片和運行時信息。可通過getShardingTotalCount(), getShardingItem()等方法分別獲取分片總數,運行在本作業服務器的分片序列號等。

    2.1.Simple類型作業

    意為簡單實現,未經任何封裝的類型。需實現SimpleJob接口。該接口僅提供單一方法用于覆蓋,此方法將定時執行。與Quartz原生接口相似,但提供了彈性擴縮容和分片等功能。

    public class CourseSimpleJob implements SimpleJob {
        
        @Override
        public void execute(ShardingContext context) {
            switch (context.getShardingItem()) {
                case 0: 
                    // do something by sharding item 0
                    break;
                case 1: 
                    // do something by sharding item 1
                    break;
                case 2: 
                    // do something by sharding item 2
                    break;
                // case n: ...
            }
        }
    }
    

    這里的context.getShardingItem()為當前分片序號,最大值為分片總數減1(n-1)。例如:分片總數為10,那么context.getShardingItem()的最大值則為9。

    2.2.Dataflow類型作業

    Dataflow類型用于處理數據流,需實現DataflowJob接口。該接口提供2個方法可供覆蓋,分別用于抓取(fetchData)和處理(processData)數據。

    public class SpringDataflowJob implements DataflowJob<Foo> {
    
        @Override
        public List<Foo> fetchData(ShardingContext context) {
            switch (context.getShardingItem()) {
                case 0: 
                    List<Foo> data = // get data from database by sharding item 0
                    return data;
                case 1: 
                    List<Foo> data = // get data from database by sharding item 1
                    return data;
                case 2: 
                    List<Foo> data = // get data from database by sharding item 2
                    return data;
                // case n: ...
            }
        }
        
        @Override
        public void processData(ShardingContext shardingContext, List<Foo> data) {
            // process data
            // ...
        }
    }
    

    流式處理

    可通過DataflowJobConfiguration配置是否流式處理。

    流式處理數據只有fetchData方法的返回值為null或集合長度為空時,作業才停止抓取,否則作業將一直運行下去; 非流式處理數據則只會在每次作業執行過程中執行一次fetchData方法和processData方法,隨即完成本次作業。

    如果采用流式作業處理方式,建議processData處理數據后更新其狀態,避免fetchData再次抓取到,從而使得作業永不停止。 流式數據處理參照TbSchedule設計,適用于不間歇的數據處理。

    2.3.Script類型作業

    Script類型作業意為腳本類型作業,支持shell,python,perl等所有類型腳本。只需通過控制臺或代碼配置scriptCommandLine即可,無需編碼。執行腳本路徑可包含參數,參數傳遞完畢后,作業框架會自動追加最后一個參數為作業運行時信息。

    #!/bin/bash
    echo sharding execution context is $*
    
  3. 作業配置

    3.1.zookeeper配置

    新建RegistryCenterConfig類

    @Configuration
    @ConditionalOnExpression("'${regCenter.serverList}'.length() > 0")
    public class RegistryCenterConfig {
    
        private final Logger log = LoggerFactory.getLogger(RegistryCenterConfig.class);
    
        @Bean(initMethod = "init")
        public ZookeeperRegistryCenter regCenter(@Value("${regCenter.serverList}") final String serverList, @Value("${regCenter.namespace}") final String namespace) {
            log.info("---------regCenter.serverList: {}---------", serverList);
            return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace));
        }
    }
    

    在配置文件application.yml中添加這兩個屬性

    regCenter:
        serverList: 127.0.0.1:2181 #zookeeper地址
        namespace: elastic-job-lite-springboot #命名空間
    

    3.2.配置事件追蹤

    關于事件追蹤這里只做代碼展示,具體詳細請參考:http://elasticjob.io/docs/elastic-job-lite/02-guide/event-trace/

    新建JobEventConfig類:

    @Configuration
    public class JobEventConfig {
    
        @Resource
        private DataSource dataSource;
    
        @Bean
        public JobEventConfiguration jobEventConfiguration() {
            return new JobEventRdbConfiguration(dataSource);
        }
    }
    

    在配置文件application.yml中添加數據源相關的配置

    spring:
        datasource:
            url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&verifyServerCertificate=false&useSSL=false&requireSSL=false
            driver-class-name: com.mysql.jdbc.Driver
            username: root
            password: 123456
            tomcat:
                max-wait: 10000
                min-idle: 0
                initial-size: 25
                validation-query: SELECT 1
                test-on-borrow: false
                test-while-idle: true
                time-between-eviction-runs-millis: 18800
                remove-abandoned: true
                remove-abandoned-timeout: 180
    

    這里是在demo中這么配置數據源,一般在現成的項目中都會有相應的數據源配置,無需更改,只需直接將DataSource注入JobEventConfig中即可。

    官方配置

    這里我們先看看官方給的通用配置:

    // 定義作業核心配置
    JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder("demoSimpleJob", "0/15 * * * * ?", 10).build();
    // 定義SIMPLE類型配置
    SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, SimpleDemoJob.class.getCanonicalName());
    // 定義Lite作業根配置
    JobRootConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();
    
    // 定義作業核心配置
    JobCoreConfiguration dataflowCoreConfig = JobCoreConfiguration.newBuilder("demoDataflowJob", "0/30 * * * * ?", 10).build();
    // 定義DATAFLOW類型配置
    DataflowJobConfiguration dataflowJobConfig = new DataflowJobConfiguration(dataflowCoreConfig, DataflowDemoJob.class.getCanonicalName(), true);
    // 定義Lite作業根配置
    JobRootConfiguration dataflowJobRootConfig = LiteJobConfiguration.newBuilder(dataflowJobConfig).build();
    
    // 定義作業核心配置配置
    JobCoreConfiguration scriptCoreConfig = JobCoreConfiguration.newBuilder("demoScriptJob", "0/45 * * * * ?", 10).build();
    // 定義SCRIPT類型配置
    ScriptJobConfiguration scriptJobConfig = new ScriptJobConfiguration(scriptCoreConfig, "test.sh");
    // 定義Lite作業根配置
    JobRootConfiguration scriptJobRootConfig = LiteJobConfiguration.newBuilder(scriptCoreConfig).build();
    

    這里官方只是告訴你他的api是這么用的,但是實際工作中我們肯定不能生搬硬套,不然每一個job都要寫一個配置,那多麻煩,有時候還會忘記。因為在實際工作中肯定是有很多個job,其實我們完全可以寫一個通用的配置類,然后將job配置的相關參數提取到配置文件中或者直接在job類中添加自定義注解,然后在注解中添加相關配置,最后在配置類中去掃描這些job的相關配置并遍歷它生成對應的job配置實例,這樣作業的配置代碼只需要編寫一次即可。

    3.3.Simple類型作業配置

    這里以Simple類型作業為例來簡單的編寫一個Simple類型通用的作業配置

    @Configuration
    public class SimpleJobConfig {
        private static final String      JOB_TYPE                 = "simpleJob";
        public static final  String      CRON                     = "cron";
        public static final  String      SHARDING_TOTAL_COUNT     = "shardingTotalCount";
        public static final  String      SHARDING_ITEM_PARAMETERS = "shardingItemParameters";
        private final        Environment env;
    
        @Resource
        private ZookeeperRegistryCenter regCenter;
    
        @Resource
        private JobEventConfiguration jobEventConfiguration;
    
        @Autowired
        public SimpleJobConfig(Environment env) {
            this.env = env;
        }
    
        @Bean
        public List<JobScheduler> simpleJobScheduler() {
            List<SimpleJob>    simpleJobs    = schedulers();
            List<JobScheduler> jobSchedulers = new ArrayList<>();
            Joiner             joiner        = Joiner.on(".");
            JobScheduler       scheduler;
            for (SimpleJob job : simpleJobs) {
                String propertiesPrefix       = joiner.join(JOB_TYPE, job.getClass().getCanonicalName());
                String cron                   = env.getProperty(joiner.join(propertiesPrefix, CRON));
                int    shardingTotalCount     = Integer.valueOf(env.getProperty(joiner.join(propertiesPrefix, SHARDING_TOTAL_COUNT)));
                String shardingItemParameters = env.getProperty(joiner.join(propertiesPrefix, SHARDING_ITEM_PARAMETERS));
    
                if (shardingTotalCount > 0
                        && StringUtils.isNoneBlank(cron)
                        && StringUtils.isNoneBlank(shardingItemParameters)
                ) {
                    scheduler = new SpringJobScheduler(job, regCenter, getLiteJobConfiguration(job.getClass(), cron, shardingTotalCount, shardingItemParameters), jobEventConfiguration);
                    scheduler.init();
                    jobSchedulers.add(scheduler);
                }
            }
            return jobSchedulers;
        }
    
        private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends SimpleJob> jobClass, final String cron, final int shardingTotalCount, final String shardingItemParameters) {
            return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(JobCoreConfiguration.newBuilder(
                    jobClass.getName(), cron, shardingTotalCount).shardingItemParameters(shardingItemParameters).build(), jobClass.getCanonicalName())).overwrite(true).build();
        }
    
        private List<SimpleJob> schedulers() {
            List<SimpleJob> jobList = new ArrayList<>(ApplicationContextProvider.getBeansOfType(SimpleJob.class).values());
            jobList.forEach(job -> System.out.println(job.getClass().getCanonicalName()));
            return jobList;
        }
    }
    

    在application.yml配置文件中添加以下配置:

    simpleJob:
        com.dangdang.ddframe.job.example.job.simple: #job的包名
            CourseSimpleJob: #job的類名
                cron: 0/30 * * * * ?
                shardingTotalCount: 1
                shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou
            SpringSimpleJob: #job的類名
                cron: 0/25 * * * * ?
                shardingTotalCount: 3
                shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou
    

    很顯然這里的做法是通過將job的相關配置參數集中到一個文件里面,好處是配置參數可以統一起來集中管理。

    3.4.Dataflow類型作業配置

    @Configuration
    public class DataflowJobConfig {
        
        @Resource
        private ZookeeperRegistryCenter regCenter;
        
        @Resource
        private JobEventConfiguration jobEventConfiguration;
        
        @Bean
        public DataflowJob dataflowJob() {
            return new SpringDataflowJob(); 
        }
        
        @Bean(initMethod = "init")
        public JobScheduler dataflowJobScheduler(final DataflowJob dataflowJob, @Value("${dataflowJob.cron}") final String cron, @Value("${dataflowJob.shardingTotalCount}") final int shardingTotalCount,
                                            @Value("${dataflowJob.shardingItemParameters}") final String shardingItemParameters) {
            return new SpringJobScheduler(dataflowJob, regCenter, getLiteJobConfiguration(dataflowJob.getClass(), cron, shardingTotalCount, shardingItemParameters), jobEventConfiguration);
        }
        
        private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends DataflowJob> jobClass, final String cron, final int shardingTotalCount, final String shardingItemParameters) {
            return LiteJobConfiguration.newBuilder(new DataflowJobConfiguration(JobCoreConfiguration.newBuilder(
                    jobClass.getName(), cron, shardingTotalCount).shardingItemParameters(shardingItemParameters).build(), jobClass.getCanonicalName(), true)).overwrite(true).build();
        }
    }
    

    這里在dataflowJobScheduler方法上的@Bean注解中有一個initMethod = "init",這表示在創建JobScheduler的實例之后會調用JobSchedulerinit方法。而在前面2.3.3.Simple類型作業配置中我們是直接顯示的調用init方法的。

    3.5.Script類型作業配置

    腳本類型的作業配置這里就不做展開了,直接參考上面的官方配置即可,或者根據上面的經驗進行簡單的修改。

  4. 作業運行

    正常地啟動springboot項目即可,作業會根據cron參數的配置自動執行。

部署

正常部署應用即可

  1. 啟動Elastic-Job-Lite指定注冊中心的Zookeeper。
  2. 運行包含Elastic-Job-Lite和業務代碼的jar文件。不限與jar或war的啟動方式。

運維平臺(可選)

  1. 解壓縮elastic-job-lite-console-${version}.tar.gz并執行bin\start.sh。

  2. 打開瀏覽器訪問http://localhost:8899/即可訪問控制臺。8899為默認端口號,可通過啟動腳本輸入-p自定義端口號。

    elastic-job-lite-console-${version}.tar.gz可通過mvn install編譯獲取。

最后附上官方文檔大全地址:http://elasticjob.io/docs/elastic-job-lite/00-overview/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容