任务调度实现

一、定时任务概述

        在项目中开发定时任务应该一种比较常见的需求,在 Java 中开发定时任务主要有三种解决方案:一是使用JDK 自带的 Timer,二是使用 Spring Task,三是使用第三方组件 Quartz

        Timer 是 JDK 自带的定时任务工具,其简单易用,但是对于复杂的定时规则无法满足,在实际项目开发中也很少使用到。而 Spring Task使用起来很简单,除 Spring 相关的包外不需要额外的包,而且支持注解和配置文件两种形式。 Quartz 功能强大,但是使用起来相对笨重。

建议:

  • 单体项目架构使用Spring Task

  • 分布式项目架构使用Quartz

 二、JDK实现任务调度

基于jdk的任务调度

public class JdkTaskDemo {public static void main(String[] args) {//创建定时类Timer timer = new Timer();//创建任务类TimerTask task = new TimerTask() {@Overridepublic void run() {System.out.println("定时任务执行了......"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}};//执行定时任务timer.schedule(task,new Date(),2000);}
}

三、Spring-task实现任务调度

编写任务调度,可调度程序加一下执行时间,模拟真实业务。

@Scheduled(cron = "*/1 * * * * *")
public void task1() throws InterruptedException {System.out.println(Thread.currentThread().getName()+":task1--->"+ LocalDateTime.now());Thread.sleep(5000);
}

Spring-task执行的任务是基于单线程执行的。由此得出两个结论:

  • Spring-task 执行任务按照单线程执行并合理执行,不会因为第一个执行任务时间过长而执行第二个

  • Spring-task是单线程的处理任务能力有限,不建议处理分布式架构的任务调度。

@Component
public class MyTask {@Scheduled(cron = "*/1 * * * * *") //每秒执行1次public void task2() throws InterruptedException {System.out.println(Thread.currentThread().getName()+":task2--->"+ LocalDateTime.now());}@Scheduled(cron = "*/1 * * * * *") //每秒执行1次public void task1() throws InterruptedException {System.out.println(Thread.currentThread().getName()+":task1--->"+ LocalDateTime.now());Thread.sleep(5000);}
}

 

 定时任务2 1s时间执行完但受任务1影响也需要等5s。

Cron表达式讲解

关于 cronExpression 表达式有至少 6 个(也可能是 7 个)由空格分隔的时间元素。从左至右,这些元素的定义如下:

1.秒(0–59)

2.分钟(0–59)

3.小时(0–23)

4.月份中的日期(1–31)

5.月份(1–12 或 JAN–DEC)

6.星期中的日期(1–7 或 SUN–SAT)

7.年份(1970–2099)

0 0 10,14,16 * * ? 
每天上午 10 点,下午 2 点和下午 4 点

0 0,15,30,45 * 1-10 * ? 
每月前 10 天每隔 15 分钟 

各个时间可用值如下:

秒 0-59 , - * /

分 0-59 , - * /

小时 0-23 , - * /

日 1-31 , - * ? / L W C

月 1-12 or JAN-DEC , - * /

周几 1-7 or SUN-SAT , - * ? / L C #

年(可选字段) empty, 1970-2099 , - * /

实际开发在线文档自动生成  (在线Cron表达式生成器 )

四、Quartz基本应用

1.介绍

 

 

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;

  • 作业管理 - 对调度作业进行有效的管理;

官方文档:

  • Documentation

  • Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API

2.Quertz API介绍

Quartz 的核心类有以下三部分:

  • 任务 Job : 需要实现的任务类,实现 execute() 方法,执行后完成任务。

  • 触发器 Trigger : 包括 SimpleTriggerCronTrigger

  • 调度器 Scheduler : 任务调度器,负责基于 Trigger触发器,来执行 Job任务。

 

