400ToJava:121-Elastic-Job批量调度框架介绍

一、介绍

1.1 技术介绍

Elastic-Job是一个批量任务调度框架,解决了分布式系统定时完成任务较为困难的问题,下面对任务调度及该框架进行简要介绍。

1.1.1 分布式任务调度简介

任务调度是指系统为了自动完成特定任务,在约定的特定时刻去执行任务的过程。而分布式任务调度即在分布式系统环境下运行任务调度。

1.1.2 Elastic-Job简介

Elastic-Job是基于quartz 二次开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片,解决了quartz框架对于分布式系统支持不够好的问题,具有调度策略丰富,作业分片一致性,支持并行调度等优点。

1.2 项目地址

GitHub主页:
https://github.com/apache/shardingsphere-elasticjob

1.3 程序版本

Elastic-Job版本:2.1.5

二、关键技术

2.1 Elatic-Job整体架构

官方文档中给出了Elastic-Job分布式任务调度框架的整体结构,下面对主要部分进行简要说明。


图-3
  • App:应用程序,内部包含任务执行业务逻辑和Elastic-Job-Lite组件,其中执行任务需要实现ElasticJob接口完成与Elastic-Job-Lite组件的集成,并进行任务的相关配置。应用程序可启动多个实例,也就出现了多个任务执行实例。
  • Elastic-Job-Lite:Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务,此组件负责任务的调度,并产生日志及任务调度记录。
  • Registry:以Zookeeper作为Elastic-Job的注册中心组件,存储了执行任务的相关信息。同时,Elastic-Job利用该组件进行执行任务实例的选举。
  • Console:Elastic-Job提供了运维平台,它通过读取Zookeeper数据展现任务执行状态,或更新Zookeeper数据修改全局配置。通过Elastic-Job-Lite组件产生的数据来查看任务执行历史记录。

2.2 Elatic-Job启动流程

应用程序在启动时,在其内嵌的Elastic-Job-Lite组件会向Zookeeper注册该实例的信息,并触发选举(此时可能已经启动了该应用程序的其他实例),从众多实例中选举出一个Leader,让其执行任务。当到达任务执行时间时,Elastic-Job-Lite组件会调用由应用程序实现的任务业务逻辑,任务执行后会产生任务执行记录。当应用程序的某一个实例宕机时,Zookeeper组件会感知到并重新触发leader选举。

三、环境搭建

Elastic-Job只需要在pom文件中添加依赖即可加入项目,启动时需要先启动Zookeeper作为注册中心

3.1 配置

3.1.1 Zookeeper配置

启动Zookeeper


图-1
3.1.2 Elastic-Job配置

在pom文件中引入Elastic-Job的依赖



    4.0.0

    cn.toj
    elasticjobdemo
    1.0-SNAPSHOT
    jar

    
        UTF-8
        UTF-8
        1.8
    

    
        
            com.dangdang
            elastic-job-lite-core
            2.1.5
        

    


    
        ${project.name}
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    1.8
                    1.8
                
            
        
    




四、Demo开发演示

Demo的主要功能是演示Elastic-Job的定时任务调度及分片功能,模拟多个服务端同时按照规定的时间在控制台输出当前时间,及当一个服务端终止服务时,自动切换的功能。

4.1 Demo概述

4.1.1 整体架构

Demo的整体架构比较简单,主要由两个文件组成,负责任务逻辑的JobConifg 文件及运行任务的JobMain文件。

图-2

4.1.2 任务逻辑

Demo的任务主要在控制台输入当前时间,及当前分片数,分片数由Elatic-Job自动传入的ShardingContext类型的对象提供

其中最重要的是任务逻辑类实现了SimpleJob接口,并在类中实现了execute方法。

package cn.toj.elasticjobdemo.job;

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;

import java.time.LocalDateTime;

/**
 * @author Carlos
 * @description
 * @Date 2020/8/13
 */

public class JobConfig implements SimpleJob {

    //任务执行代码逻辑
    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println("当前分片:" + shardingContext.getShardingItem());
        System.out.printf("当前时间:%s \n", LocalDateTime.now());
    }

}

4.1.3 运行任务

启动类由4部分构成

4.1.3.1 常量

常量设定了zookeeper的地址及该任务在zookeeper中的命名空间,用于区别于其他任务

    //zookeeper端口
    private static final int ZOOKEEPER_PORT = 2181;
    //zookeeper链接字符串 localhost:2181
    private static final String ZOOKEEPER_CONNECTION_STRING = "localhost:" + ZOOKEEPER_PORT;
    //定时任务命名空间
    private static final String JOB_NAMESPACE = "elastic-job-example-java-test";
