Elastic-Job创建动态定时任务、动态修改定时任务

本文详细介绍了如何在ElasticJob中实现动态添加、修改和删除定时任务,包括配置步骤、关键类和API的使用。从静态定时任务创建到动态任务数据库管理,再到操作工具的实现,全面展示了ElasticJob在互联网场景中的调度能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Elastic-Job实现动态增加定时任务、动态修改定时任务。

一、ElasticJob简介

ElasticJob 是面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目 ElasticJob-Lite 和 ElasticJob-Cloud 组成。 它通过弹性调度、资源管控、以及作业治理的功能,打造一个适用于互联网场景的分布式调度解决方案,并通过开放的架构设计,提供多元化的作业生态。 它的各个产品使用统一的作业 API,开发者仅需一次开发,即可随意部署。

二、使用Elastic Job创建静态定时任务

2.1引入依赖包

<dependency>
            <groupId>com.github.kuhn-he</groupId>
            <artifactId>elastic-job-lite-spring-boot-starter</artifactId>
            <version>2.1.5</version>
</dependency>

或者用

        <dependency>
            <groupId>com.github.xjzrc.spring.boot</groupId>
            <artifactId>elastic-job-lite-spring-boot-starter</artifactId>
            <version>1.0.1</version>
        </dependency>

2.2添加配置信息

spring:
  elasticjob:
    zookeeper:      
      server-lists: 你自己zk的IP地址:2181
      namespace:daemon-job

2.3简单作业

实现 SimpleJob 接口。 该接口仅提供单一方法用于覆盖,此方法将定时执行。 与Quartz原生接口相似,但提供了弹性扩缩容和分片等功能。


import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.zen.elasticjob.spring.boot.annotation.ElasticJobConfig;
import lombok.extern.slf4j.Slf4j;

/**
 * @author songfayuan
 * @date 2018/2/7
 * 测试Job
 */
@Slf4j
@ElasticJobConfig(cron = "0 45 0 * * ? *", shardingTotalCount = 3,
        shardingItemParameters = "0=collection1,1=collection2,2=collection3",
        startedTimeoutMilliseconds = 5000L,
        completedTimeoutMilliseconds = 10000L,
        eventTraceRdbDataSource = "dataSource",
        description = "测试案例")
public class SimpleJob implements SimpleJob {
    /**
     * 业务执行逻辑
     *
     * @param shardingContext 分片信息
     */
    @Override
    public void execute(ShardingContext shardingContext) {
        log.info("shardingContext:{}", shardingContext);
    }
}

2.4数据流作业

用于处理数据流,需实现 DataflowJob 接口。 该接口提供2个方法可供覆盖,分别用于抓取 (fetchData) 和处理 (processData) 数据。


import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.dataflow.DataflowJob;
import com.zen.elasticjob.spring.boot.annotation.ElasticJobConfig;

import java.util.List;

/**
 * @author songfayuan
 * @date 2018/2/8
 */
@ElasticJobConfig(cron = "0 45 0 * * ? *", shardingTotalCount = 3,
        shardingItemParameters = "0=Beijing,1=Shanghai,2=Guangzhou",
        description = "测试案例")
public class AmazonDataflowJob implements DataflowJob<Integer> {


    @Override
    public List<Integer> fetchData(ShardingContext shardingContext) {
        return null;
    }

    @Override
    public void processData(ShardingContext shardingContext, List<Integer> list) {

    }
}

三、使用Elastic Job创建动态定时任务

3.1创建存储动态任务的数据库表

如果没有这个本地持久化的数据,那么你的任务创建以后,如果Elastic Job服务挂掉,重启以后,就没有这个任务了…

CREATE TABLE `job_dynamic_task` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `is_delete` int(4) NOT NULL DEFAULT '0' COMMENT '是否删除(0否 1是)',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `job_name` varchar(200) NOT NULL DEFAULT '' COMMENT '任务名称',
  `cron` varchar(100) NOT NULL COMMENT 'cron表达式',
  `description` varchar(255) NOT NULL DEFAULT '' COMMENT '作业描述',
  `parameters` text NOT NULL COMMENT '参数',
  `status` int(11) NOT NULL DEFAULT '-1' COMMENT '状态:0未执行 1已执行',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='Elastic Job 自定义定时任务(动态任务)';

在这里插入图片描述