 3.JobDetail

JobDetail 的作用是绑定 Job,是一个任务实例,它为 Job 添加了许多扩展参数。

主要字段含义
name任务名称
group任务分组,默认分组DEFAULT
jobClass要执行的Job实现类
jobDataMap任务参数信息,JobDetail、Trigger都可以使用JobDataMap来设置一些参数或者信息

 每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。

为什么设计成JobDetail + Job,不直接使用Job?

JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中。

这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。

JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以 规避并发访问 的问题。

4.Simple Trigger

这是比较简单的一类触发器,用它能实现很多基础的应用。使用它的主要场景包括:

  • 在指定时间段内,执行一次任务

最基础的 Trigger 不设置循环,设置开始时间。

  • 在指定时间段内,循环执行任务

在 1 基础上加上循环间隔。可以指定 永远循环、运行指定次数

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger2","group1").startNow().withSchedule(//使用简单触发器SimpleScheduleBuilder.simpleSchedule().//3s间隔执行withIntervalInSeconds(3).//始终执行repeatForever()).build();

5.CronTrigger

CronTrigger 是基于日历的任务调度器,在实际应用中更加常用。虽然很常用,但是知识点都一样,只是可以通过表达式来设置时间而已。使用方式就是绑定调度器时换一下

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger2","group1").startNow().withSchedule(//使用日历触发器CronScheduleBuilder.cronSchedule("0/1 * * * * ? ")).build();

五、SpringBoot整合Quartz

1.添加依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version></dependency>

2.编写application.yml文件

server:port: 80
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456url: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai# 定时配置quartz:# 相关属性配置properties:org:quartz:# 数据源dataSource:globalJobDataSource:# URL必须大写URL: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaidriver: com.mysql.cj.jdbc.DrivermaxConnections: 5username: rootpassword: 123456# 必须指定数据源类型provider: hikaricpscheduler:instanceName: globalScheduler# 实例idinstanceId: AUTOtype: com.alibaba.druid.pool.DruidDataSourcejobStore:# 数据源dataSource: globalJobDataSource# JobStoreTX将用于独立环境,提交和回滚都将由这个类处理class: org.quartz.impl.jdbcjobstore.JobStoreTX# 驱动配置driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate# 表前缀tablePrefix: QRTZ_# 失效阈值(只有配置了这个时间,超时策略根据这个时间才有效)misfireThreshold: 100# 集群配置isClustered: true# 线程池配置threadPool:class: org.quartz.simpl.SimpleThreadPool# 线程数threadCount: 10# 优先级threadPriority: 5

这里面有quartz的数据源,线程池,集群和misfire相关配置,简单配置,更多的配置可以到官网查看。

Configuration Reference

配置application.properties 自动生成表

spring.quartz.jdbc.initialize-schema: always
spring.quartz.job-store-type: jdbc

 3.编写实体类

@Data
public class JobInfo {/*** 任务名称*/private String jobName;/*** 任务组*/private String jobGroup;/*** 触发器名称*/private String triggerName;/*** 触发器组*/private String triggerGroup;/*** cron表达式*/private String cron;/*** 类名*/private String className;/*** 状态*/private String status;/*** 下一次执行时间*/private String nextTime;/*** 上一次执行时间*/private String prevTime;/*** 配置信息(data)*/private String config;
}

4.编写任务类

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
@Component
public class MyTask extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) {System.out.println("TimeEventJob正在执行..." + LocalDateTime.now());// 执行9秒try {Thread.sleep(9000);System.out.println("TimeEventJob执行完毕..." + LocalDateTime.now());} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

        这个类就是继承的QuartzJobBean,当然也可以实现Job接口,这个类就是任务需要具体执行的业务操作类,类上面添加了两个注解,这两个注解的目的就是让同一个任务必须在上一个任务执行完毕之后再按照触发后续执行,以及定时任务里面的JobDataMap,能够在任务中流转以及修改更新;不添加注解的情况下,JobDataMap里面的数据不能在任务之间流转,以及任务的触发不会参照上一任务是否执行完毕。