4.1.3.2 主函数

主函数中确定了整个任务的流程,分为配置注册中心及启动任务

    //执行启动任务
    public static void main(String[] args) {
        //配置注册中心
        CoordinatorRegistryCenter registryCenter = setUpRegistryCenter();
        //启动任务
        startJob(registryCenter);
    }
4.1.3.3 配置注册中心

填入zookeeper注册中心的地址及任务的名称,再创建注册中心,最后启动并返回该注册中心。

    //zk的配置及创建注册中心
    private static CoordinatorRegistryCenter setUpRegistryCenter(){
        //zk的配置
        ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);

        //创建注册中心
        CoordinatorRegistryCenter zookeeperRegistryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);
        zookeeperRegistryCenter.init();
        return zookeeperRegistryCenter;
    }
4.1.3.4 配置及启动任务

传入上一步启动的注册中心,并将任务逻辑类的全限定类名传入任务调度中,根据Cron表达式(3.2节介绍Cron表达式的写法)代表的周期,任务将规定的周期进行运行,本Demo的周期为从0秒开始,每三秒运行一次。

    //任务的配置和启动
    private static void startJob(CoordinatorRegistryCenter registryCenter){
        //String jobName 任务名称, String cron 调度表达式, int shardingTotalCount 作业分片数量
        JobCoreConfiguration jobCoreConfiguration = JobCoreConfiguration.newBuilder("job-test", "0/3 * * * * ?", 1).build();
        //创建SimpleJobConfiguration
        SimpleJobConfiguration simpleJobConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, JobConfig.class.getCanonicalName());
        //创建new JobScheduler
        new JobScheduler(registryCenter, LiteJobConfiguration.newBuilder(simpleJobConfiguration).overwrite(true).build()).init();

    }

4.2 Cron表达式详解

Cron表达式主要用于配置任务调度的周期,可以根据年月日或者星期来进行规定启动时间,如可规定每月末(可为28/29/30/31日)进行启动,由7个子表达式构成,至少由六个子表达式构成;表达式之间由空格构成,每个表达式的意义如下:

1.Seconds (秒)
2.Minutes(分)
3.Hours(小时)
4.Day-of-Month  (天)
5.Month(月)
6.Day-of-Week (周)
7.Year(年)可选

以Demo中的表达式为例:0/3 * * * * ?,其中 0/3表示从0秒开始,每三秒执行一次;
另一个表达式:3 * * * * ?,表示每分钟的第三秒执行一次,即/前的数字为在每个位置的第几,而/后面的数字为以位置为起点,周期为多少。

4.2.1 Cron表达式各个位置的取值范围

表达式 取值范围
Seconds (秒) 0~59
Minutes(分) 0~59
Hours(小时) 0~23
Day-of-Month(天) 1~31(有些月份没有31天)
Month(月) 0~11*
Day-of-Week (周) 1~7
.Year(年) 1970~2099

注:表格中的月份可使用英文简写,JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV,DEC,星期几同样可写成SUN, MON, TUE, WED, THU, FRI, SAT,注意欧美的每周是从周日开始的,所以”周“的”1“是周日,为避免混淆,建议优先使用英文缩写;年份如不需规定可以省略。

4.2.2 字符使用方法

字符使用较为复杂,请看以下各个字符的介绍

  • *代表所有可能的值。因此,*在Month中表示每个月,在Day-of-Month中表示每天,在Hours表示中每小时
  • -表示指定范围,如在”周“位填写MON-THU代表每周的周一到周四运行任务。
  • ,表示枚举间隔,如在“月”位填写FEB,APR,MAY代表在“二月,四月,五月”运行任务。
  • ?比较重要,是为避免"Day-of-Month(天)"和“Day-of-Week (周)”之间产生冲突而写的占位符,如分别填写“21”和“WED”,则会产生某月的21号不是周三的冲突,所以填写这两个其中一个后另一个必须以?进行填充。如Demo中的0/3 * * * * ?填写了“天”后“周”置为?
  • L可以用在“Day-of-Month(天)”和“Day-of-Week (周)”中,是单词“last”的缩写,他们分别表示为“月份的最后一天(28,29,30或31)”及“一周的最后一天(周六SAT)”;同时它还可以接在数字后,如在月份中“5L表示该月的倒数第五天(可能是24,25,26,27日)”,而在周中“FRIL”表示本月的最后一个周五。
  • W是Weekday”的缩写。只能用在“day-of-month(天)”字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第 16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在 day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日,即最后一个星期五。
  • #只能用在“day-of-week(周)”字段。用来指定这个月的第几个周几。例如:在day-of-week字段用"6#3" or "FRI#3"指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。

