【手把手】分布式定时任务调度解析之Quartz

news2024/11/19 19:31:34

1、任务调度背景

在业务系统中有很多这样的场景:
1、账单日或者还款日上午 10 点,给每个信用卡客户发送账单通知,还款通知。如何判断客户的账单日、还款日,完成通知的发送?
2、银行业务系统,夜间要完成跑批的一系列流程,清理数据,下载文件,解析文件,对账清算、切换结算日期等等,如何触发一系列流程的执行?
3、金融机构跟人民银行二代支付系统对接,人民银行要求低于 5W的金额(小额支付)半个小时打一次包发送,以缓解并发压力。所以,银行的跨行转账分成了多个流程: 录入、复核、发送。如何把半个小时以内的所有数据一次性发送?

类似于这种基于准确的时刻或者固定的时间间隔触发的任务,或者有批量数据需要处理,再或者要实现两个动作解耦的场景,都可以用任务调度来实现。

任务调度的实现方式有很多,如果要实现调度需求,那么工具应该要有什么样的基本要求呢?
1)可以定义触发的规则。比如基于时刻、时间间隔、表达式;
2)可以定义需要执行的任务。比如执行一个脚本或者一段代码,任务和规则是分开的;
3)集中管理配置,持久配置。不用把规则写在代码里面,可以看到所有的任务配置,方便维护,重启之后任务可以再次调度——配置文件或者配置中心;
4)支持任务的串行执行。例如执行 A 任务后再执行 B 任务再执行 C 任务;
5)支持多个任务并发执行,互不干扰(例如 ScheduledThreadPoolExecutor);
6)有自己的调度器,可以启动、中断、停止任务;
7)容易集成到 Spring

任务调度工具对比:

层次举例特点
操作系统Linux crontab、Windows 计划任务只能执行简单脚本或者命令
数据库MySQL、Oracle可以操作数据,但不能执行 Java 代码
工具Kettle可以操作数据,执行脚本,但没有集中配置
开发语言JDK Timer、ScheduledThreadPoolTimer:单线程,JDK1.5 之后:ScheduledThreadPool(Cache、Fiexed、Single):没有集中配置,日程管理不够灵活
容器Spring Task、@Scheduled不支持集群
分布式框架XXL-JOB,Elastic-Job

@Scheduled 也是用 JUC 的 ScheduledExecutorService 实现的 
Scheduled(cron = “0 15 10 15 * ?”)**
1、 ScheduledAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法将@Scheduled 的方法包装为指定的 task添加到 ScheduledTaskRegistrar 中;
2、 ScheduledAnnotationBeanPostProcessor 会监听 Spring 的容器初始化事件,在 Spring 容器初始化完成后进行TaskScheduler 实现类实例的查找,若发现有 SchedulingConfigurer 的实现类实例,则跳过 3;
3、 查找 TaskScheduler 的实现类实例默认是通过类型查找,若有多个实现则会查找名字为"taskScheduler"的实现 Bean,若没有找到则在 ScheduledTaskRegistrar 调度任务的时候会创建一个 newSingleThreadScheduledExecutor,将TaskScheduler 的实现类实例设置到 ScheduledTaskRegistrar 属性中;
4、 ScheduledTaskRegistrar 的 scheduleTasks 方法触发任务调度;
5、 真正调度任务的类是 TaskScheduler 实现类中的 ScheduledExecutorService,由JUC提供;

2、Quartz是什么

老规矩,没有什么比官网更适合去学习一门新的知识:http://www.quartz-scheduler.org/

Quartz 的意思是石英,像石英表一样精确。 Quartz 的目的就是让任务调度更加简单,开发人员只需要关注业务即可。他是用 Java 语言编写的(也有.NET 的版本)。Java 代码能做的任何事情,Quartz 都可以调度。

而官网吹起来也是毫不吝啬:Quartz是一个功能丰富的开源作业调度库,可以集成到几乎所有的Java应用中--从最小的独立应用到最大的电子商务系统。Quartz可用于创建简单或复杂的时间表,以执行几十、几百、甚至几万个作业;这些作业的任务被定义为标准的Java组件,几乎可以执行任何你可以编程让它们做的事情。Quartz Scheduler包括许多企业级功能,如支持JTA事务和集群。