 5.编写任务的开关停删操作

@Configuration
public class JobHandler {@Resourceprivate Scheduler scheduler;/*** 添加任务*/@SuppressWarnings("unchecked")public void addJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {Objects.requireNonNull(jobInfo, "任务信息不能为空");// 生成job keyJobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());// 当前任务不存在才进行添加if (!scheduler.checkExists(jobKey)) {Class<Job> jobClass = (Class<Job>)Class.forName(jobInfo.getClassName());// 任务明细JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobKey).withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).withDescription(jobInfo.getJobName()).build();// 配置信息jobDetail.getJobDataMap().put("config", jobInfo.getConfig());// 定义触发器TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());// 设置任务的错过机制Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing()).build();scheduler.scheduleJob(jobDetail, trigger);} else {throw new SchedulerException(jobInfo.getJobName() + "任务已存在,无需重复添加");}}/*** 任务暂停*/public void pauseJob(String jobGroup, String jobName) throws SchedulerException {JobKey jobKey = JobKey.jobKey(jobName, jobGroup);if (scheduler.checkExists(jobKey)) {scheduler.pauseJob(jobKey);}}/*** 继续任务*/public void continueJob(String jobGroup, String jobName) throws SchedulerException {JobKey jobKey = JobKey.jobKey(jobName, jobGroup);if (scheduler.checkExists(jobKey)) {scheduler.resumeJob(jobKey);}}/*** 删除任务*/public boolean deleteJob(String jobGroup, String jobName) throws SchedulerException {JobKey jobKey = JobKey.jobKey(jobName, jobGroup);if (scheduler.checkExists(jobKey)) {// 这里还需要先删除trigger相关//TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());//scheduler.getTrigger()//scheduler.rescheduleJob()return scheduler.deleteJob(jobKey);}return false;}/*** 获取任务信息*/public JobInfo getJobInfo(String jobGroup, String jobName) throws SchedulerException {JobKey jobKey = JobKey.jobKey(jobName, jobGroup);if (!scheduler.checkExists(jobKey)) {return null;}List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);if (Objects.isNull(triggers)) {throw new SchedulerException("未获取到触发器信息");}TriggerKey triggerKey = triggers.get(0).getKey();Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);JobDetail jobDetail = scheduler.getJobDetail(jobKey);JobInfo jobInfo = new JobInfo();jobInfo.setJobName(jobGroup);jobInfo.setJobGroup(jobName);jobInfo.setTriggerName(triggerKey.getName());jobInfo.setTriggerGroup(triggerKey.getGroup());jobInfo.setClassName(jobDetail.getJobClass().getName());jobInfo.setStatus(triggerState.toString());if (Objects.nonNull(jobDetail.getJobDataMap())) {jobInfo.setConfig(JSONObject.toJSONString(jobDetail.getJobDataMap()));}CronTrigger theTrigger = (CronTrigger) triggers.get(0);jobInfo.setCron(theTrigger.getCronExpression());return jobInfo;}
}

6.编写接口,实现任务操作

@RestController
@RequestMapping("/job")
public class QuartzController {@Resourceprivate JobHandler jobHandler;@Resourceprivate Scheduler scheduler;/*** 查询所有的任务*/@RequestMapping("/all")public List<JobInfo> list() throws SchedulerException {List<JobInfo> jobInfos = new ArrayList<>();List<String> triggerGroupNames = scheduler.getTriggerGroupNames();for (String triggerGroupName : triggerGroupNames) {Set<TriggerKey> triggerKeySet = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroupName));for (TriggerKey triggerKey : triggerKeySet) {Trigger trigger = scheduler.getTrigger(triggerKey);JobKey jobKey = trigger.getJobKey();JobInfo jobInfo = jobHandler.getJobInfo(jobKey.getGroup(), jobKey.getName());jobInfos.add(jobInfo);}}return jobInfos;}/*** 添加任务*/@PostMapping("/add")public JobInfo addJob(@RequestBody JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {jobHandler.addJob(jobInfo);return jobInfo;}/*** 暂停任务*/@RequestMapping("/pause")public void pauseJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)throws SchedulerException {jobHandler.pauseJob(jobGroup, jobName);}/*** 继续任务*/@RequestMapping("/continue")public void continueJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)throws SchedulerException {jobHandler.continueJob(jobGroup, jobName);}/*** 删除任务*/@RequestMapping("/delete")public boolean deleteJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)throws SchedulerException {return jobHandler.deleteJob(jobGroup, jobName);}
}

