SpringBoot整合分布式任务调度Elastic-Job

Elastic-Job介绍

elastic-job是当当网基于quartz 二次开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片。Elastic-Job是一个分布式调度的解决方案,由当当网开源,它由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成,使用Elastic-Job可以快速实现分布式任务调度。

ElasticJob 已于 2020 年 5 月 28 日成为 Apache ShardingSphere 的子项目。

官方文档:https://shardingsphere.apache.org/elasticjob/index_zh.html

zk在elastic-job的作用

  • Elastic-Job依赖ZooKeeper完成对执行任务信息的存储(如任务名称、任务参与实例、任务执行策略等);
  • Elastic-Job依赖ZooKeeper实现选举机制,在任务执行实例数量变化时(如在快速上手中的启动新实例或停止实例),会触发选举机制来决定让哪个实例去执行该任务。

功能特性

分布式调度协调

在分布式环境中,任务能够按指定的调度策略执行,并且能够避免同一任务多实例重复执行

弹性扩容缩容

当集群中增加某一个实例,它也能够被选举并执行任务;当集群减少一个实例时,它所执行的任务能被转移到别的实例来执行。

丰富的作业类型

支持Simple、DataFlow、Script三种作业类型。

丰富的调度策略

基于成熟的定时任务作业框架Quartz cron表达式执行定时任务。

失效转移

某实例在任务执行失败后,会被转移到其他实例执行。

错过执行作业重触发

若因某种原因导致作业错过执行,自动记录错过执行的作业,并在上次作业完成后自动触发。

支持并行调度

支持任务分片,任务分片是指将一个任务分为多个小任务项在多个实例同时执行。

作业分片一致性

当任务被分片后,保证同一分片在分布式环境中仅一个执行实例。

支持作业生命周期操作

可以动态对任务进行开启及停止操作。

Spring 整合以及命名空间支持

对Spring支持良好的整合方式,支持spring自定义命名空间,支持占位符。

运维平台

提供运维界面,可以管理作业和注册中心。

SpringBoot集成Elastic-Job

非场景启动器starter

引入相关依赖

<!-- elastic job-->
<dependency>
    <groupId>com.dangdang</groupId>
    <artifactId>elastic-job-lite-spring</artifactId>
    <version>2.1.5</version>
</dependency>
<dependency>
    <groupId>com.dangdang</groupId>
    <artifactId>elastic-job-lite-core</artifactId>
    <version>2.1.5</version>
</dependency>

application.yml配置elasticjob

server:
  port: 9999

# 分布式任务调度自定义配置参数
elasticjob:
  # 注册中心
  reg-center:
    server-list: 192.168.198.155:2182,192.168.198.155:2183,192.168.198.155:2184 #zookeeper地址
    namespace: elastic-job-springboot
  # 作业配置
  jobs:
    list:
      - clazz: com.harvey.demo.job.SpringBootJob
        cron: 0/20 * * * * ?
        shardingTotalCount: 2
        shardingItemParam: 0=text,1=image
        jobParam: name=test
        jobName: SpringBootJob # 本实例中没有特殊含义
      - clazz: com.harvey.demo.job.SpringRunnerJob
        cron: 0/20 * * * * ?
        shardingTotalCount: 3
        shardingItemParam: 0=text,1=image,2=video
        jobParam: name=test
        jobName: SpringRunnerJobs # 本实例中没有特殊含义

注册中心、job实例配置

1)job定义

第一个job:

@Component
public class SpringBootJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println(this.getClass() + ":分片总数是: [{" + shardingContext.getShardingTotalCount() + "}]; 当前分片项是: [{" + shardingContext.getShardingItem() + "}]");
        //分片参数,(0=text,1=image,2=video,参数就是text、image...)
        String shardingParameter = shardingContext.getShardingParameter();
        //任务参数, 配置是name=test就是name=test
        String jobParameter = shardingContext.getJobParameter();
        System.out.println("current sharding item " + shardingContext.getShardingItem() + ", shardingParameter = " + shardingParameter + ", jobParameter:" + jobParameter);
    }
}