不过说句实在话,Quartz是一个非常老牌的任务调度系统,从1998 年构思,于 2001 年发布到 sourceforge,到现在也风风雨雨熬了20来年,目前整体更新比较慢,因为已经非常成熟了:

1、精确到毫秒级别的调度;

2、可以独立运行,也可以集成到容器中;

3、支持事务(JobStoreCMT );

4、支持集群;

5、支持持久化;

目前GitHub上最新版还是三年前的版本

3、Quartz初尝

引入maven依赖

<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

默认配置文件

在org.quartz.core 包下,有一个默认的配置文件:quartz.properties。如果没有定义一个同名的配置文件的时候,就会使用默认配置文件里面的配置。

创建任务Job

实现Quartz提供的Job接口,实现接口中的execute方法,也就是任务执行的内容:

执行任务

可以看到,任务每2s执行一次,封装在JobDetail中的信息也可以取出

所以,使用Quartz就是这么简单,拢共就4步,依赖、任务、触发器、调度器,完事儿。值得注意的是,调度器一定是单例的,在构建的时候,Quartz也做了一个单例的判断和保证。

4、Quartz基本架构

Trigger的四大类型

而刚刚上面Demo中使用的Trigger是SimpleTrigger,听名字就知道肯定干不了多复杂的事儿,它是基于某种固定时刻进行任务触发。而在Quartz中一共提供了4中不同类型的Trigger:

1、SimpleTrigger:简单触发器。固定时刻或时间间隔,精度可以做到毫秒级。例如:每天 9 点钟运行;每隔 30 分钟运行一次。

2、CalendarIntervalTrigger:基于日历的触发器。比简单触发器更多时间单位,支持非固定时间的触发,例如一年可能 365/366,一个月可能 28/29/30/31。例如:每年、每个月、每周、每天、每小时、每分钟、每秒。即使每年的月数和每个月的天数不是固定的也适用;

3、DailyTimeIntervalTrigger:基于日期的触发器。每天的某个时间段。例如:每天早上 9 点到晚上 9 点,每隔半个小时执行一次,并且只在周一到周六执行。

4、CronTrigger:基于 Cron 表达式的触发器,是最常用的触发器类型;Cron表达式是基于Linux的crontab基础上移植出的表达式,用来定义时间维度的调度规则。虽然是移植出来的,但是一点优化都舍不得做,可读性还么那么差。

Cron表达式

位置时间域特殊值
10-59, - * /
2分钟0-59, - * /
3小时0-23, - * /
4日期1-31, - * ? / L W C
5月份1-12, - * /
6星期1-7, - * ? / L W C
7年份(可选)1-31, - * /

Cron 表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。好了,下面对于Cron表达式的介绍感兴趣的就看一眼,不感兴趣的直接跳过既可。反正在真正开发的时候,会写的顺手就写了,不会写的度娘查一下也顺手就复制了,没人真的会去自己研究规则。

星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示“每分钟”;

问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;

减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从 10 到 12 点,即 10,11,12;

逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;

斜杠(/):x/y 表达一个等步长序列,x 为起始值,y 为增量步长值。如在分钟字段中使用 0/15,则表示为 0,15,30 和 45 秒,而 5/15 在分钟字段中表示 5,20,35,50,你也可以使用*/y,它等同于 0/y;

L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L 在日期字段中,表示 这个月份的最后一天,如一月的 31 号,非闰年二月的 28 号;如果 L 用在星期中,则表示星期六,等同于 7。但是,如果 L 出现在星期字段里,而且在前面有一个数值 X,则表示“这个月的最后 X 天”,例如,6L 表示该月的最后星期五;

W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如 15W 表示离该月 15号最近的工作日,如果该月 15 号是星期六,则匹配 14 号星期五;如果 15 日是星期日,则匹配 16 号星期一;如果 15号是星期二,那结果就是 15 号星期二。但必须注意关联的匹配日期不能够跨月,如你指定 1W,如果 1 号是星期六,结果匹配的是 3 号星期一,而非上个月最后的那天。W 字符串只能指定单一日期,而不能指定日期范围;