7.测试

使用postman进行接口测试

单线程与多线程执行任务调度的区别

单线程运行任务不同任务之间串行,任务A运行时间会响应任务B运行间隔  

任务调度持久化的好处

 如果任务调度没有持久化,而任务又是基于动态设置,不是开机自启的,会有一个问题,服务重启之后设置的任务都会失效了。如果任务整合持久化之后,设置的动态任务信息就会保存到数据库,开机自启就会加载这些数据库信息,就会按照原来的设置运行任务。

但是,一定要把建表配置注释掉  

#spring.quartz.jdbc.initialize-schema: always
#spring.quartz.job-store-type: jdbc

Quartz 集群执行与单机执行区别

Quartz是一个开源的作业调度框架,用于在Java应用程序中调度任务。Quartz集群和非集群的区别主要体现在以下几个方面:

  1. 高可用性:Quartz集群可以提供高可用性,即使其中一个节点出现故障,其他节点仍然可以继续工作。而非集群模式下,如果应用程序所在的服务器出现故障,任务调度将会停止。

  2. 负载均衡:Quartz集群可以通过将任务分配给不同的节点来实现负载均衡。这意味着任务将在集群的各个节点上分布,从而提高系统整体的性能和吞吐量。非集群模式下,所有的任务将在单个节点上运行,可能会导致性能瓶颈。

  3. 数据共享:Quartz集群可以共享任务调度的数据,包括作业和触发器等。这意味着当一个节点添加或删除任务时,其他节点也能够感知到。非集群模式下,每个节点都有自己独立的任务调度数据,可能导致数据不一致。

需要注意的是,Quartz集群需要配置和管理多个节点,可能需要更多的系统资源和维护工作。非集群模式则相对简单,适用于小规模的应用程序。选择使用哪种模式应根据具体的需求和系统要求来决定。

 六、总结

【1】简述一下什么是任务调度?

任务调度就是按照特定时间规则执行系统某个固定的业务逻辑。任务调度底层是使用jdk的Timer实现的。单体项目建议使用Spring-task任务调度技术,分布式架构建议使用quartz任务调度框架。Spring-task是单线程运行旳,Quartz是多线程运行的,且功能更为丰富,支持作业管理。

【2】说一下你都用过什么任务调度技术,他们的区别是什么?

Spring-task是单线程,且功能简单。执行任务只需开启开关@EnableScheduling,在要执行的任务方法上加

@Scheduled(cron = "*/1 * * * * *")注解。它的使用弊端:

  1. 任务A的执行时间会影响任务B的执行间隔,但是任务A和任务B是两个任务,不应该相互影响。

  2. 没有固定组件,持久化等功能,也就没法形成作业系统

Quartz是多线程的高可用的任务调度框架,支持持久化,多线程,集群模式,且有固定组件结构Job、Trigger、scheduler。他的优点一一说明

  1. 有固定组件,有持久化功能,这样就能基于Quartz开发一个任务调度系统,通过UI界面去管理任务调度。

  2. 任务进行持久化之后,重启服务器会加载持久化的任务继续执行。

  3. 任务支持集群模式,如果任务调度模块是一个集群n个节点,那么任务调度不会因为一个节点挂掉而挂掉,且任务在集群之间形成负载均衡。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/324134.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 3.6 + robotFramework自动化框架 环境搭建、学习笔记

