1、除了下视频下图片,定时任务还可以干什么?
2、当我们在用Spring Task的时候,我们在用什么?
3、节假日购买理财不计息,怎么实现?
4、任务跑完给管理员发一条短信,怎么实现?
5、明明配置了线程池,怎么就变成单线程了?
6、怎么kill一个进程里面的一个任务?
7、Quartz的幕后角色:包工头、工人、项目经理
8、当我想要任务重复执行的时候,为什么没有重复执行?

(一)定时器基本要求


1. 配置触发规则,比如每天九点钟跑一次,或者每隔一个小时跑一次.
2. 定义要执行的任务或者脚本或者代码
3. 能集中管理配置的,能够看到所有的任务有哪些,每个任务的规则是怎样的
4. 支持任务的并发的执行,假如说有三个任务,可以同时跑,不会出现三个任务互相干扰的情况
5. 调度器,指挥任务的运行,就是能动态的启动某个任务或者动态的停止某个任务
6. 方便整合到Spring容器里面去

(二)Quartz简介


Quartz是一个特性丰富的,开源的任务调度库,它几乎可以嵌入所有的Java程序,从很小的独立应用程序到大型商业系统。
Quartz可以用来创建成百上千的简单的或者复杂的任务,这些任务可以用来执行任何程序可以做的事情。Quartz拥有很多企业级的特性,包括支持JTA事务和集群。

(三)体系结构



Quartz[笔记] - 图1

 Trigger执行规则

(一)SimpleTrigger


固定时刻,固定时间间隔
精确到毫秒级别的

(二)CalendarIntervalTrigger


基于日期或日历 , 基于时间单位的

比如说每个月跑一次定时任务,框架能自动识别每个月有多少天
每周跑一次
每年跑一次

(三)DailyTimeIntervalTrigger


在一天内的时间段之内,然后一定的时间间隔去执行任务.

比如说你想让在早晨八点钟到晚上五点钟每隔半个小时跑一次,并且我只需要在周一到周五去运行,如果有这样的需求的话就可以使用DailyTimeIntervalTrigger.

(四)CronTrigger


基于Cron表达式,最灵活的.



 Calendar排除规则

案例:ZJJ_Quartz_2020/06/11_15:57:08_j9hqk



(一)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

排除星期中的任意周几,例如,可用于排除周末,默认周六和周日

 Listener任务生命周期监听

案例:ZJJ_Quartz_2020/06/11_16:01:12_orbrc



我们有这么一种需求,在每个任务运行结束之后发送通知给运维管理员。那是不是
要在每个任务的最后添加一行代码呢?这种方式对原来的代码造成了入侵,不利于维护。
如果代码不是写在任务代码的最后一行,怎么知道任务执行完了呢?或者说,怎么监测
到任务的生命周期呢?
观察者模式:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则
所有依赖它的对象都会得到通知并自动更新。
Quartz 中提供了三种 Listener,监听 Scheduler 的,监听 Trigger 的,监听 Job 的。
只需要创建类实现相应的接口,并在 Scheduler 上注册 Listener,便可实现对核心对象的监听



(一)JobListener


四个方法:


Quartz[笔记] - 图2


工具类:ListenerManager,用于添加、获取、移除监听器
工具类:Matcher,主要是基于 groupName 和 keyName 进行匹配。

(二)SchedulerListener




(三)TriggerListener

Quartz[笔记] - 图3
Quartz[笔记] - 图4

 Scheduler

调度器,是 Quartz 的指挥官,由 StdSchedulerFactory 产生。它是单例的。
并且是 Quartz 中最重要的 API,默认是实现类是 StdScheduler,里面包含了一个
QuartzScheduler。QuartzScheduler 里面又包含了一个 QuartzSchedulerThread。

Quartz[笔记] - 图5


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


 数据持久化



QuartZ默认是将数据保存到内存中的,当然也可以基于jdbc规范保存到数据库里面去.下次再重启服务器的时候就可以连接数据库给任务加载出来.

源码包里面会有建表语句.有各种各样的建表语句,比如MySQL,Oracle的等等.总共有11张表.