LW 组合:在日期字段可以组合使用 LW,它的意思是当月的最后一个工作日;

井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如 6#3 表示当月的第三个星期五(6 表示星期五,\#3 表示当前的第三个),而 4#5 表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;

C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如 5C 在日期字段中就相当于日历 5 日以后的第一天。1C 在星期字段中相当于星期日后的第一天。

基于Calendar的排除规则

上面定义的都是在什么时间执行,但是有一些在什么时间不执行的需求。比如:理财周末和法定假日购买不计息;证券公司周末和法定假日休市。是不是要把日期写在数据库中,然后读取基于当前时间判断呢?

如果要在触发器的基础上,排除一些时间区间不执行任务,就要用到Quartz的Calendar类(注意不是JDK的Calendar)。可以按年、月、周、日、特定日期、Cron表达式排除。

Calendar名称用法
BaseCalendar为高级的 Calendar 实现了基本的功能,实现了 org.quartz.Calendar 接口
AnnualCalendar排除年中一天或多天
CronCalendar日历的这种实现排除了由给定的CronExpression表达的时间集合。 例如:可以使用此日历使用表达式“* * 0-7,18-23?* *”每天排除所有营业时间(上午8点至下午5点)。 如果CronTrigger具有给定的cron表达式并且与具有相同表达式的CronCalendar相关联,则日历将排除触发器包含的所有时间,并且它们将彼此抵消。
DailyCalendar可以使用此日历来排除营业时间(上午8点 - 5点)每天。 每个DailyCalendar仅允许指定单个时间范围,并且该时间范围可能不会跨越每日边界(即,您不能指定从上午8点至凌晨5点的时间范围)。 如果属性invertTimeRange为false(默认),则时间范围定义触发器不允许触发的时间范围。 如果invertTimeRange为true,则时间范围被反转 - 也就是排除在定义的时间范围之外的所有时间。
HolidayCalendar特别的用于从 Trigger 中排除节假日
MonthlyCalendar排除月份中的指定数天,例如,可用于排除每月的最后一天
WeeklyCalendar排除星期中的任意周几,例如,可用于排除周末,默认周六和周日

以AnnualCalendar为例,其实使用非常简单,只要指定好想要排除的日期,然后设置进去调度器中即可,调度器在执行的时候自然会将指定的日期排除掉:

Scheduler,中文翻译叫调度器,是Quartz的指挥官,由SchedulerFactory产生,它是单例的。而在Scheduler几种实现里面,最值得说一说的就是StdScheduler,里面有一个 QuartzScheduler,在QuartzScheduler里面又包含了一个QuartzSchedulerThread。

Scheduler中的方法主要分为三大类:
1、操作调度器本身,例如调度器的启动start()、调度器的关闭shutdown();
2、操作Trigger,例如pauseTriggers()、resumeTrigger();
3、操作Job,例如scheduleJob()、unscheduleJob()、rescheduleJob();
这些方法非常重要,可以实现任务的动态调度。

Listener

有这么一种需求,在每个任务运行结束之后发送通知给运维管理员。那是不是要在每个任务的最后添加一行代码呢?这种方式对原来的代码造成了入侵,不利于维护。如果代码不是写在任务代码的最后一行,怎么知道任务执行完了呢?或者说,怎么监测到任务的生命周期呢?Quartz中提供了三种Listener,监听Scheduler的,监听Trigger的,监听Job的。只需要创建类实现相应的接口,并在Scheduler上注册Listener,便可实现对核心对象的监听。

JobListener中的四个方法:
1、getName():返回JobListener 的名称;
2、jobToBeExecuted():Scheduler在JobDetail将要被执行时调用这个方法;
3、jobExecutionVetoed():Scheduler在JobDetail即将被执行,但又被TriggerListener否决了时调用这个方法;
4、jobWasExecuted():Scheduler在JobDetail执行之后调用这个方法;

