深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康检测(一)

目录

前言

环境配置

Quartz

什么是Quartz?

应用场景

核心组件

Job

JobDetail

Trigger

CronTrigger

SimpleTrigger

Scheduler

任务存储

RAM

JDBC

导入依赖

 定时任务

销量统计

Redis检测

使用

注意事项


前言

在悦享校园1.0中引入了Quartz框架实现了对于商家每日的销量统计功能,而目前项目已升级到SpringBoot版本,因此需要进行对应的代码进行调整。除此之外考虑到若Redis出现故障时或使用该项目不想要配置Redis时如何保证该项目正常启动呢?即当Redis出现故障时如何无缝切换到数据库查询数据而不是抛出错误信息?由于项目中整合Redis的客户端为Lettuce,因此可以考虑使定时任务实现对Redis服务的监测从而无感切换数据查询操作。

环境配置

JDK 1.8

Spring Boot 2.7.12

lettuce 6.1.10(包含在Spring-Boot-Starter-Data-Redis中)

Quartz 2.3.2

Quartz

什么是Quartz?

根据官方文档描述,Quartz 是一种功能丰富的,开放源码的作业调度库,可以在集成在任何的Java应用程序中,小到独立的应用程序,大到复杂的电子商务系统。 Quartz可以用来创建简单或复杂的日程安排执行几十,几百,甚至数以万计的作业数,作业被定义为标准的Java组件,可以通过编程使其执行。

应用场景

如文章开头所述,当我们需要统计店铺的每日销量或者每周销量时,可以通过一个定时任务来执行相应的操作。以及Redis由于长时间不使用时可能会造成客户端除此之外,也可以将Quartz使用到如定期发送消息通知,如获取每日天气进行邮件推送到指定客户等其它的操作。这些都可通过Quartz来实现。除此之外Quartz还能完成其它复杂的任务。

核心组件

Job

用于存放真正需要定时执行的任务逻辑

JobDetail

用于对任务信息的相关描述,如任务名称、任务分组等。需要注意的是,JobDetail中含有一个Key属性,该属性将通过传入的任务名称和分组名称构建Key,若参数为空时则通过UUID来构建,从而确保该Key是唯一的。因此相同的任务名称和组名会覆盖之前的任务,这一点是需要注意的。

使用JobDetail+Job方式的设计,可以避免在并发情况下,对同一个实例的访问问题。

可以通过JobDataMap将数据存储并将数据传给Job实例。

Trigger

Trigger即为触发器,用于指定将以何种方式执行定时任务,注意Trigger与JobDetail一一对应,即一个触发器只做用于一个定义任务上。以下为两种常用的触发器:

CronTrigger

其核心在于使用Cron表达式进行任务的构建,如下是一个表示每月最后一天执行任务的Cron表达式:

# 每月的最后1天
    0 0 L * * *

    说明:
    Linux
    *    *    *    *    *
    -    -    -    -    -
    |    |    |    |    |
    |    |    |    |    +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
    |    |    |    +---------- month (1 - 12) OR jan,feb,mar,apr ...
    |    |    +--------------- day of month (1 - 31)
    |    +-------------------- hour (0 - 23)
    +------------------------- minute (0 - 59)

鉴于网上关于Cron表达式的文章非常多,这里不做过多赘述。这里列举几个常用的Cron表达式:

(1)0/2 * * * * ?   表示每2秒 执行任务

(2)0 0/2 * * * ?    表示每2分钟 执行任务

(3)0 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务

(4)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业

(5)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点 