3.2初始化配置(DynamicElasticJobConfig)

import com.dangdang.ddframe.job.event.JobEventConfiguration;
import com.dangdang.ddframe.job.event.rdb.JobEventRdbConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.github.amazon.daemon.utils.ElasticJobListener;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * Elastic Job 动态定时任务配置
 * @author songfayuan
 * @date 2021/4/26 9:23 上午
 */
@Configuration
public class DynamicElasticJobConfig {
    @Value("${spring.elasticjob.zookeeper.server-lists}")
    private String serverLists;
    @Value("${spring.elasticjob.zookeeper.namespace}")
    private String namespace;
    @Resource
    private HikariDataSource dataSource;

    @Bean
    public ZookeeperConfiguration zookeeperConfiguration() {
        return new ZookeeperConfiguration(serverLists, namespace);
    }

    @Bean(initMethod = "init")
    public ZookeeperRegistryCenter zookeeperRegistryCenter(ZookeeperConfiguration zookeeperConfiguration){
        return new ZookeeperRegistryCenter(zookeeperConfiguration);
    }

    @Bean
    public ElasticJobListener elasticJobListener(){
        return new ElasticJobListener(100, 100);
    }

    /**
     * 将作业运行的痕迹进行持久化到DB
     *
     * @return
     */
    @Bean
    public JobEventConfiguration jobEventConfiguration() {
        return new JobEventRdbConfiguration(dataSource);
    }

}

3.3动态定时任务相关操作工具(ElasticJobHandler)

import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.dangdang.ddframe.job.event.JobEventConfiguration;
import com.dangdang.ddframe.job.lite.api.listener.ElasticJobListener;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.internal.schedule.JobRegistry;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.github.amazon.common.constant.enums.DingTokenEnum;
import com.github.amazon.common.util.DingDingMsgSendUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 动态定时任务相关操作工具
 * @author songfayuan
 * @date 2021/4/26 10:54 上午
 */
@Slf4j
@Component
public class ElasticJobHandler {

    @Autowired
    private ZookeeperRegistryCenter zookeeperRegistryCenter;
    @Resource
    private JobEventConfiguration jobEventConfiguration;
    @Resource
    private ElasticJobListener elasticJobListener;

    /***
     * 动态创建定时任务
     * @param jobName:定时任务名称
     * @param cron:表达式
     * @param shardingTotalCount:分片数量
     * @param instance:定时任务实例
     * @param parameters:参数
     * @param description:作业描述
     */
    public void addJob(String jobName, String cron, int shardingTotalCount, SimpleJob instance, String parameters, String description){
        log.info("动态创建定时任务:jobName = {}, cron = {}, shardingTotalCount = {}, parameters = {}", jobName, cron, shardingTotalCount, parameters);
        DingDingMsgSendUtils.sendDingDingGroupMsgNoAt(DingTokenEnum.DYNAMIC_SCHEDULED_TASK_NOTIFICATION.getToken(), "正在动态创建定时任务:jobName = "+jobName+",cron = "+cron+",parameters = "+parameters+",shardingTotalCount = "+shardingTotalCount);

        LiteJobConfiguration.Builder builder = LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(
                JobCoreConfiguration.newBuilder(
                        jobName,
                        cron,
                        shardingTotalCount
                ).jobParameter(parameters).description(description).build(),
                instance.getClass().getName()
        )).overwrite(true);
        LiteJobConfiguration liteJobConfiguration = builder.build();

        new SpringJobScheduler(instance,zookeeperRegistryCenter,liteJobConfiguration,jobEventConfiguration,elasticJobListener).init();
    }

    /**
     * 更新定时任务
     * @param jobName
     * @param cron
     */
    public void updateJob(String jobName, String cron) {
        log.info("更新定时任务:jobName = {}, cron = {}", jobName, cron);
        DingDingMsgSendUtils.sendDingDingGroupMsgNoAt(DingTokenEnum.DYNAMIC_SCHEDULED_TASK_NOTIFICATION.getToken(), "正在更新定时任务:jobName = "+jobName+",cron = "+cron);
        JobRegistry.getInstance().getJobScheduleController(jobName).rescheduleJob(cron);
    }

    /**
     * 删除定时任务
     * @param jobName
     */
    public void removeJob(String jobName){
        log.info("删除定时任务:jobName = {}", jobName);
        DingDingMsgSendUtils.sendDingDingGroupMsgNoAt(DingTokenEnum.DYNAMIC_SCHEDULED_TASK_NOTIFICATION.getToken(), "正在删除定时任务:jobName = "+jobName);
        JobRegistry.getInstance().getJobScheduleController(jobName).shutdown();
    }
}