TriggerListener中的五个方法:
1、getName():返回监听器的名称;
2、triggerFired():Trigger被触发,Job上的execute()方法将要被执行时,Scheduler调用此方法;
3、vetoJobExecution():在Trigger触发后,Job将要被执行时由Scheduler调用这个方法。TriggerListener给了一个选择去否决Job的执行。假如这个方法返回true,这个Job将不会为此次Trigger触发而得到执行;
4、triggerMisfired():Trigger错过触发时调用;
5、triggerComplete():Trigger被触发并且完成了Job的执行时,Scheduler调用这个方法;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CCF CSP认证——201312

文章目录201312-1 出现次数最多的数201312-2 ISBN号码201312-3 最大的矩形201312-4 有趣的数201312-5 I’m stuck!201312-1 出现次数最多的数 题目链接 数据量较小&#xff0c;且数据范围也比较小。可以直接暴力&#xff0c;通过设置数组记录下标数据出现的次数&#xff0c;最…

C/C++关键字

C/C关键字【1】extern "C"【2】asm【3】关键字auto【4】break语句【5】catch 语句【6】关键字class【7】关键字const【8】#if【9】#pragma once【10】#pragma pack(1)【11】#pragma pack(4)【12】explicit【】 continue语句【13】关键字enum【14】friend【15】goto语…

【springboot进阶】基于starter项目构建(二)构建starter项目-web

目录 一、创建 web-spring-boot-starter 项目 二、添加 pom 文件依赖 三、构建配置 1. rest模板配置 RestTemplateConfig 2. 统一异常处理 BackendGlobalExceptionHandler 3. 统一返回数据结构 4. jwt鉴权处理 5. 请求日志切面处理 WebLogAspect 6. 邮件配置 BackendM…

mysql数据同步到elasticsearch数据解决方案

mysql数据同步到elasticsearch数据解决方案 问题场景 1.分库分表后多关联或者多条件查找效率低下&#xff0c;例如2b场景的查询&#xff0c;导出等需要多条件查询&#xff0c;继续用分库分表话效率低下。 2.数据量太多需要转移非关系型数据库elasticsearch存储 3.其他数据转…

AI 实战篇 |基于 AI开放平台实现 【植物识别】 功能,成为行走的百科全书

&#x1f3ac; 博客主页&#xff1a;https://xiaoy.blog.csdn.net &#x1f3a5; 本文由 呆呆敲代码的小Y 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;Unity系统学习专栏 &#x1f332; 游戏制作专栏推荐&#xff1a;游戏制作 &…

代码随想录刷题记录day40 爬楼梯+零钱兑换+完全平方数·

代码随想录刷题记录day40 爬楼梯零钱兑换完全平方数 参考&#xff1a;代码随想录 70. 爬楼梯 思想 当作完全背包问题&#xff0c;物品是1&#xff0c;2&#xff0c;可以无限次取用&#xff0c;背包的容量是n 1.dp[j]表示容量为j的背包&#xff0c;装满有dp[j]次 2.dp[j]dp…

【图像处理】opencv | 图像的二值化操作| cv2.threshold() | cv2.adaptiveThreshold()

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、cv2.threshold()阈值操作函数1.1、初见1.2、阈值不同时的效果二、cv2.adaptiveThreshold()自适应阈值操作函数2.1、初见2.2、固定blocksize,改变C值大小的实…

104-127-linux-vim-shell基础

104-linux-shell: 1.shell基础 分类&#xff1a;linux使用Bash&#xff0c;可通过vi /etc/shells查看linux支持的shell类型。 1、echo [rootlocalhost ~]#echo [选项] [输出内容] 选项:-e:支持反斜线控制的字符转换&#xff08;具体百度吧)-n:取消输出后行末的换行符号&…

