@@ -15,16 +15,21 @@ head:
1515
1616我们来看一下几个非常常见的业务场景:
1717
18- 1 . 某系统凌晨要进行数据备份 。
18+ 1 . 某系统凌晨 1 点要进行数据备份 。
19192 . 某电商平台,用户下单半个小时未支付的情况下需要自动取消订单。
20203 . 某媒体聚合平台,每 10 分钟动态抓取某某网站的数据为自己所用。
21214 . 某博客平台,支持定时发送文章。
22225 . 某基金平台,每晚定时计算用户当日收益情况并推送给用户最新的数据。
23236 . ......
2424
25- 这些场景往往都要求我们在某个特定的时间去做某个事情。
25+ 这些场景往往都要求我们在某个特定的时间去做某个事情,也就是定时或者延时去做某个事情 。
2626
27- ## 单机定时任务技术选型
27+ - 定时任务:在指定时间点执行特定的任务,例如每天早上 8 点,每周一下午 3 点等。定时任务可以用来做一些周期性的工作,如数据备份,日志清理,报表生成等。
28+ - 延时任务:一定的延迟时间后执行特定的任务,例如 10 分钟后,3 小时后等。延时任务可以用来做一些异步的工作,如订单取消,推送通知,红包撤回等。
29+
30+ 尽管二者的适用场景有所区别,但它们的核心思想都是将任务的执行时间安排在未来的某个点上,以达到预期的调度效果。
31+
32+ ## 单机定时任务
2833
2934### Timer
3035
@@ -113,6 +118,16 @@ executor.shutdown();
113118
114119不论是使用 `Timer` 还是 `ScheduledExecutorService` 都无法使用 Cron 表达式指定任务执行的具体时间。
115120
121+ ### DelayQueue
122+
123+ `DelayQueue` 是 JUC 包(`java.util.concurrent)`为我们提供的延迟队列,用于实现延时任务比如订单下单 15 分钟未支付直接取消。它是 `BlockingQueue` 的一种,底层是一个基于 `PriorityQueue` 实现的一个无界队列,是线程安全的。关于`PriorityQueue`可以参考笔者编写的这篇文章:[PriorityQueue 源码分析](https://javaguide.cn/java/collection/priorityqueue-source-code.html) 。
124+
125+ 
126+
127+ `DelayQueue` 和 `Timer/TimerTask` 都可以用于实现定时任务调度,但是它们的实现方式不同。`DelayQueue` 是基于优先级队列和堆排序算法实现的,可以实现多个任务按照时间先后顺序执行;而 `Timer/TimerTask` 是基于单线程实现的,只能按照任务的执行顺序依次执行,如果某个任务执行时间过长,会影响其他任务的执行。另外,`DelayQueue` 还支持动态添加和移除任务,而 `Timer/TimerTask` 只能在创建时指定任务。
128+
129+ 关于 `DelayQueue` 的详细介绍,请参考我写的这篇文章:[`DelayQueue` 源码分析](https://javaguide.cn/java/collection/delayqueue-source-code.html)。
130+
116131### Spring Task
117132
118133我们直接通过 Spring 提供的 `@Scheduled` 注解即可定义定时任务,非常方便!
@@ -153,7 +168,7 @@ Kafka、Dubbo、ZooKeeper、Netty、Caffeine、Akka 中都有对时间轮的实
153168
154169
155170
156- 那当我们需要创建一个 13s 后执行的定时任务怎么办呢?这个时候可以引入一叫做 **圈数/轮数** 的概念,也就是说这个任务还是放在下标为 3 的时间格中, 不过它的圈数为 2 。
171+ 那当我们需要创建一个 13s 后执行的定时任务怎么办呢?这个时候可以引入一叫做 **圈数/轮数** 的概念,也就是说这个任务还是放在下标为 1 的时间格中, 不过它的圈数为 2 。
157172
158173除了增加圈数这种方法之外,还有一种 **多层次时间轮** (类似手表),Kafka 采用的就是这种方案。
159174
@@ -171,36 +186,58 @@ Kafka、Dubbo、ZooKeeper、Netty、Caffeine、Akka 中都有对时间轮的实
171186
172187**时间轮比较适合任务数量比较多的定时任务场景,它的任务写入和执行的时间复杂度都是 0(1)。**
173188
174- ## 分布式定时任务技术选型
189+ ## 分布式定时任务
190+
191+ ### Redis
192+
193+ Redis 是可以用来做延时任务的,基于 Redis 实现延时任务的功能无非就下面两种方案:
194+
195+ 1. Redis 过期事件监听
196+ 2. Redisson 内置的延时队列
197+
198+ 这部分内容的详细介绍我放在了[《后端面试高频系统设计&场景题》](https://javaguide.cn/zhuanlan/back-end-interview-high-frequency-system-design-and-scenario-questions.html)中,有需要的同学可以进入星球后阅读学习。篇幅太多,这里就不重复分享了。
175199
176- 上面提到的一些定时任务的解决方案都是在单机下执行的,适用于比较简单的定时任务场景比如每天凌晨备份一次数据。
200+ 
201+
202+ ### MQ
203+
204+ 大部分消息队列,例如 RocketMQ、RabbitMQ,都支持定时/延时消息。定时消息和延时消息本质其实是相同的,都是服务端根据消息设置的定时时间在某一固定时刻将消息投递给消费者消费。
205+
206+ 不过,在使用 MQ 定时消息之前一定要看清楚其使用限制,以免不适合项目需求,例如 RocketMQ 定时时长最大值默认为 24 小时且不支持自定义修改、只支持 18 个 Level 的延时并不支持任意时间。
207+
208+ **优缺点总结:**
209+
210+ - **优点**:可以与 Spring 集成、支持分布式、支持集群、性能不错
211+ - **缺点**:功能性较差、不灵活、需要保障消息可靠性
212+
213+ ## 分布式任务调度框架
177214
178215如果我们需要一些高级特性比如支持任务在分布式场景下的分片和高可用的话,我们就需要用到分布式任务调度框架了。
179216
180- 通常情况下,一个定时任务的执行往往涉及到下面这些角色 :
217+ 通常情况下,一个分布式定时任务的执行往往涉及到下面这些角色 :
181218
182219- **任务**:首先肯定是要执行的任务,这个任务就是具体的业务逻辑比如定时发送文章。
183220- **调度器**:其次是调度中心,调度中心主要负责任务管理,会分配任务给执行器。
184221- **执行器**:最后就是执行器,执行器接收调度器分派的任务并执行。
185222
186223### Quartz
187224
188- 一个很火的开源任务调度框架,完全由Java写成 。Quartz 可以说是 Java 定时任务领域的老大哥或者说参考标准,其他的任务调度框架基本都是基于 Quartz 开发的,比如当当网的`elastic-job`就是基于Quartz二次开发之后的分布式调度解决方案 。
225+ 一个很火的开源任务调度框架,完全由 Java 写成 。Quartz 可以说是 Java 定时任务领域的老大哥或者说参考标准,其他的任务调度框架基本都是基于 Quartz 开发的,比如当当网的`elastic-job`就是基于 Quartz 二次开发之后的分布式调度解决方案 。
189226
190- 使用 Quartz 可以很方便地与 Spring集成 ,并且支持动态添加任务和集群。但是,Quartz 使用起来也比较麻烦,API 繁琐。
227+ 使用 Quartz 可以很方便地与 Spring 集成 ,并且支持动态添加任务和集群。但是,Quartz 使用起来也比较麻烦,API 繁琐。
191228
192229并且,Quartz 并没有内置 UI 管理控制台,不过你可以使用 [quartzui](https://github.com/zhaopeiym/quartzui) 这个开源项目来解决这个问题。
193230
194231另外,Quartz 虽然也支持分布式任务。但是,它是在数据库层面,通过数据库的锁机制做的,有非常多的弊端比如系统侵入性严重、节点负载不均衡。有点伪分布式的味道。
195232
196233**优缺点总结:**
197234
198- - 优点:可以与 Spring集成 ,并且支持动态添加任务和集群。
199- - 缺点:分布式支持不友好,没有内置 UI 管理控制台 、使用麻烦(相比于其他同类型框架来说)
235+ - 优点:可以与 Spring 集成 ,并且支持动态添加任务和集群。
236+ - 缺点:分布式支持不友好,不支持任务可视化管理 、使用麻烦(相比于其他同类型框架来说)
200237
201238### Elastic-Job
202239
203- ElasticJob 当当网开源的一个面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目 ElasticJob-Lite 和 ElasticJob-Cloud 组成。
240+ ElasticJob 当当网开源的一个面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目 ElasticJob-Lite 和 ElasticJob-Cloud 组成。
204241
205242ElasticJob-Lite 和 ElasticJob-Cloud 两者的对比如下:
206243
@@ -219,7 +256,7 @@ ElasticJob-Lite 的架构设计如下图所示:
219256
220257
221258
222- 从上图可以看出,Elastic-Job没有调度中心这一概念 ,而是使用 ZooKeeper 作为注册中心,注册中心负责协调分配任务到不同的节点上。
259+ 从上图可以看出,Elastic-Job 没有调度中心这一概念 ,而是使用 ZooKeeper 作为注册中心,注册中心负责协调分配任务到不同的节点上。
223260
224261Elastic-Job 中的定时调度都是由执行器自行触发,这种设计也被称为去中心化设计(调度和处理都是执行器单独完成)。
225262
@@ -243,7 +280,7 @@ public class TestJob implements SimpleJob {
243280
244281**优缺点总结:**
245282
246- - 优点:可以与 Spring集成 、支持分布式、支持集群、性能不错
283+ - 优点:可以与 Spring 集成 、支持分布式、支持集群、性能不错、支持任务可视化管理
247284- 缺点:依赖了额外的中间件比如 Zookeeper(复杂度增加,可靠性降低、维护成本变高)
248285
249286### XXL-JOB
@@ -254,22 +291,22 @@ public class TestJob implements SimpleJob {
254291
255292根据 `XXL-JOB` 官网介绍,其解决了很多 Quartz 的不足。
256293
257- > Quartz作为开源作业调度中的佼佼者 ,是作业调度的首选。但是集群环境中Quartz采用API的方式对任务进行管理 ,从而可以避免上述问题,但是同样存在以下问题:
294+ > Quartz 作为开源作业调度中的佼佼者 ,是作业调度的首选。但是集群环境中 Quartz 采用 API 的方式对任务进行管理 ,从而可以避免上述问题,但是同样存在以下问题:
258295>
259- > - 问题一:调用API的的方式操作任务 ,不人性化;
260- > - 问题二:需要持久化业务QuartzJobBean到底层数据表中 ,系统侵入性相当严重。
261- > - 问题三:调度逻辑和QuartzJobBean耦合在同一个项目中 ,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;
262- > - 问题四:quartz底层以 “抢占式”获取DB锁并由抢占成功节点负责运行任务 ,会导致节点负载悬殊非常大;而XXL-JOB通过执行器实现 “协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。
296+ > - 问题一:调用 API 的的方式操作任务 ,不人性化;
297+ > - 问题二:需要持久化业务 QuartzJobBean 到底层数据表中 ,系统侵入性相当严重。
298+ > - 问题三:调度逻辑和 QuartzJobBean 耦合在同一个项目中 ,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;
299+ > - 问题四:quartz 底层以 “抢占式”获取 DB 锁并由抢占成功节点负责运行任务 ,会导致节点负载悬殊非常大;而 XXL-JOB 通过执行器实现 “协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。
263300>
264- > XXL-JOB弥补了quartz的上述不足之处 。
301+ > XXL-JOB 弥补了 quartz 的上述不足之处 。
265302
266303`XXL-JOB` 的架构设计如下图所示:
267304
268305
269306
270307从上图可以看出,`XXL-JOB` 由 **调度中心** 和 **执行器** 两大部分组成。调度中心主要负责任务管理、执行器管理以及日志管理。执行器主要是接收调度信号并处理。另外,调度中心进行任务调度时,是通过自研 RPC 来实现的。
271308
272- 不同于 Elastic-Job的去中心化设计 , `XXL-JOB` 的这种设计也被称为中心化设计(调度中心调度多个执行器执行任务)。
309+ 不同于 Elastic-Job 的去中心化设计 , `XXL-JOB` 的这种设计也被称为中心化设计(调度中心调度多个执行器执行任务)。
273310
274311和 `Quzrtz` 类似 `XXL-JOB` 也是基于数据库锁调度任务,存在性能瓶颈。不过,一般在任务量不是特别大的情况下,没有什么影响的,可以满足绝大部分公司的要求。
275312
@@ -307,7 +344,7 @@ public ReturnT<String> myAnnotationJobHandler(String param) throws Exception {
307344
308345**优缺点总结:**
309346
310- - 优点:开箱即用(学习成本比较低)、与 Spring 集成、支持分布式、支持集群、内置了 UI 管理控制台 。
347+ - 优点:开箱即用(学习成本比较低)、与 Spring 集成、支持分布式、支持集群、支持任务可视化管理 。
311348- 缺点:不支持动态添加任务(如果一定想要动态创建任务也是支持的,参见:[xxl-job issue277](https://github.com/xuxueli/xxl-job/issues/277))。
312349
313350### PowerJob
@@ -320,17 +357,28 @@ public ReturnT<String> myAnnotationJobHandler(String param) throws Exception {
320357
321358由于 SchedulerX 属于人民币产品,我这里就不过多介绍。PowerJob 官方也对比过其和 QuartZ、XXL-JOB 以及 SchedulerX。
322359
323- 
360+ | | QuartZ | xxl-job | SchedulerX 2.0 | PowerJob |
361+ | -------------- | ------------------------------------------ | ---------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------ |
362+ | 定时类型 | CRON | CRON | CRON、固定频率、固定延迟、OpenAPI | **CRON、固定频率、固定延迟、OpenAPI** |
363+ | 任务类型 | 内置Java | 内置Java、GLUE Java、Shell、Python等脚本 | 内置Java、外置Java(FatJar)、Shell、Python等脚本 | **内置Java、外置Java(容器)、Shell、Python等脚本** |
364+ | 分布式计算 | 无 | 静态分片 | MapReduce动态分片 | **MapReduce动态分片** |
365+ | 在线任务治理 | 不支持 | 支持 | 支持 | **支持** |
366+ | 日志白屏化 | 不支持 | 支持 | 不支持 | **支持** |
367+ | 调度方式及性能 | 基于数据库锁,有性能瓶颈 | 基于数据库锁,有性能瓶颈 | 不详 | **无锁化设计,性能强劲无上限** |
368+ | 报警监控 | 无 | 邮件 | 短信 | **WebHook、邮件、钉钉与自定义扩展** |
369+ | 系统依赖 | JDBC支持的关系型数据库(MySQL、Oracle...) | MySQL | 人民币 | **任意Spring Data Jpa支持的关系型数据库(MySQL、Oracle...)** |
370+ | DAG工作流 | 不支持 | 不支持 | 支持 | **支持** |
324371
325- ## 总结
372+ ## 定时任务方案总结
326373
327- 这篇文章中,我主要介绍了:
374+ 单机定时任务的常见解决方案有 `Timer`、`ScheduledExecutorService`、`DelayQueue`、Spring Task 和时间轮,其中最常用也是比较推荐使用的是时间轮。另外,这几种单机定时任务解决方案同样可以实现延时任务。
328375
329- - **定时任务的相关概念**:为什么需要定时任务、定时任务中的核心角色、分布式定时任务。
330- - **定时任务的技术选型**:XXL-JOB 2015 年推出,已经经过了很多年的考验。XXL-JOB 轻量级,并且使用起来非常简单。虽然存在性能瓶颈,但是,在绝大多数情况下,对于企业的基本需求来说是没有影响的。PowerJob 属于分布式任务调度领域里的新星,其稳定性还有待继续考察。ElasticJob 由于在架构设计上是基于 Zookeeper ,而 XXL-JOB 是基于数据库,性能方面的话,ElasticJob 略胜一筹。
376+ Redis 和 MQ 虽然可以实现分布式定时任务,但这两者本身不是专门用来做分布式定时任务的,它们并不提供较为完整和强大的分布式定时任务的功能。而且,两者不太适合执行周期性的定时任务,因为它们只能保证消息被消费一次,而不能保证消息被消费多次。因此,它们更适合执行一次性的延时任务,例如订单取消、红包撤回。实际项目中,MQ 延时任务用的更多一些,可以降低业务之间的耦合度。
331377
332- 这篇文章并没有介绍到实际使用,但是,并不代表实际使用不重要。我在写这篇文章之前,已经动手写过相应的 Demo。像 Quartz,我在大学那会就用过。不过,当时用的是 Spring 。为了能够更好地体验,我自己又在 Spring Boot 上实际体验了一下。如果你并没有实际使用某个框架,就直接说它并不好用的话,是站不住脚的 。
378+ Quartz、Elastic-Job、XXL-JOB 和 PowerJob 这几个是专门用来做分布式调度的框架,提供的分布式定时任务的功能更为完善和强大,更加适合执行周期性的定时任务。除了 Quartz 之外,另外三者都是支持任务可视化管理的 。
333379
334- 最后,这篇文章要感谢艿艿的帮助,写这篇文章的时候向艿艿询问过一些问题。推荐一篇艿艿写的偏实战类型的硬核文章:[《Spring Job?Quartz?XXL-Job?年轻人才做选择,艿艿全莽~》](https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247490679&idx=1&sn=25374dbdcca95311d41be5d7b7db454d&chksm=fa4963c6cd3eead055bb9cd10cca13224bb35d0f7373a27aa22a55495f71e24b8273a7603314&scene=27#wechat_redirect) 。
380+ XXL-JOB 2015 年推出,已经经过了很多年的考验。XXL-JOB 轻量级,并且使用起来非常简单。虽然存在性能瓶颈,但是,在绝大多数情况下,对于企业的基本需求来说是没有影响的。PowerJob 属于分布式任务调度领域里的新星,其稳定性还有待继续考察。ElasticJob 由于在架构设计上是基于 Zookeeper ,而 XXL-JOB 是基于数据库,性能方面的话,ElasticJob 略胜一筹。
381+
382+ 这篇文章并没有介绍到实际使用,但是,并不代表实际使用不重要。我在写这篇文章之前,已经动手写过相应的 Demo。像 Quartz,我在大学那会就用过。不过,当时用的是 Spring 。为了能够更好地体验,我自己又在 Spring Boot 上实际体验了一下。如果你并没有实际使用某个框架,就直接说它并不好用的话,是站不住脚的。
335383
336384<!-- @include: @article-footer.snippet.md -->
0 commit comments