3.4配置ElasticJobListener监听器(ElasticJobListener)

package com.github.amazon.daemon.utils;

import com.dangdang.ddframe.job.executor.ShardingContexts;
import com.dangdang.ddframe.job.lite.api.listener.AbstractDistributeOnceElasticJobListener;

/**
 * ElasticJobListener 监听器
 *
 * 实现分布式任务监听器
 * 如果任务有分片,分布式监听器会在总的任务开始前执行一次,结束时执行一次
 *
 * @author songfayuan
 * @date 2021/4/26 2:28 下午
 */
public class ElasticJobListener extends AbstractDistributeOnceElasticJobListener {

    public ElasticJobListener(long startedTimeoutMilliseconds, long completedTimeoutMilliseconds) {
        super(startedTimeoutMilliseconds,completedTimeoutMilliseconds);
    }

    @Override
    public void doBeforeJobExecutedAtLastStarted(ShardingContexts shardingContexts) {
    }

    @Override
    public void doAfterJobExecutedAtLastCompleted(ShardingContexts shardingContexts) {
        //任务执行完成后更新状态为已执行,当前未处理
    }
}


3.5动态任务执行类(DynamicJob)

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.github.amazon.common.constant.enums.DingTokenEnum;
import com.github.amazon.common.util.DingDingMsgSendUtils;
import com.xiaoleilu.hutool.date.DatePattern;
import com.xiaoleilu.hutool.date.DateUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;

/**
 * 动态定时任务执行
 * @author songfayuan
 * @date 2021/4/26 10:56 上午
 */
@Slf4j
public class DynamicJob implements SimpleJob {