Python实现ALO蚁狮优化算法优化支持向量机分类模型(SVC算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蚁狮优化(Ant Lion Optimizer&#xff0c;ALO)算法是Mirjalili于2015提出的一种新型元启发式群智能算法…

185.基于Django的富文本编辑器安装与使用

1.DjangoUeditor 1.1 概述 富文本编辑器&#xff0c;在web开发中必不可少&#xff0c;但是django没有自带富文本编辑器&#xff0c;因此我们需要使用第三方库&#xff0c;这里使用DjangoUeditor DjangoUditor是百度开源的在线HTML编辑器&#xff0c;功能非常强大&#xff0c;像…

VUEElement简单介绍。

目录 一、VUE 1、基本介绍 2、Vue 指令 3、生命周期 二、Element 1、基本介绍 2、Element 布局 一、VUE 1、基本介绍 ▶ 概述 Vue 是一套前端框架&#xff0c;免除原生JavaScript中的DOM操作&#xff0c;简化书写。 我们之前也学习过后端的框架 Mybatis &#xff0c;My…

Linux下C/C++实现类似netstat命令(列出TCP和UDP连接)

网络连接一般包括最基本的五元组信息(源地址、目标地址、源端口、目标端口、协议号)再加上所属进程信息pid, exe, cmdline等。其中这两项数据大多可直接读取linux /proc目录下的网络状态连接文件/proc/net/tcp、/proc/net/udp), 进程状态目录(/proc/pid/xx)。 Linux 下的/proc…

Java基于JSP的报刊订阅管理系统

随着人类的发展&#xff0c;人们对信息的获取方式也越来越多&#xff0c;虽然很多时候人们习惯了通过手机来获取各类信息&#xff0c;但是手机也逐渐的成为了危害人类健康的杀手之一&#xff0c;为了能够让大家回归到健康的生活中来&#xff0c;我开发了本系统&#xff0c;旨在…

基于jsp+mysql+ssm医药进销存管理系统-计算机毕业设计

项目介绍 为了减少传统医药进销存管理的繁杂的工作量&#xff0c;提高医药进销存管理的效率而设计开发了此系统。本系统综合各方面的需求决定采用B/S架构&#xff0c;并利用clipse搭建java开发平台。从而共同完成整个医药的设计开发。系统实现的功能主要包括&#xff1a;用户在…

java 通过InetAddress获取ip 计算机名称操作

本文属于java网络编程部分 需要你的网络编程三要素 有所了解 如果您尚未了解 可以先查看我的文章 java网络编程三要素 而 为了更好的获取和使用IP地址 java提供了InetAddress类 来到文档 首先 他在 java的net包下 所以 想用它 是需要导包的 根据文档叙述 InetAddress就是一个…

python数据分析及可视化(十七)聚宽(双均线分析、因子选股策略、多因子选股策略、均值回归理论、布林带策略、PEG策略、权重收益策略)

聚宽 聚宽是一个做金融量化的网站&#xff0c;https://www.joinquant.com&#xff0c;登录注册&#xff0c;如果你写的文章、策略被别人采纳&#xff0c;增加积分&#xff0c;积分用于免费的回测时长。在我的策略&#xff0c;进入策略列表&#xff0c;里面有做好的策略模板可以…

Web前端105天-day40-GIT

git--版本控制系统(VCS) 目录 前言 一、版本控制系统(VCS) 二、Git中的常用概念 三、Git中的常用命令 四、分支 五、远程仓库(代码托管平台) 总结 前言 day40学习开始 一、版本控制系统(VCS) 用于项目中文件的存储、共享、历史回退、合并、代码追踪文件历史常用版本控制…

acwing基础课——spfa

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板3——搜索与图论 - AcWing 基本思想&#xff1a; 一般单源最短路我们都可以用spfa算法来做&#xff0c;如果过不了再尝试其他算法。 spfa算法就是在bellman-ford算法的基础上就行优化&#xff0c;bellman-算法是每…

微信小程序|小程序事件

首先,我们在index.wxml中与index.js中添加如下代码: <button bindtap="alert">bindtap</button>Page({data: {},alert: function (event) {wx.showToast({title: 触发成功, // 标题icon: success, // 图标类型,默认successduration: 1500 // 提示窗停…

贪吃蛇复现-CoCube

需要完成下面所提及博文中里面所有前序案例&#xff1a; 从开环到闭环的旅程-CoCube 在完成如上代码之后&#xff0c;添加一个彩蛋&#xff0c;贪吃蛇的案例。 蓝桥ROS之半自动贪吃龟turtlesim版 基本上就是上述代码复现一下&#xff0c;完全没有难度的。 贪吃蛇复现-CoCubep…