QRTZ_BLOB_TRIGGERS Trigger 作为 Blob 类型存储
QRTZ_CALENDARS 存储 Quartz 的 Calendar 信息
QRTZ_CRON_TRIGGERS 存储 CronTrigger,包括 Cron 表达式和时区信息
QRTZ_FIRED_TRIGGERS 存储与已触发的 Trigger 相关的状态信息,以及相关 Job 的执行信息
QRTZ_JOB_DETAILS 存储每一个已配置的 Job 的详细信息
QRTZ_LOCKS 存储程序的悲观锁的信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的 Trigger 组的信息
QRTZ_SCHEDULER_STATE 存储少量的有关 Scheduler 的状态信息,和别的 Scheduler 实例
QRTZ_SIMPLE_TRIGGERS 存储 SimpleTrigger 的信息,包括重复次数、间隔、以及已触的次数
QRTZ_SIMPROP_TRIGGERS 存储 CalendarIntervalTrigger 和 DailyTimeIntervalTrigger 两种类型的触发器
QRTZ_TRIGGERS 存储已配置的 Trigger 的信息

 动态调度的实现

思路,将配置信息存放到数据库里面

案例:ZJJ_Quartz_2020/06/11_18:27:14_0sl60



建表语句




| CREATE TABLE sys_job (
id int(
11_) _NOT NULL AUTOINCREMENT COMMENT ‘ID’,
**job_name varchar
(512) NOT NULL COMMENT ‘任务名称’,
job_group varchar
(512) NOT NULL COMMENT ‘任务组名’,
job_cron varchar
(512) NOT NULL COMMENT ‘时间表达式’,
job_class_path varchar
(1024) NOT NULL COMMENT ‘类路径,全类型’,
job_data_map varchar
(1024) DEFAULT NULL COMMENT ‘传递map参数’,
job_status int
(2) NOT NULL COMMENT ‘状态:1启用 0停用’,
job_describe varchar
(1024) DEFAULT NULL COMMENT ‘任务功能描述’,
PRIMARY KEY
(id)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

— 插入3条数据,3个任务 — 注意第三条,是一个发送邮件的任务,需要改成你自己的QQ和授权码。不知道什么是授权码的自己百度。
INSERT INTO sys_job
(id, job_name, job_group, job_cron, job_class_path, job_data_map, job_status, job_describe) VALUES (22, ‘test’, ‘test’, /20 ?’, ‘com.gupaoedu.demo.task.TestTask1’, NULL*, 1, ‘a job a’);
INSERT INTO sys_job
(id, job_name, job_group, job_cron, job_class_path, job_data_map, job_status, job_describe) VALUES (23, ‘test2’, ‘test’, /30 ?’, ‘com.gupaoedu.demo.task.TestTask2’, NULL*, 1, ‘another job’);
INSERT INTO sys_job
(id, job_name, job_group, job_cron, job_class_path, job_data_map, job_status, job_describe) VALUES (24, ‘test3’, ‘mail’, /10 ?’, ‘com.gupaoedu.demo.task.TestTask3’, ‘{\“data\“:{\“loginAccount\“:\“改成你的QQ邮箱\“,\“loginAuthCode\“:\“改成你的邮箱授权码\“,\“sender\“:\“改成你的QQ邮箱\“,\“emailContent\“:\“    你好,我是蒋介石的私生子,我在台湾有2000亿新台币冻结了。我现在在古交,又回不了台湾。所以没办法,只要你给我转1000块钱帮我解冻我的账号,我在台湾有我自己的部队。要是你今天帮了我,等我回到台湾给你留一个三军统帅的位置,另外再给你200亿人民币,我建行账号158158745745110蒋宽。这是我女秘书的账号,打了钱通知我,我给你安排专机接你来台。\“,\“emailContentType\“:\“text/html;charset=utf-8\“,\“emailSubject\“:\“十万火急\“,\“recipients\“:\“改成你要的收件人邮箱,可以有多个,英文逗号隔开\“}}’*, 1, ‘fdsafdfds’)_**; | | —- |


(二)Scheduler

你修改了数据库里面的信息之后,Quartz是不知道的,所以需要调用Scheduler来触发一下让Quartz重新加载新的配置信息.



(三)启动Spring容器将MySQL里面的任务自动加载到Quartz里面


实现CommandLineRunner接口在run方法里面去编写业务代码

案例:ZJJ_Quartz_2020/06/11_18:38:32_rkiwr



(四)Quartz容器注入Spring的bean

需要自定义JobFactory然后注入到Spring容器里面去,

还需要通过Scheduler.setJobFactory方法设置调度器

案例:ZJJ_Quartz_2020/06/11_18:42:56_p4f4i


| /*

  • 将Spring的对象注入到Quartz Job 1
    /
    public class AdaptableJobFactory implements JobFactory {
    @Override
    public Job newJob(TriggerFiredBundle bundle, Scheduler arg1_) _throws SchedulerException *
    _{

    1. _return **newJob**_(_**bundle**_)_**;<br />

    _}

    _public
    Job newJob(TriggerFiredBundle bundle_) _throws SchedulerException **_{

    1. _try _{<br />
    2. _**// 返回Job实例
    3. Object jobObject = createJobInstance**_(_**bundle**_)_**;<br />
    4. **return **adaptJob**_(_**jobObject**_)_**;<br />
    5. **_}<br />
    6. _catch _(_**Exception ex**_) {<br />
    7. _throw new **SchedulerException**_(_"Job instantiation failed"**, ex**_)_**;<br />
    8. **_}<br />
    9. }<br />


    1. _**// 通过反射的方式创建实例
    2. **protected **Object createJobInstance**_(_**TriggerFiredBundle bundle**_) _throws **Exception **_{<br />
    3. _**Method getJobDetail = bundle.getClass**_()_**.getMethod**_(_"getJobDetail"_)_**;<br />
    4. Object jobDetail = ReflectionUtils._invokeMethod_**_(_**getJobDetail, bundle**_)_**;<br />
    5. Method getJobClass = jobDetail.getClass**_()_**.getMethod**_(_"getJobClass"_)_**;<br />
    6. Class jobClass = **_(_**Class**_) _**ReflectionUtils._invokeMethod_**_(_**getJobClass, jobDetail**_)_**;<br />
    7. **return **jobClass.newInstance**_()_**;<br />
    8. **_}<br />


    1. _protected **Job adaptJob**_(_**Object jobObject**_) _throws **Exception **_{<br />
    2. _if _(_**jobObject **instanceof **Job**_) {<br />
    3. _return _(_**Job**_) _**jobObject;<br />
    4. **_}<br />
    5. _else if _(_**jobObject **instanceof **Runnable**_) {<br />
    6. _return new **DelegatingJob**_((_**Runnable**_) _**jobObject**_)_**;<br />
    7. **_}<br />
    8. _else _{<br />
    9. _throw new **IllegalArgumentException**_(_"Unable to execute job class [" **+ jobObject.getClass**_()_**.getName**_() _**+<br />
    10. **"]: only [org.quartz.Job] and [java.lang.Runnable] supported."_)_**;<br />
    11. **_}<br />
    12. }<br />

    }_** | | —- |