(5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时

可以通过Crontab.guru - The cron schedule expression editor 网站来验证Cron表达式。

SimpleTrigger

相比于上述提到的Cron表达式,SimpleTrigger来构建一些指定间隔时间执行的任务更加容易,如每隔75s执行某个任务,则使用SimpleTrigger更佳。

Scheduler

任务调度器,它可以JobDetail与Trigger关联起来,通过任务调度器将启动任务的执行。一个任务调度器中可以包含多个关联的实例。

任务存储

Quartz提供了两种定时任务存储功能,一种为RAM,另一种为JDBC。

RAM

默认情况下Quartz会将任务数据存储到内存中,优点为任务读取速度快,简单易用。但缺点是随着服务重启会导致任务丢失。(本文采用该方式)

JDBC

Quartz提供了对定时任务持久化的功能,可以通过创建对应的数据表将定时任务持久化到数据库中,如此一来可以确保任务不会因为服务重启而丢失,可以更好的管理任务。缺点是需要额外的进行数据库表创建。本文采用默认的RAM方式存储,可以自行通过具体需求场景来选择。

导入依赖

由于本文项目使用了SpringBoot,因此导入如下依赖即可

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

 定时任务

任务创建可以通过继承 QuartzJobBean 类并重写其excuteInternal方法,或实现 Job 接口的excute方法,从QuartzJobBean的源码可知,其实现了Job接口,因此以上的创建方式任选其一即可。  

销量统计

销量统计任务类,此处仅使用日志打印体现,具体的业务逻辑可自行编写

@Slf4j
public class SellDailyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("这是每日销量统计定时任务。。。");
    }
}

执行规则配置类

@Configuration
public class SellDailyConfig{

    @Bean("sellDailyJob")
    public JobDetail jobDetail(){
        // 指定任务执行的类
        return JobBuilder.newJob(SellDailyJob.class)
                 // 任务名称和分组名,不可重复
                .withIdentity("sellDailyJob", "group")
                .withDescription("任务描述:内存方式运行")
                .storeDurably()
                .build();
    }

    @Bean("sellDailyTrigger")
    public Trigger trigger() {
        return TriggerBuilder.newTrigger()
                // 触发器名和分组名,不可重复
                .withIdentity("trigger", "group")
                .forJob(jobDetail())
                .startNow()
                // 使用Cron表达式构建执行事件 每5s执行一次
                .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
                .build();
    }

}

Redis检测

Redis健康检测定时任务类,构建方法同上

@Slf4j
public class RedisCheckJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("这是Redis定时心跳任务");
    }
}

执行规则配置类

@Configuration
@Slf4j
public class RedisCheckConfig {
    // 指定生成的Bean实例对象名称
    @Bean("redisCheck")
    public JobDetail jobDetail() {
        return JobBuilder.newJob(RedisCheckJob.class)
                // 任务名和任务分组
                .withIdentity("RedisCheckJob", "group")
                .withDescription("任务描述:内存方式运行")
                .storeDurably()
                .build();
    }

    @Bean("redisTrigger")
    public Trigger trigger() {
        return TriggerBuilder.newTrigger()
                // 触发器名称和分组
                .withIdentity("redisCheck", "group")
                .forJob(jobDetail())
                .startNow()
                // 使用SimpleSchedule构建定时任务
                .withSchedule(
                        SimpleScheduleBuilder
                                .simpleSchedule()
                                 // 每隔10s执行任务
                                .withIntervalInSeconds(10)
                                // 永不过期
                                .repeatForever())
                .build();
    }
}

使用

上个部分中分别使用CronTrigger和SimpleTrigger构建了两种定时任务,而根据SpringBoot官方文档中所示,当Quartz可用时,SchedulerFactoryBean会将Scheduler自动装配到容器中,因此在SpringBoot中使用@Configuration+@Bean注解构建定时任务后,无需显式创建Scheduler,SpringBoot会自动加载这些定时任务交由Scheduler调度。

因此配置完成后只需启动应用程序即可,执行结果如下所示。

注意事项

1.以上构建定时任务时需要创建任务类并在其中写入自定义的业务方法。并在配置类newJob中写入对应任务类。jobDetail和Trigger方法使用@Bean注解构建时,需要指定名称且不重复,否则其它配置类无法正常构建Bean实例。

