背景
定时任务在后端程序开发中有着广泛的应用场景,例如:
- 银行缴费系统的对账。为了保证最终数据的一致性,使用定时任务核对金额。
- 定时生成报表。
- 定时清理无用数据。日志清理。
- …
文章主要总结了定时任务的设计思路和常见的实现方案。
Cron表达式
Cron表达式语法: [秒] [分] [小时] [日] [月] [周] [年]
常见Cron表达式例子:
1、定点触发
表达式 | 说明 |
---|---|
0 0 19 * * ? * | 每天19点开始新闻联播 |
0 0 9,17 * * ? * | 每天朝九晚五上下班打卡 |
0 0 17 8 * ? * | 每月1号的8点扣话费 |
0 0 17 ?* FRI * | 每周五的17点写周报 |
0 30 11 ?* SAT-SUN * | 每周六-周日的11点半起床 |
0 0 19 L * ? * | 每月最后一天的19点还花呗 |
0 30 17 ?* 6L * | 每月最后一个周五的17点半还信用卡 |
0 0 0 ?* 1#2 * | 每年5月第二个星期日母亲节 |
2、间隔触发
表达式 | 说明 |
---|---|
0 0/2 7-9 * * ? * | 每天7点-9点上班高峰期每两分钟做一次红绿灯调度 |
Quartz
Quartz是一个Java编写的作业调度框架。功能:
- 支持定时触发和间隔触发两种调度方式。
- 调度信息(状态)持久化(数据入库)。
- 具有分布式和集群能力。
1、核心要素
说明:
- Job(被调度的任务)和Trigger(触发器)是任务调度的元数据,Scheduler(任务调度器)是任务调度的实际执行者。
- Job和Trigger是一对多的关系,即一个Job可以同时关联多个Trigger。
2、Trigger
说明:
- SimpleTrigger:适用于【一次性执行,在指定时间点触发任务】。
属性值包括:开始时间、结束时间、重复次数和重复间隔。其中结束时间会覆盖重复次数。例如:
数据库导出数据。从明天凌晨1点开始,6点结束,每隔2分钟导出1000条数据。 - CronTrigger:基于Cron表达式的触发器,更加通用。CronTrigger也可以指定startTime和endTime。
3、Job
Job是Quartz框架的一个接口,当任务被触发时,Job 的execute() 方法会被调用。业务程序只需实现Job,在execute()中完成定时任务的业务逻辑。
Job VS JobDetail:
JobDetail是一个定时任务的定义(元数据),包含定时任务的类名和运行时的状态信息。Scheduler每次执行时,会根据JobDetail创建一个新的Job对象,并调用Job的execute() 方法,执行完毕后,释放Job对象。
这样规避了 并发访问Job对象的问题??
JobExecutionContext :包含Quartz运行时的环境以及Job本身的详细数据信息。
JobDataMap:是一个Map对象,用于存放Job的状态数据。由于JobDataMap数据会入库做持久化,所以存储自定义数据时要考虑对象的序列化问题。
@DisallowConcurrentExecution:在Job类加上该注解,表示禁止关联该Job的JobDetail的多个实例并发执行。
@PersistJobDataAfterExecution:在Job类加上该注解,表示在Job成功执行后,更新JobDetail中JobDataMap数据。建议结合@DisallowConcurrentExecution使用,可规避多个JobDetail并发更新数据的问题。
4、调度过程
Scheduler调度线程分为:常规调度线程和Misfire调度线程。
- 常规调度线程会扫描所有Trigger,如果有Trigger触发,则从任务执行线程池获取一个空闲线程,执行与该Trigger关联的Job。
- Misfire调度线程是扫描所有的Trigger,如果发现有Misfired Trigger,则根据指定的Misfire策略处理(【fire now】 或 【wait for the next fire】)
5、调度信息的存储
JobStore负责存储调度信息。具体可分为两类:
RAMJobStore:数据保存在内存。IO速度快,但重启或宕机会导致数据丢失。
JDBC JobStore:数据保存在数据库。数据会持久化。虽然IO速度比不上内存,但通过给表加上索引,性能下降并不会很糟糕。
6、关注问题
- 如何跟踪任务状态?
- 如何处理任务并发问题?