一、环境搭建 概览&#xff1a;win 7 python 3.6 pycharm robotframework IntelliBot fireFox 1.安装相关的库 推荐使用pip安装&#xff0c;如 pip install robotframework(以及selenium2library) 研发环境没外网&#xff0c;采用离线安装&#xff0c;down包后解压--进入…

buildroot 编译错误【001】

在GitHub 查找错误,也挺好用 解决办法 fakeroot 错误

通过聚道云软件连接器实现金蝶软件与客如云软件的无缝对接

客户介绍 某知名冷饮连锁品牌是国内一家拥有数千家门店的知名品牌&#xff0c;作为一家专注于冷饮和甜品的企业&#xff0c;我们致力于提供高品质、健康美味的食品&#xff0c;为消费者带来冰凉的甜蜜与畅快的口感。 我们始终坚持选用优质原料&#xff0c;严格把控生产流程&a…

二叉搜索树与双向链表

解题思路一&#xff1a; /** public class TreeNode {int val 0;TreeNode left null;TreeNode right null;public TreeNode(int val) {this.val val;} } */ // 一定要用自己的理解真正弄出来才行&#xff0c;否则没有用&#xff01; // 再次提醒&#xff0c;计算机这种工科…

vue中动态出来返回的时间秒数,在多少范围显示多少秒,多少范围显示分,小时等等

在Vue中&#xff0c;你可以使用计算属性&#xff08;computed property&#xff09;或过滤器&#xff08;filter&#xff09;来根据动态返回的时间秒数来显示不同的时间单位&#xff0c;比如秒、分、小时等等。 下面是一个使用计算属性的示例&#xff1a; <template>&l…

正则表达式解析与应用:深度剖析正则表达式的威力

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

柯桥职场英语中千万别把英文邮件中的“OOO”理解成“哦哦哦”!不然可就尴尬了!

有的同学工作中跟老外打交道比较多 尤其是写邮件&#xff0c;除了要把事情交代清楚 还有一些缩写我们也必须掌握 就拿发邮件时老外最爱用到的“OOO”来说 千万不要把“OOO”理解成“噢噢噢” 今天给大家分享几个关于邮件的常用表达 它们真正的意思你都知道吗 01 “OOO”可…

视频文字想要提取应该使用哪些软件呢

随着短视频的兴起&#xff0c;快手成为了很多人喜爱的平台。有时候&#xff0c;我们看到一些有趣的视频&#xff0c;想要提取其中的文字内容&#xff0c;却不知道该如何操作。今天&#xff0c;我们就来介绍一种使用水印云快手提取视频文字的方法。 首先&#xff0c;我们需要下…

超维空间M1无人机使用说明书——21、基于opencv的人脸识别

引言&#xff1a;M1型号无人机不仅提供了yolo进行物体识别&#xff0c;也增加了基于opencv的人脸识别功能包&#xff0c;仅需要启动摄像头和识别节点即可 一、一键启动摄像头和人脸识别节点 roslaunch robot_bringup bringup_face_detect.launch无报错&#xff0c;出现以下界…

数据结构期末复习(1)数据结构和算法 线性表

数据结构期末总复习&#xff08;gaois课堂版&#xff09; 数据结构的概念 数据结构是计算机科学中的一个重要概念&#xff0c;它指的是组织和存储数据的方式。数据结构可以帮助我们高效地操作和管理数据&#xff0c;使得计算机程序能够更加有效地执行各种任务。 数据结构有很…

设计模式——迭代器模式(Iterator Pattern)

概述 迭代器模式(Iterator Pattern)&#xff1a;提供一种方法来访问聚合对象&#xff0c;而不用暴露这个对象的内部表示&#xff0c;其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。 在软件开发中&#xff0c;我们经常需要使用聚合对象来存储一系列数据。聚合对象拥有…