美文网首页分布式系统(distributed system)
SpringBoot+Quartz实现动态定时任务

SpringBoot+Quartz实现动态定时任务

作者: 小波同学 | 来源:发表于2024-05-10 14:46 被阅读0次

目前常用的几种任务调度

  • Timer,简单无门槛,一般也没人用。
  • spring @Scheduled注解,一般集成于项目中,小任务很方便。
  • 开源工具 Quartz,分布式集群开源工具,以下两个分布式任务应该都是基于Quartz实现的,可以说是中小型公司必选,当然也视自身需求而定。
  • 分布式任务 XXL-JOB,是一个轻量级分布式任务调度框架,支持通过 Web 页面对任务进行 CRUD 操作,支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,支持在线配置调度任务入参和在线查看调度结果。
  • 分布式任务 Elastic-Job,是一个分布式调度解决方案,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。定位为轻量级无中心化解决方案,使用 jar 包的形式提供分布式任务的协调服务。支持分布式调度协调、弹性扩容缩容、失效转移、错过执行作业重触发、并行调度、自诊。
  • 分布式任务 Saturn,Saturn是唯品会在github开源的一款分布式任务调度产品。它是基于当当elastic-job来开发的,其上完善了一些功能和添加了一些新的feature。目前在github上开源大半年,470个star。Saturn的任务可以用多种语言开发比如python、Go、Shell、Java、Php。其在唯品会内部已经发部署350+个节点,每天任务调度4000多万次。同时,管理和统计也是它的亮点。

SpringBoot项目的实现

1.config配置

import org.quartz.Scheduler;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
  
import java.io.IOException;
import java.util.Properties;
  
/**
 * quartz配置
 */
@Configuration
public class SchedulerConfig {
     
    @Bean(name="SchedulerFactory")
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setAutoStartup(true);
        //factory.setStartupDelay(5);//延时5秒启动
        return factory;
    }
  
    /*
     * quartz初始化监听器
     */
    @Bean
    public QuartzInitializerListener executorListener() {
       return new QuartzInitializerListener();
    }
  
    /*
     * 通过SchedulerFactoryBean获取Scheduler的实例
     */
    @Bean(name="Scheduler")
    public Scheduler scheduler() throws IOException {
        return schedulerFactoryBean().getScheduler();
    }
  
}

定时任务动态操作

import lombok.extern.slf4j.Slf4j;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

import javax.annotation.Resource;

/**
 * @Author: huangyibo
 * @Date: 2024/5/11 14:29
 * @Description: 定时任务动态操作
 */

@Component
@Slf4j
public class TaskGroupHandler {

    @Resource
    private Scheduler scheduler;