2.jobDetail方法和trigger方法中的withIdentity分别指定不同任务名和任务分组,需要保证其它配置类中以上两个属性之中至少有一个属性不同,否则同样将造成定时任务无法正常执行。(eg:A配置类中的任务名为work,分组名为group,B中配置与之相同则会导致A服务无法正常运行)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是 SpringBoot 搭配 Quartz 实现动态定时任务的源码: 1. 首先,我们需要引入 QuartzSpringBoot 的依赖: ```xml <!-- Quartz相关依赖 --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.3.2</version> </dependency> <!-- SpringBoot相关依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ``` 2. 创建定时任务实体类,用于封装定时任务的信息,包括任务名称、任务组、任务类名、任务状态(是否启用)、任务表达式等: ```java @Entity @Table(name = "job_task") @Data public class JobTask implements Serializable { private static final long serialVersionUID = 1L; /** * ID */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** * 任务名称 */ @NotBlank(message = "任务名称不能为空") private String name; /** * 任务分组 */ @NotBlank(message = "任务分组不能为空") private String group; /** * 任务类名 */ @NotBlank(message = "任务类名不能为空") private String className; /** * 任务状态,0:禁用,1:启用 */ @NotNull(message = "任务状态不能为空") private Integer status; /** * 任务表达式 */ @NotBlank(message = "任务表达式不能为空") private String cronExpression; /** * 创建时间 */ private LocalDateTime createTime; /** * 最后一次修改时间 */ private LocalDateTime updateTime; } ``` 3. 创建定时任务的服务类,用于管理定时任务的增删改查等操作,同时也需要实现 `InitializingBean` 接口,在启动应用时加载已存在的定时任务: ```java @Service @AllArgsConstructor public class JobTaskService implements InitializingBean { private final Scheduler scheduler; private final JobTaskRepository jobTaskRepository; /** * 添加任务 * @param jobTask * @return * @throws Exception */ public boolean addJobTask(JobTask jobTask) throws Exception { if (jobTask == null || StringUtils.isBlank(jobTask.getCronExpression())) { return false; } if (StringUtils.isBlank(jobTask.getName()) || StringUtils.isBlank(jobTask.getClassName())) { throw new Exception("任务名称或任务类名不能为空"); } // 判断任务是否已存在 JobKey jobKey = JobKey.jobKey(jobTask.getName(), jobTask.getGroup()); if (scheduler.checkExists(jobKey)) { return false; } // 构建任务实例 JobDetail jobDetail = JobBuilder.newJob(getClass(jobTask.getClassName()).getClass()) .withIdentity(jobTask.getName(), jobTask.getGroup()) .build(); // 构建任务触发器 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobTask.getCronExpression()); CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(jobTask.getName(), jobTask.getGroup()) .withSchedule(cronScheduleBuilder) .build(); // 注册任务和触发器 scheduler.scheduleJob(jobDetail, trigger); // 如果任务状态为启用,则立即启动任务 if (jobTask.getStatus() == 1) { scheduler.triggerJob(jobKey); } // 保存任务信息 jobTask.setCreateTime(LocalDateTime.now()); jobTask.setUpdateTime(LocalDateTime.now()); jobTaskRepository.save(jobTask); return true; } /** * 修改任务 * @param jobTask * @return * @throws Exception */ public boolean modifyJobTask(JobTask jobTask) throws Exception { if (jobTask == null || StringUtils.isBlank(jobTask.getCronExpression())) { return false; } if (StringUtils.isBlank(jobTask.getName()) || StringUtils.isBlank(jobTask.getClassName())) { throw new Exception("任务名称或任务类名不能为空"); } // 判断任务是否存在 JobKey jobKey = JobKey.jobKey(jobTask.getName(), jobTask.getGroup()); if (!scheduler.checkExists(jobKey)) { return false; } // 修改任务触发器 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobTask.getCronExpression()); CronTrigger newTrigger = TriggerBuilder.newTrigger() .withIdentity(jobTask.getName(), jobTask.getGroup()) .withSchedule(cronScheduleBuilder) .build(); scheduler.rescheduleJob(TriggerKey.triggerKey(jobTask.getName(), jobTask.getGroup()), newTrigger); // 修改任务信息 JobTask oldJobTask = jobTaskRepository.findByNameAndGroup(jobTask.getName(), jobTask.getGroup()); oldJobTask.setClassName(jobTask.getClassName()); oldJobTask.setStatus(jobTask.getStatus()); oldJobTask.setCronExpression(jobTask.getCronExpression()); oldJobTask.setUpdateTime(LocalDateTime.now()); jobTaskRepository.save(oldJobTask); return true; } /** * 删除任务 * @param name * @param group * @return * @throws Exception */ public boolean deleteJobTask(String name, String group) throws Exception { JobKey jobKey = JobKey.jobKey(name, group); if (!scheduler.checkExists(jobKey)) { return false; } scheduler.deleteJob(jobKey); jobTaskRepository.deleteByNameAndGroup(name, group); return true; } /** * 获取所有任务 * @return */ public List<JobTask> getAllJobTask() { return jobTaskRepository.findAll(); } /** * 根据任务名称和分组获取任务信息 * @param name * @param group * @return */ public JobTask getJobTaskByNameAndGroup(String name, String group) { return jobTaskRepository.findByNameAndGroup(name, group); } /** * 获取任务类实例 * @param className * @return * @throws Exception */ private Object getClass(String className) throws Exception { Class<?> clazz = Class.forName(className); return clazz.newInstance(); } /** * 实现 InitializingBean 接口,在启动应用时加载已存在的定时任务 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { List<JobTask> jobTaskList = jobTaskRepository.findAll(); for (JobTask jobTask : jobTaskList) { if (jobTask.getStatus() == 1) { addJobTask(jobTask); } } } } ``` 4. 创建定时任务的控制器类,用于处理新增、修改、删除等请求: ```java @RestController @AllArgsConstructor @RequestMapping("/job") public class JobTaskController { private final JobTaskService jobTaskService; /** * 添加任务 * @param jobTask * @return * @throws Exception */ @PostMapping public ResponseEntity addJobTask(@RequestBody JobTask jobTask) throws Exception { boolean result = jobTaskService.addJobTask(jobTask); return result ? ResponseEntity.ok("任务添加成功") : ResponseEntity.badRequest().body("任务添加失败"); } /** * 修改任务 * @param jobTask * @return * @throws Exception */ @PutMapping public ResponseEntity modifyJobTask(@RequestBody JobTask jobTask) throws Exception { boolean result = jobTaskService.modifyJobTask(jobTask); return result ? ResponseEntity.ok("任务修改成功") : ResponseEntity.badRequest().body("任务修改失败"); } /** * 删除任务 * @param name * @param group * @return * @throws Exception */ @DeleteMapping("/{name}/{group}") public ResponseEntity deleteJobTask(@PathVariable String name, @PathVariable String group) throws Exception { boolean result = jobTaskService.deleteJobTask(name, group); return result ? ResponseEntity.ok("任务删除成功") : ResponseEntity.badRequest().body("任务删除失败"); } /** * 获取所有任务 * @return */ @GetMapping public ResponseEntity getAllJobTask() { List<JobTask> jobTaskList = jobTaskService.getAllJobTask(); return ResponseEntity.ok(jobTaskList); } /** * 根据任务名称和分组获取任务信息 * @param name * @param group * @return */ @GetMapping("/{name}/{group}") public ResponseEntity getJobTaskByNameAndGroup(@PathVariable String name, @PathVariable String group) { JobTask jobTask = jobTaskService.getJobTaskByNameAndGroup(name, group); return ResponseEntity.ok(jobTask); } } ``` 5. 创建定时任务的启动类,用于启动 SpringBoot 应用: ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } /** * 注册定时任务调度器 * @return * @throws SchedulerException */ @Bean public SchedulerFactoryBean schedulerFactoryBean() throws SchedulerException { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); Properties properties = new Properties(); properties.put("org.quartz.scheduler.instanceName", "ChitGPTScheduler"); properties.put("org.quartz.threadPool.threadCount", "10"); schedulerFactoryBean.setQuartzProperties(properties); schedulerFactoryBean.setStartupDelay(5); return schedulerFactoryBean; } /** * 注册定时任务实例 * @return */ @Bean public Scheduler scheduler() { return schedulerFactoryBean().getScheduler(); } } ``` 以上就是 SpringBoot 搭配 Quartz 实现动态定时任务的源码,希望能对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值