    /**
     * 业务执行逻辑
     *
     * @param shardingContext
     */
    @Override
    public void execute(ShardingContext shardingContext) {
        log.info("{}动态定时任务执行逻辑start...", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
        String jobName = shardingContext.getJobName();
        String jobParameter = shardingContext.getJobParameter();
        log.info("---------DynamicJob---------动态定时任务正在执行:jobName = {}, jobParameter = {}", jobName, jobParameter);
        DingDingMsgSendUtils.sendDingDingGroupMsgNoAt(DingTokenEnum.DYNAMIC_SCHEDULED_TASK_NOTIFICATION.getToken(), "动态定时任务正在执行:JobName = "+jobName+",jobParameter = "+jobParameter);

        //根据参数调用不同的业务接口处理,请远程调用业务模块处理,避免本服务与业务依赖过重...

        log.info("{}动态定时任务执行逻辑end...", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
    }

}


3.6新增controller测试动态创建动态定时任务(JobOperateController)

import com.github.amazon.common.util.Response;
import com.github.amazon.daemon.job.DynamicJob;
import com.github.amazon.daemon.utils.ElasticJobHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.Objects;

/**
 * <p>
 * 动态定时任务 前端控制器
 * </p>
 *
 * @author songfayuan
 * @date 2021/4/26 11:05 上午
 */
@RestController
@RequestMapping("/noAuthorization")
public class JobOperateController {

    @Autowired
    private ElasticJobHandler elasticJobHandler;

    /**
     * 创建动态定时任务
     * jobName 任务名称
     * cron cron表达式 0 * * * * ? *
     * @param params
     * @return
     */
    @GetMapping("/createJob")
    public Response createJob(@RequestBody Map<String, Object> params){
        if (Objects.isNull(params.get("jobName"))){
            return Response.errorResponse("jobName不能为空");
        }
        if (Objects.isNull(params.get("cron"))){
            return Response.errorResponse("cron不能为空");
        }
        elasticJobHandler.addJob(params.get("jobName").toString(), params.get("cron").toString(), 1, new DynamicJob(), Objects.isNull(params.get("params")) ? "" : params.get("params").toString(), Objects.isNull(params.get("description")) ? "" : params.get("description").toString());
        return Response.successResponse("请求成功");
    }


    /**
     * 更新定时任务(似乎,好像,他内内的这个方法没用!!!)
     * jobName
     * cron cron表达式 0 0/5 * * * ?
     * @return
     */
    @GetMapping("/updateJob")
    public Response updateJob(@RequestBody Map<String, Object> params){
        if (Objects.isNull(params.get("jobName"))){
            return Response.errorResponse("jobName不能为空");
        }
        if (Objects.isNull(params.get("cron"))){
            return Response.errorResponse("cron不能为空");
        }
        elasticJobHandler.updateJob(params.get("jobName").toString(), params.get("cron").toString());
        return Response.successResponse("请求成功");
    }

    /**
     * 删除定时任务
     * jobName 任务名称
     * @return
     */
    @GetMapping("/removeJob")
    public Response removeJob(@RequestBody Map<String, Object> params){
        if (Objects.isNull(params.get("jobName"))){
            return Response.errorResponse("jobName不能为空");
        }
        elasticJobHandler.removeJob(params.get("jobName").toString());
        return Response.successResponse("请求成功");
    }

}


3.7扫描本地持久化的任务、添加任务(ScanDynamicJobHandler)

import com.github.amazon.common.entity.JobDynamicTask;
import com.github.amazon.daemon.feign.task.ElasticJobService;
import com.github.amazon.daemon.job.DynamicJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@Service
public class ScanDynamicJobHandler {
    @Resource
    private ElasticJobHandler elasticJobHandler;
    @Resource
    private ElasticJobService elasticJobService;

    /**
     * 扫描动态任务列表,并添加任务
     *
     * 循环执行的动态任务,本服务重启的时候,需要重新加载任务
     *
     * @author songfayuan
     * @date 2021/4/26 9:15 下午
     */
    public void scanAddJob() {
        List<JobDynamicTask> jobDynamicTaskList = this.elasticJobService.getAllList(); //这里为从MySQL数据库读取job_dynamic_task表的数据,微服务项目中建议使用feign从业务服务获取,避免本服务过度依赖业务的问题,然后业务服务新增动态任务也通过feign调取本服务JobOperateController实现,从而相对独立本服务模块
        log.info("扫描动态任务列表,并添加任务:本次共扫描到{}条任务。", jobDynamicTaskList.size());
        for (JobDynamicTask jobDynamicTask : jobDynamicTaskList) {
            /******创建任务******/
            elasticJobHandler.addJob(jobDynamicTask.getJobName(), jobDynamicTask.getCron(), 1, new DynamicJob(), jobDynamicTask.getParameters(), jobDynamicTask.getDescription());
        }
    }
}

3.8项目启动程序中新增加载本地持久化任务

import com.github.amazon.daemon.utils.ScanDynamicJobHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

import javax.annotation.Resource;

/**
 * @author songfayuan
 * @date 2018年02月07日20:35:35
 * 分布式任务调度模块
 */
@Slf4j
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class AmazonDaemonApplication implements CommandLineRunner {
    @Resource
    private ScanDynamicJobHandler scanDynamicJobHandler;

    public static void main(String[] args) {
        SpringApplication.run(AmazonDaemonApplication.class, args);
    }
	
    //如果不需要加载本地持久化的任务,移除CommandLineRunner和本方法即可
    @Override
    public void run(String... strings) throws Exception {
        log.info(">>>>>>>>>>>>>>>服务启动执行,扫描动态任务列表,并添加任务<<<<<<<<<<<<<");
        scanDynamicJobHandler.scanAddJob();
    }

}

3.9在elastic-job-lite-console中查看任务创建和执行状态如下

本elastic-job-lite-console是经过编译的,用git命令下载到本地

git clone https://gitee.com/yuejuncheng/elastic-job-lite-console.gi

下载完成后,解压到指定目录。如果是在Windows中运行,执行bin目录下的start.bat文件;如果是在linux中运行,则执行bin目录下的start.sh文件。

默认端口是8899,静默指定端口执行:

nohup ./start.sh -p 8888 >/dev/null 2>&1 &

如果遇到权限不够,先执行如下命令:

chmod 777 start.sh

运行成功后,在浏览器中输入访问地址http://127.0.0.1:8888,默认账号和密码都是root。如果要修改账号和密码,可修改config目录下的auth.properties文件。

进入UI管理界面后,配置注册中心,然后就可以看到相应的任务。
在这里插入图片描述

在这里插入图片描述

上文中涉及到的钉钉发送工具移步到这里:这里这里>>

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋发元

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值