1 漫谈任务调度
1.1 什么时候需要任务调度?
1.1.1 任务调度的背景
在业务系统中有很多这样的场景:
1、账单日或者还款日上午 10 点,给每个信用卡客户发送账单通知,还款通知。如 何判断客户的账单日、还款日,完成通知的发送?
2、银行业务系统,夜间要完成跑批的一系列流程,清理数据,下载文件,解析文件, 对账清算、切换结算日期等等。如何触发一系列流程的执行?
3、金融机构跟人民银行二代支付系统对接,人民银行要求低于 5W 的金额(小额支付)半个小时打一次包发送,以缓解并发压力。所以,银行的跨行转账分成了多个流程: 录入、复核、发送。如何把半个小时以内的所有数据一次性发送?
类似于这种 1、基于准确的时刻或者固定的时间间隔触发的任务,或者 2、有批量数据需要处理,或者 3、要实现两个动作解耦的场景,我们都可以用任务调度来实现。
1.2 任务调度需求分析
任务调度的实现方式有很多,如果要实现我们的调度需求,我们对这个工具有什么样的基本要求呢?
1.2.1 基本需求
1)可以定义触发的规则,比如基于时刻、时间间隔、表达式。
2)可以定义需要执行的任务。比如执行一个脚本或者一段代码。任务和规则是分开的。
3)集中管理配置,持久配置。不用把规则写在代码里面,可以看到所有的任务配置,方便维护。重启之后任务可以再次调度——配置文件或者配置中心。
4)支持任务的串行执行,例如执行 A 任务后再执行 B 任务再执行 C 任务。
5)支持多个任务并发执行,互不干扰(例如 ScheduledThreadPoolExecutor)。
6)有自己的调度器,可以启动、中断、停止任务。
7)容易集成到 Spring。
1.3 任务调度工具对比
层次 | 举例 | 特点 |
操作系统 | Linux crontab <br>Windows 计划任务 | 只能执行简单脚本或者命令 |
数据库 | MySQL、Oracle | 可以操作数据。不能执行 Java 代码 |
工具 | Kettle | 可以操作数据,执行脚本。没有集中配置 |
开发语言 | JDK Timer、ScheduledThreadPool | Timer:单线程<br>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,由 J.U.C 提供