4.3 任务调度测试

启动main函数,查看控制台输出,从0秒开始,每3秒运行一次

当前分片:0
当前时间:2020-08-17T15:55:24.082 
当前分片:0
当前时间:2020-08-17T15:55:27.009 
当前分片:0
当前时间:2020-08-17T15:55:30.010 
当前分片:0
当前时间:2020-08-17T15:55:33.010 
当前分片:0
当前时间:2020-08-17T15:55:36.009 

修改Cron表达式,设定成每分钟的5秒,及20秒运行,表达式为5,20 * * * * ?,运行结果

当前分片:0
当前时间:2020-08-17T16:01:05.071 
当前分片:0
当前时间:2020-08-17T16:01:20.008 
当前分片:0
当前时间:2020-08-17T16:02:05.010 
当前分片:0
当前时间:2020-08-17T16:02:20.008 
当前分片:0
当前时间:2020-08-17T16:03:05.009 
当前分片:0
当前时间:2020-08-17T16:03:20.010 

4.4 分片测试

JobCoreConfiguration.newBuilder的三个参数分别为:任务名,Cron表达式,分片数,分片数大于1且小于进程数时,相同名称的任务之间通过锁进行争抢,当一个进程中断时,空闲的进程获得锁从而运行;而分片数大于进程数时,一个进程可分配到多于一个分片。以下为各种情况的测试。

4.4.1 一个进程,3个分片

当前进程获得所有分片

当前分片:0
当前分片:1
当前分片:2
当前时间:2020-08-17T16:08:21.074 
当前时间:2020-08-17T16:08:21.074 
当前时间:2020-08-17T16:08:21.074 
当前分片:0
当前时间:2020-08-17T16:08:24.015 
当前分片:1
当前时间:2020-08-17T16:08:24.015 
当前分片:2
当前时间:2020-08-17T16:08:24.015 
当前分片:0
当前时间:2020-08-17T16:08:27.014 
当前分片:1
当前时间:2020-08-17T16:08:27.014 
当前分片:2
当前时间:2020-08-17T16:08:27.015 
4.4.2 两个进程,3个分片

一个进程获得两个,另一个获得一个
进程一:

当前分片:0
当前时间:2020-08-17T16:09:57.013 
当前分片:2
当前时间:2020-08-17T16:09:57.014 
当前分片:0
当前时间:2020-08-17T16:10:00.013 
当前分片:2
当前时间:2020-08-17T16:10:00.013 
当前分片:0
当前时间:2020-08-17T16:10:03.015 
当前分片:2
当前时间:2020-08-17T16:10:03.015 

进程二:

当前分片:1
当前时间:2020-08-17T16:09:57.008 
当前分片:1
当前时间:2020-08-17T16:10:00.010 
当前分片:1
当前时间:2020-08-17T16:10:03.009
4.4.3 三个进程,3个分片

每个进程获得一个分片
进程一:

当前分片:2
当前时间:2020-08-17T16:12:18.047 
当前分片:2
当前时间:2020-08-17T16:12:21.008 
当前分片:2
当前时间:2020-08-17T16:12:24.009

进程二:

当前分片:1
当前时间:2020-08-17T16:12:18.112 
当前分片:1
当前时间:2020-08-17T16:12:21.009 
当前分片:1
当前时间:2020-08-17T16:12:24.008 

进程三:

当前分片:0
当前时间:2020-08-17T16:12:18.073 
当前分片:0
当前时间:2020-08-17T16:12:21.016 
当前分片:0
当前时间:2020-08-17T16:12:24.012
4.4.4 四个进程,三个分片及突然中断一个进程

发生争抢,运行结果和3.4.3 相同,但是有一个进程没有抢到锁所以不运行,处在等待状态,当一个进程突然中断时,该进程获得锁,从而获得一个分片。

以上模拟了多台服务器运行相同的任务的分片情况。

4.5 Demo下载地址

  • GitHub项目地址:
    https://github.com/diyzhang/42j121-elasticjobdemo
  • 使用Git下载项目的命令:
git clone https://github.com/diyzhang/42j121-elasticjobdemo.git

5. Cron表达式测验

  1. 每年的父亲节中午十二点运行任务(父亲节为每年6月的第三个星期日)。
  2. 以下Cron表达式代表的日期:
    0 0 17 ? * TUES,THUR,SAT
    0 15 10 ? * FRIL 2002-2005

你可能感兴趣的:(400ToJava:121-Elastic-Job批量调度框架介绍)