第二个job:

@Component
public class SpringRunnerJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println(this.getClass() + ":分片总数是: [{" + shardingContext.getShardingTotalCount() + "}]; 当前分片项是: [{" + shardingContext.getShardingItem() + "}]");
        //分片参数,(0=text,1=image,2=video,参数就是text、image...)
        String shardingParameter = shardingContext.getShardingParameter();
        //任务参数
        String jobParameter = shardingContext.getJobParameter();
        System.out.println("current sharding item " + shardingContext.getShardingItem() + ", shardingParameter = " + shardingParameter + ", jobParameter:" + jobParameter);
    }
}

2)注册中心

@Configuration
public class ElasticJobRegistryCenterConfig {

    @Value("${elasticjob.reg-center.server-list}")
    private String zookeeperAddressList;

    @Value("${elasticjob.reg-center.namespace}")
    private String namespace;

    /****
     * 注册中心
     * @return
     */
    @Bean(initMethod = "init")
    public CoordinatorRegistryCenter zkCenter() {
        // 1 zk的配置
        // 参数一:zk的地址,如果是集群,每个地址用逗号隔开
        ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration(zookeeperAddressList, namespace);
        // 创建协调注册中心
        // 2 CoordinatorRegistryCenter接口,elastic-job提供了一个实现ZookeeperRegistryCenter
        CoordinatorRegistryCenter zookeeperRegistryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);
        // 3 注册中心初始化
        //zookeeperRegistryCenter.init();
        return zookeeperRegistryCenter;
    }
}

3)job配置

job基础信息:

/**
 * job基础信息
 */
public class JobScheduler {

    /**
     * 继承ElasticJob的类的全路径名称
     */
    private String clazz;
    /**
     * cron表达式
     */
    private String cron;
    /**
     * 分片数
     */
    private int shardingTotalCount;
    /**
     * 分片参数
     */
    private String shardingItemParam;

    /**
     * 任务参数
     */
    private String jobParam;

    /**
     * job名称
     */
    private String jobName;

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }

    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }

    public int getShardingTotalCount() {
        return shardingTotalCount;
    }

    public void setShardingTotalCount(int shardingTotalCount) {
        this.shardingTotalCount = shardingTotalCount;
    }

    public String getShardingItemParam() {
        return shardingItemParam;
    }

    public void setShardingItemParam(String shardingItemParam) {
        this.shardingItemParam = shardingItemParam;
    }

    public String getJobParam() {
        return jobParam;
    }

    public void setJobParam(String jobParam) {
        this.jobParam = jobParam;
    }

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }
}

SpringContextUtil工具类

/**
 * 获取springBean对象
 */
@Component
public class SpringContextUtil implements ApplicationContextAware {
    // spring上下文
    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public <T> T getBean(Class<T> tClass) {
        return applicationContext.getBean(tClass);
    }

    public <T> Map<String, T> getBeansOfType(Class<T> tClass) {
        return applicationContext.getBeansOfType(tClass);
    }

    /**
     * 获取接口下的所有实现类
     *
     * @param tClass
     * @param <T>
     */
    public <T> List<T> getBeansOfTypeList(Class<T> tClass) {
        Map<String, T> beanOfType = getBeansOfType(tClass);
        List<T> list = new ArrayList<>();
        beanOfType.forEach((k, v) -> {
            list.add(v);
        });
        return list;
    }
}

job配置并启动

@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "elasticjob.jobs")
public class ElasticJobConfig implements InitializingBean {

    /**
     * 任务列表job
     */
    private List<JobScheduler> list;
    public void setList(List<JobScheduler> list) {
        this.list = list;
    }

    @Autowired
    private CoordinatorRegistryCenter zkCenter;

    @Autowired
    private SpringContextUtil springContextUtil;