| @Component
public class MyJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle_) _throws Exception {
//调用父类的方法 Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);

return jobInstance;
}
}
| | —- |

| @Component
public class InitStartSchedule implements CommandLineRunner _{
_private final
Logger logger = LoggerFactory.getLogger_(_this.getClass());

@Autowired
private ISysJobService sysJobService;
@Autowired
private MyJobFactory myJobFactory;

@Override
public void run(String… args_) _throws Exception {
**
//……__代码 ** // 通过SchedulerFactory获取一个调度器实例 SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
//!!!!!!!!!!!!!!!! 如果不设置JobFactory,Service注入到Job会报空指针 scheduler.setJobFactory(_myJobFactory)_;
// 启动调度器 scheduler.start(); ** //……__代码

_ }
}


}_** | | —- |


 Quartz集群

(一)为什么需要集群?


1、防止单点故障,减少对业务的影响

2、减少节点的压力,例如在 10 点要触发 1000 个任务,如果有 10 个节点,则每个节点之需要执行 100 个任务.


(二)集群需要解决的问题?


1、任务重跑,因为节点部署的内容是一样的,到 10 点的时候,每个节点都会执行相同的操作,引起数据混乱。比如跑批,绝对不能执行多次。
比如说work1 work2 work3 到了一定时间了,集群都给work1 work2 work3跑起来,那么任务就重了




2、任务漏跑,假如任务是平均分配的,本来应该在某个节点上执行的任务,因为节点故障,到了需要触发的时间就一直没有得到执行。


Quartz默认是用数据库来进行集群之间的内容通讯的,就是A服务器知道B服务器已经执行了某个任务,A服务器不会重复执行.


3、水平集群需要注意时间同步问题



4、Quartz 使用的是随机的负载均衡算法,不能指定节点执行
所以必须要有一种共享数据或者通信的机制。在分布式系统的不同节点中,我们可
以采用什么样的方式,实现数据共享?
两两通信,或者基于分布式的服务,实现数据共享。
例如:ZK、Redis、DB。
在 Quartz 中,提供了一种简单的方式,基于数据库共享任务执行信息。也就是说,
一个节点执行任务的时候,会操作数据库,其他的节点查询数据库,便可以感知到了。
同样的问题:建什么表?哪些字段?依旧使用系统自带的 11 张表。