    /**
     * 新增定时任务
     * @param jobId
     */
    public void addCronJob(String jobId) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobId, "TaskGroup");

            //构建job信息
            JobDetail job = JobBuilder.newJob(TaskGroupJob.class).withIdentity(jobId, "TaskGroup")
                    .withDescription("任务组编排").build();
            JobDataMap jobDataMap = job.getJobDataMap();
            jobDataMap.put("jobId", jobId);
            //CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("cron的表达式");

            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerKey)
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule("10 * * * * ?").withMisfireHandlingInstructionFireAndProceed())
                    .build();

            //SimpleScheduleBuilder.simpleSchedule().withRepeatCount(0).withIntervalInSeconds(20)//每隔多少秒执行一次; withRepeatCount 设置重复的次数
            //.startNow().withSchedule(cronScheduleBuilder)
            //交由Scheduler安排触发
            scheduler.scheduleJob(job, trigger);
            if(!scheduler.isStarted()){
                scheduler.start();
            }
            log.info("添加定时任务成功, startJob:{}", jobId);
        } catch (SchedulerException e) {
            log.error("添加定时任务异常, jobId:{}", jobId, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除任务
     * @param jobId
     * @return
     */
    public void removeCronJob(String jobId) {
        try {
            // TriggerKey 定义了trigger的名称和组别 ,通过任务名和任务组名获取TriggerKey
            TriggerKey triggerKey = TriggerKey.triggerKey(jobId,"TaskGroup");
            // 停止触发器
            scheduler.resumeTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 移除任务
            scheduler.deleteJob(JobKey.jobKey(jobId,"TaskGroup"));
            log.info("删除定时任务成功, jobId:{}", jobId);
        } catch (SchedulerException e) {
            log.error("删除定时任务异常, jobId:{}", jobId, e);
        }
    }

    /**
     * 暂停定时任务
     * @param jobId
     */
    public void pauseJob(String jobId) {
        try {
            JobKey jobKey = JobKey.jobKey(jobId,"TaskGroup");
            // 暂停任务
            scheduler.pauseJob(jobKey);
            log.info("暂停定时任务成功, jobId:{}", jobId);
        } catch (SchedulerException e) {
            log.error("暂停定时任务异常, jobId:{}", jobId, e);
        }
    }

    /**
     * 继续定时任务
     * @param jobId
     */
    @GetMapping("/resumeJob")
    public void resumeJob(String jobId) {
        try {
            // 通过任务名和任务组名获取jobKey
            JobKey jobKey = JobKey.jobKey(jobId,"TaskGroup");
            // 继续任务
            scheduler.resumeJob(jobKey);
            log.info("继续定时任务成功, jobId:{}", jobId);
        } catch (SchedulerException e) {
            log.error("继续定时任务异常, jobId:{}", jobId, e);
        }
    }
}
@RestController
@RequestMapping("/api/test")
@Slf4j
public class TestController {

    @Resource
    private TaskGroupHandler taskGroupHandler;


    @GetMapping("/quartzAdd")
    public boolean quartzAdd(String jobId) {
        taskGroupHandler.addCronJob(jobId);
        log.info("添加定时任务成功, startJob:{}", jobId);
        return Boolean.TRUE;
    }

    /**
     * 删除任务
     *
     * @param jobId
     * @return
     */
    @GetMapping("/quartzDel")
    public boolean quartzDel(String jobId) {
        taskGroupHandler.removeCronJob(jobId);
        log.info("删除定时任务成功, startJob:{}", jobId);
        return Boolean.TRUE;
    }

    // 暂停定时任务
    @GetMapping("/pauseJob")
    public void pauseJob(String jobId) {
        taskGroupHandler.pauseJob(jobId);
        log.info("暂停定时任务成功, jobId:{}", jobId);
    }

    // 继续定时任务
    @GetMapping("/resumeJob")
    public void resumeJob(String jobId) {
        taskGroupHandler.resumeJob(jobId);
        log.info("继续定时任务成功, jobId:{}", jobId);
    }
}

任务预热,预先加载数据库已经配置好的任务

/**
 * @Author: huangyibo
 * @Date: 2024/5/10 18:26
 * @Description: 任务预热,预先加载数据库已经配置好的任务
 */
@Component
@Slf4j
public class TaskGroupJobRunner implements CommandLineRunner {

    @Resource
    private TaskGroupHandler taskGroupHandler;

    @Override
    public void run(String... args) {
        // 初始加载数据库里状态为正常的定时任务
        List<String> list = Stream.of("1", "2").collect(Collectors.toList());
        list.forEach(jobId -> {
            taskGroupHandler.addCronJob(jobId);
        });
    }
}

TaskGroupJob实现具体逻辑

@Component
@Slf4j
public class TaskGroupJob implements Job {

    DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jdMap = context.getJobDetail().getJobDataMap();
        String jobId = (String) jdMap.get("jobId");

        //TODO 逻辑待补充

        log.info(formatter.format(LocalDateTime.now()) + "----TaskGroupJob-计划执行开始===>jobId:{}", jobId);
    }

}

参考:
https://www.jb51.net/article/261554.htm

https://www.cnblogs.com/Alida/p/12986967.html

相关文章

网友评论

    本文标题:SpringBoot+Quartz实现动态定时任务

    本文链接:https://www.haomeiwen.com/subject/rmkefjtx.html