    @Override
    public void afterPropertiesSet() throws Exception {
        //这里可以替换为从数据库查询,将JobScheduler的信息配置到表里
        Map<String, JobScheduler> jobSchedulerMap = new HashMap(list.size());
        for (JobScheduler scheduler : list) {
            jobSchedulerMap.put(scheduler.getClazz(), scheduler);
        }
        //收集所有实现SimpleJob接口的job实例
        Map<String, SimpleJob> simpleJobMap = springContextUtil.getApplicationContext().getBeansOfType(SimpleJob.class);
        Collection<SimpleJob> simpleJobs = simpleJobMap.values();
        for (SimpleJob simpleJob : simpleJobs) {
            //根据job实例的全类名获取JobScheduler信息
            JobScheduler jobScheduler = jobSchedulerMap.get(simpleJob.getClass().getName());
            if (jobScheduler == null) {
                throw new RuntimeException("请配置[" + simpleJob.getClass().getName() + "]的JobScheduler");
            }
            System.out.println("job scheduler init :" + jobScheduler.getClazz());
            SpringJobScheduler springJobScheduler = new SpringJobScheduler(simpleJob, zkCenter,
                    liteJobConfiguration(simpleJob.getClass(), jobScheduler.getCron(), jobScheduler.getShardingTotalCount(),
                            jobScheduler.getShardingItemParam(), jobScheduler.getJobParam(), jobScheduler.getJobName()));
            //启动任务
            springJobScheduler.init();
        }
    }

    private LiteJobConfiguration liteJobConfiguration(final Class<? extends ElasticJob> jobClass,
                                                      final String cron,
                                                      final int shardingTotalCount,
                                                      final String shardingItemParam,
                                                      final String jobParam,
                                                      final String jobName
    ) {
        // 1 job核心配置
        JobCoreConfiguration.Builder builder = JobCoreConfiguration.newBuilder(
                // job的名字
                jobName,
                // cron表达式
                cron,
                // 分片总数
                shardingTotalCount
        );
        // 分片参数不是空,设置分片参数
        if (StringUtils.isNotBlank(shardingItemParam)) {
            builder.shardingItemParameters(shardingItemParam);
        }
        // 任务参数不是空,设置任务参数
        if(StringUtils.isNotBlank(jobParam)){
            builder.jobParameter(jobParam);
        }
        JobCoreConfiguration jobCoreConfiguration = builder.build();
        // 2 job类型配置
        // 参数1: job核心配置JobCoreConfiguration
        // 参数2 job的全类名
        JobTypeConfiguration jobTypeConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, jobClass.getCanonicalName());
        // 3 job根配置(LiteJobConfiguration)
        LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder(jobTypeConfiguration)
                .overwrite(true)
                .build();
        return liteJobConfiguration;
    }
}

启动项目,日志打印:

采用场景启动器starter

引入相关依赖

<!-- 引入版本的时候要充分考虑自己的Zookeeper Server版本, 建议与其保持一致 -->
<!-- 例如 版本使用的是 zk 3.6.x, Zookeeper Server 也是3.6.x, 如果使用3.4.x则会报错 -->
<dependency>
    <groupId>org.apache.shardingsphere.elasticjob</groupId>
    <artifactId>elasticjob-lite-spring-boot-starter</artifactId>
    <version>3.0.1</version>
</dependency>

application.yml中配置注册中心和作业调度

server:
  port: 9999

#elasticjob配置
elasticjob:
  # 注册中心配置
  reg-center:
    # 连接 ZooKeeper 服务器的列表, 包括 IP 地址和端口号,多个地址用逗号分隔
    server-lists: 127.0.0.1:2188
    # ZooKeeper 的命名空间
    namespace: elastic-job-spring
    # 等待重试的间隔时间的初始毫秒数
    base-sleep-time-milliseconds: 1000
    # 等待重试的间隔时间的最大毫秒数
    maxSleepTimeMilliseconds: 3000
    # 最大重试次数
    maxRetries: 3
    # 会话超时毫秒数
    sessionTimeoutMilliseconds: 60000
    # 连接超时毫秒数
    connectionTimeoutMilliseconds: 15000
  # 作业配置, 更多的配置参考官网
  jobs:
    springJob: # job名
      elasticJobClass: com.harvey.demo.job.SpringBootJob
      cron: 0/5 * * * * ?
      shardingTotalCount: 2
      shardingItemParameters: 0=Beijing,1=Shanghai
    springTestJob:
      elasticJobClass: com.harvey.demo.job.SpringRunnerJob
      cron: 0/10 * * * * ?
      shardingTotalCount: 3
      shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou

job实例

第一个job:

@Component
public class SpringBootJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println(this.getClass() + ":分片总数是: [{" + shardingContext.getShardingTotalCount() + "}]; 当前分片项是: [{" + shardingContext.getShardingItem() + "}]");
        //分片参数,(0=text,1=image,2=video,参数就是text、image...)
        String shardingParameter = shardingContext.getShardingParameter();
        //任务参数, 配置是name=test就是name=test
        String jobParameter = shardingContext.getJobParameter();
        System.out.println("current sharding item " + shardingContext.getShardingItem() + ", shardingParameter = " + shardingParameter + ", jobParameter:" + jobParameter);
    }
}

第二个job:

@Component
public class SpringRunnerJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println(this.getClass() + ":分片总数是: [{" + shardingContext.getShardingTotalCount() + "}]; 当前分片项是: [{" + shardingContext.getShardingItem() + "}]");
        //分片参数,(0=text,1=image,2=video,参数就是text、image...)
        String shardingParameter = shardingContext.getShardingParameter();
        //任务参数
        String jobParameter = shardingContext.getJobParameter();
        System.out.println("current sharding item " + shardingContext.getShardingItem() + ", shardingParameter = " + shardingParameter + ", jobParameter:" + jobParameter);
    }
}

启动项目,可以看到相关的日志。

elastic-job控制台 v2.1.5

适用于前面讲的 【非场景启动器starter】形式开发的elastic-job。本人尝试将基于场景启动器starter开发的elastic-job注册在这个控制台,在【作业操作-作业维度】的列表中报错。

下载elastic-job-console

下载地址:https://gitee.com/kancy666/elastic-job-console/releases/2.1.5

解压后启动

(本人是在windows启动)

访问控制台

http://127.0.0.1:8899/# ,默认的账号密码是root/root

控制台操作说明

配置Zookeeper信息

监控平台配置zookeeper的信息是关键,只有连通了zookeeper才能对定时任务进行操作。

1)设置界面语言

点击右上角【选择语言】,下拉出现【中文】进行切换。

2)左边菜单点击【全局配置】选中【注册中心配置】,然后出现一个已配置列表。

在列表的下方有个【添加】按钮,点击添加按钮进行添加

添加的字段说明:

  • 注册中心名称:自定义,用于当前列表显示,便于区分
  • 注册中心地址:zookeeper的地址,需要连接哪个就填写哪个 【IP:端口】
  • 命名空间:任务创建ZookeeperRegistryCenter的时候填写namespace,要对应上,才能看到对应下的任务
  • 登录凭证:可不填,默认zookeeper不需要填写,除非设置了zookeeper相关信息

填写完之后,点击右下角【提交】按钮,即完成。

列表中出现新增的注册配置,然后点击该配置最后的操作项【连接】,则连接上zookeeper。

作业操作

1)作业维度

查看当前挂在zookeeper的命名空间下的所有任务,提供删除,编辑,触发,失效等一系列功能。

2)修改作业

3)服务器维度

查看当前连着zookeeper的服务器,提供删除,失效,终止等一些列功能。

点击【详情】可以看到该服务器下的正在运行的定时任务。

ElasticJob-UI监控平台

适用于【场景启动器starter】方式开发的elastic-job。

下载ElasticJob-UI

https://shardingsphere.apache.org/elasticjob/current/cn/downloads/

解压后在bin目录双击启动

(本人是在windows环境)

访问控制台

启动后游览器访问(默认端口是8088):http://127.0.0.1:8088/#/login 用户名/密码 root/root

登录成功后,链接上注册中心,链接成功后便可以进行任务的管理。

 

posted @ 2022-04-24 14:39  残城碎梦  阅读(1405)  评论(1编辑  收藏  举报