再见 Spring Task,这个定时任务框架真香!

最近有朋友问到定时任务相关的问题。

于是,我简单写了一篇文章总结一下定时任务的一些概念以及一些常见的定时任务技术选型。希望能对小伙伴们有帮助!

个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!

为什么需要定时任务?

我们来看一下几个非常常见的业务场景:

  1. 某系统凌晨要进行数据备份。

  2. 某电商平台,用户下单半个小时未支付的情况下需要自动取消订单。

  3. 某媒体聚合平台,每 10 分钟动态抓取某某网站的数据为自己所用。

  4. 某博客平台,支持定时发送文章。

  5. 某基金平台,每晚定时计算用户当日收益情况并推送给用户最新的数据。

  6. ......

这些场景往往都要求我们在某个特定的时间去做某个事情。

单机定时任务技术选型

Timer

java.util.Timer是 JDK 1.3 开始就已经支持的一种定时任务的实现方式。

Timer 内部使用一个叫做 TaskQueue 的类存放定时任务,它是一个基于最小堆实现的优先级队列。TaskQueue 会按照任务距离下一次执行时间的大小将任务排序,保证在堆顶的任务最先执行。这样在需要执行任务时,每次只需要取出堆顶的任务运行即可!

Timer 使用起来比较简单,通过下面的方式我们就能创建一个 1s 之后执行的定时任务。

// 示例代码:
TimerTask task = new TimerTask() {public void run() {System.out.println("当前时间: " + new Date() + "n" +"线程名称: " + Thread.currentThread().getName());}
};
System.out.println("当前时间: " + new Date() + "n" +"线程名称: " + Thread.currentThread().getName());
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);//输出:
当前时间: Fri May 28 15:18:47 CST 2021n线程名称: main
当前时间: Fri May 28 15:18:48 CST 2021n线程名称: Timer

不过其缺陷较多,比如一个 Timer 一个线程,这就导致 Timer 的任务的执行只能串行执行,一个任务执行时间过长的话会影响其他任务(性能非常差),再比如发生异常时任务直接停止(Timer 只捕获了 InterruptedException )。

Timer 类上的有一段注释是这样写的:

* This class does not offer real-time guarantees: it schedules* tasks using the <tt>Object.wait(long)</tt> method.*Java 5.0 introduced the {@code java.util.concurrent} package and* one of the concurrency utilities therein is the {@link* java.util.concurrent.ScheduledThreadPoolExecutor* ScheduledThreadPoolExecutor} which is a thread pool for repeatedly* executing tasks at a given rate or delay.  It is effectively a more* versatile replacement for the {@code Timer}/{@code TimerTask}* combination, as it allows multiple service threads, accepts various* time units, and doesn't require subclassing {@code TimerTask} (just* implement {@code Runnable}).  Configuring {@code* ScheduledThreadPoolExecutor} with one thread makes it equivalent to* {@code Timer}.

大概的意思就是:ScheduledThreadPoolExecutor 支持多线程执行定时任务并且功能更强大,是 Timer 的替代品。

ScheduledExecutorService

ScheduledExecutorService 是一个接口,有多个实现类,比较常用的是 ScheduledThreadPoolExecutor

f51ad6b4a8933eb8a9915cefebb69ce9.png

ScheduledThreadPoolExecutor 本身就是一个线程池,支持任务并发执行。并且,其内部使用 DelayQueue 作为任务队列。

// 示例代码:
TimerTask repeatedTask = new TimerTask() {@SneakyThrowspublic void run() {System.out.println("当前时间: " + new Date() + "n" +"线程名称: " + Thread.currentThread().getName());}
};
System.out.println("当前时间: " + new Date() + "n" +"线程名称: " + Thread.currentThread().getName());
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
long delay  = 1000L;
long period = 1000L;
executor.scheduleAtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS);
Thread.sleep(delay + period * 5);
executor.shutdown();
//输出:
当前时间: Fri May 28 15:40:46 CST 2021n线程名称: main
当前时间: Fri May 28 15:40:47 CST 2021n线程名称: pool-1-thread-1
当前时间: Fri May 28 15:40:48 CST 2021n线程名称: pool-1-thread-1
当前时间: Fri May 28 15:40:49 CST 2021n线程名称: pool-1-thread-2
当前时间: Fri May 28 15:40:50 CST 2021n线程名称: pool-1-thread-2
当前时间: Fri May 28 15:40:51 CST 2021n线程名称: pool-1-thread-2
当前时间: Fri May 28 15:40:52 CST 2021n线程名称: pool-1-thread-2

不论是使用 Timer 还是 ScheduledExecutorService 都无法使用 Cron 表达式指定任务执行的具体时间。

Spring Task

44122c25b2ca84d6b31b4f6ddf7ab153.png

我们直接通过 Spring 提供的 @Scheduled 注解即可定义定时任务,非常方便!

/*** cron:使用Cron表达式。 每分钟的1,2秒运行*/
@Scheduled(cron = "1-2 * * * * ? ")
public void reportCurrentTimeWithCronExpression() {log.info("Cron Expression: The time is now {}", dateFormat.format(new Date()));
}

我在大学那会做的一个 SSM 的企业级项目,就是用的 Spring Task 来做的定时任务。

并且,Spring Task 还是支持 Cron 表达式 的。Cron 表达式主要用于定时作业(定时任务)系统定义执行时间或执行频率的表达式,非常厉害,你可以通过 Cron 表达式进行设置定时任务每天或者每个月什么时候执行等等操作。咱们要学习定时任务的话,Cron 表达式是一定是要重点关注的。推荐一个在线 Cron 表达式生成器:http://cron.qqe2.com/ 。

但是,Spring 自带的定时调度只支持单机,并且提供的功能比较单一。之前写过一篇文章:《5 分钟搞懂如何在 Spring Boot 中 Schedule Tasks》

Spring Task 底层是基于 JDK 的 ScheduledThreadPoolExecutor 线程池来实现的。

优缺点总结:

  • 优点:简单,轻量,支持 Cron 表达式

  • 缺点 :功能单一

时间轮

Kafka、Dubbo、ZooKeeper、Netty 、Caffeine 、Akka 中都有对时间轮的实现。

时间轮简单来说就是一个环形的队列(底层一般基于数组实现),队列中的每一个元素(时间格)都可以存放一个定时任务列表。

时间轮中的每个时间格代表了时间轮的基本时间跨度或者说时间精度,加入时间一秒走一个时间格的话,那么这个时间轮的最高精度就是 1 秒(也就是说 3 s 和 3.9s 会在同一个时间格中)。

下图是一个有 12 个时间格的时间轮,转完一圈需要 12 s。当我们需要新建一个 3s 后执行的定时任务,只需要将定时任务放在下标为 3 的时间格中即可。当我们需要新建一个 9s 后执行的定时任务,只需要将定时任务放在下标为 9 的时间格中即可。

fe58df4726d3b77cc40b80a0b806bffe.png

那当我们需要创建一个 13s 后执行的定时任务怎么办呢?这个时候可以引入一叫做 圈数/轮数 的概念,也就是说这个任务还是放在下标为 3 的时间格中, 不过它的圈数为 2 。

除了增加圈数这种方法之外,还有一种 多层次时间轮 (类似手表),Kafka 采用的就是这种方案。

针对下图的时间轮,我来举一个例子便于大家理解。

dd9888175a10f3fa83e5fdb48c91d964.png

上图的时间轮,第 1 层的时间精度为 1 ,第 2 层的时间精度为 20 ,第 3 层的时间精度为 400。假如我们需要添加一个 350s 后执行的任务 A 的话(当前时间是 0s),这个任务会被放在第 2 层(因为第二层的时间跨度为 20*20=400>350)的第 350/20=17 个时间格子。

当第一层转了 17 圈之后,时间过去了 340s ,第 2 层的指针此时来到第 17 个时间格子。此时,第 2 层第 17 个格子的任务会被移动到第 1 层。

任务 A 当前是 10s 之后执行,因此它会被移动到第 1 层的第 10 个时间格子。

这里在层与层之间的移动也叫做时间轮的升降级。参考手表来理解就好!

69ca7c735b71368ffc57d0a5a76db384.png

时间轮比较适合任务数量比较多的定时任务场景,它的任务写入和执行的时间复杂度都是 0(1)。

分布式定时任务技术选型

上面提到的一些定时任务的解决方案都是在单机下执行的,适用于比较简单的定时任务场景比如每天凌晨备份一次数据。

如果我们需要一些高级特性比如支持任务在分布式场景下的分片和高可用的话,我们就需要用到分布式任务调度框架了。

通常情况下,一个定时任务的执行往往涉及到下面这些角色:

  • 任务 :首先肯定是要执行的任务,这个任务就是具体的业务逻辑比如定时发送文章。

  • 调度器 :其次是调度中心,调度中心主要负责任务管理,会分配任务给执行器。

  • 执行器 :最后就是执行器,执行器接收调度器分派的任务并执行。

Quartz

250bb1087edc15e3a18a08b52cf75034.png

一个很火的开源任务调度框架,完全由Java写成。Quartz 可以说是 Java 定时任务领域的老大哥或者说参考标准,其他的任务调度框架基本都是基于 Quartz 开发的,比如当当网的elastic-job就是基于quartz二次开发之后的分布式调度解决方案。

使用 Quartz 可以很方便地与 Spring 集成,并且支持动态添加任务和集群。但是,Quartz 使用起来也比较麻烦,API 繁琐。

并且,Quzrtz 并没有内置 UI 管理控制台,不过你可以使用 quartzui 这个开源项目来解决这个问题。

另外,Quartz 虽然也支持分布式任务。但是,它是在数据库层面,通过数据库的锁机制做的,有非常多的弊端比如系统侵入性严重、节点负载不均衡。有点伪分布式的味道。

优缺点总结:

  • 优点:可以与 Spring 集成,并且支持动态添加任务和集群。

  • 缺点 :分布式支持不友好,没有内置 UI 管理控制台、使用麻烦(相比于其他同类型框架来说)

Elastic-Job

5498c23f17294a8cfbfa16349c9849f0.png

Elastic-Job 是当当网开源的一个基于QuartzZooKeeper的分布式调度解决方案,由两个相互独立的子项目 Elastic-Job-LiteElastic-Job-Cloud 组成,一般我们只要使用 Elastic-Job-Lite 就好。

ElasticJob 支持任务在分布式场景下的分片和高可用、任务可视化管理等功能。

8db297a5ceb0a774cecd17c61e2dc077.png

ElasticJob-Lite 的架构设计如下图所示:

d660366bf2df0ae1cbe94526ab40b78c.png

从上图可以看出,Elastic-Job 没有调度中心这一概念,而是使用 ZooKeeper 作为注册中心,注册中心负责协调分配任务到不同的节点上。

Elastic-Job 中的定时调度都是由执行器自行触发,这种设计也被称为去中心化设计(调度和处理都是执行器单独完成)。

@Component
@ElasticJobConf(name = "dayJob", cron = "0/10 * * * * ?", shardingTotalCount = 2,shardingItemParameters = "0=AAAA,1=BBBB", description = "简单任务", failover = true)
public class TestJob implements SimpleJob {@Overridepublic void execute(ShardingContext shardingContext) {log.info("TestJob任务名:【{}】, 片数:【{}】, param=【{}】", shardingContext.getJobName(), shardingContext.getShardingTotalCount(),shardingContext.getShardingParameter());}
}

相关地址:

  • Github 地址:https://github.com/apache/shardingsphere-elasticjob。

  • 官方网站:https://shardingsphere.apache.org/elasticjob/index_zh.html 。

优缺点总结:

  • 优点 :可以与 Spring 集成、支持分布式、支持集群、性能不错

  • 缺点 :依赖了额外的中间件比如 Zookeeper(复杂度增加,可靠性降低、维护成本变高)

XXL-JOB

36d990bcefec4ae3cebf6e0111958e2c.png

XXL-JOB 于 2015 年开源,是一款优秀的轻量级分布式任务调度框架,支持任务可视化管理、弹性扩容缩容、任务失败重试和告警、任务分片等功能,

774e17f5b87b691479cd0e41db5fb852.png

根据 XXL-JOB 官网介绍,其解决了很多 Quartz 的不足。

55c971270266883ad035792f6c3f33a8.png

XXL-JOB 的架构设计如下图所示:

64fe7c22c66521c74f6a3065aa69f19e.png

从上图可以看出,XXL-JOB调度中心执行器 两大部分组成。调度中心主要负责任务管理、执行器管理以及日志管理。执行器主要是接收调度信号并处理。另外,调度中心进行任务调度时,是通过自研 RPC 来实现的。

不同于 Elastic-Job 的去中心化设计, XXL-JOB 的这种设计也被称为中心化设计(调度中心调度多个执行器执行任务)。

Quzrtz 类似 XXL-JOB 也是基于数据库锁调度任务,存在性能瓶颈。不过,一般在任务量不是特别大的情况下,没有什么影响的,可以满足绝大部分公司的要求。

不要被 XXL-JOB 的架构图给吓着了,实际上,我们要用 XXL-JOB 的话,只需要重写 IJobHandler 自定义任务执行逻辑就可以了,非常易用!

@JobHandler(value="myApiJobHandler")
@Component
public class MyApiJobHandler extends IJobHandler {@Overridepublic ReturnT<String> execute(String param) throws Exception {//......return ReturnT.SUCCESS;}
}

还可以直接基于注解定义任务。

@XxlJob("myAnnotationJobHandler")
public ReturnT<String> myAnnotationJobHandler(String param) throws Exception {//......return ReturnT.SUCCESS;
}
263f58d803b9c842f2b2285913626368.png

相关地址:

  • Github 地址:https://github.com/xuxueli/xxl-job/。

  • 官方介绍:https://www.xuxueli.com/xxl-job/ 。

优缺点总结:

  • 优点:开箱即用(学习成本比较低)、与 Spring 集成、支持分布式、支持集群、内置了 UI 管理控制台。

  • 缺点:不支持动态添加任务(如果一定想要动态创建任务也是支持的,参见:xxl-job issue277)。

PowerJob

9ad448be19d478f5c96ac55e7ba85fd8.png

非常值得关注的一个分布式任务调度框架,分布式任务调度领域的新星。目前,已经有很多公司接入比如 OPPO、京东、中通、思科。

这个框架的诞生也挺有意思的,PowerJob 的作者当时在阿里巴巴实习过,阿里巴巴那会使用的是内部自研的 SchedulerX(阿里云付费产品)。实习期满之后,PowerJob 的作者离开了阿里巴巴。想着说自研一个 SchedulerX,防止哪天 SchedulerX 满足不了需求,于是 PowerJob 就诞生了。

更多关于 PowerJob 的故事,小伙伴们可以去看看 PowerJob 作者的视频 《我和我的任务调度中间件》。简单点概括就是:“游戏没啥意思了,我要扛起了新一代分布式任务调度与计算框架的大旗!”。

由于 SchedulerX 属于人民币产品,我这里就不过多介绍。PowerJob 官方也对比过其和 QuartZ、XXL-JOB 以及 SchedulerX。

b8967429ea5f9608b55304169cf00c8a.png

总结

这篇文章中,我主要介绍了:

  • 定时任务的相关概念 :为什么需要定时任务、定时任务中的核心角色、分布式定时任务。

  • 定时任务的技术选型 :XXL-JOB 2015 年推出,已经经过了很多年的考验。XXL-JOB 轻量级,并且使用起来非常简单。虽然存在性能瓶颈,但是,在绝大多数情况下,对于企业的基本需求来说是没有影响的。PowerJob 属于分布式任务调度领域里的新星,其稳定性还有待继续考察。ElasticJob 由于在架构设计上是基于 Zookeeper ,而 XXL-JOB 是基于数据库,性能方面的话,ElasticJob 略胜一筹。

这篇文章并没有介绍到实际使用,但是,并不代表实际使用不重要。我在写这篇文章之前,已经动手写过相应的 Demo。像 Quartz,我在大学那会就用过。不过,当时用的是 Spring 。为了能够更好地体验,我自己又在 Spring Boot 上实际体验了一下。如果你并没有实际使用某个框架,就直接说它并不好用的话,是站不住脚的。

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

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

相关文章

C语言实现动画控制

文章目录原材料说明一场革命原材料 下载原材料网址: https://www.easyx.cn/downloads/ 下载easyx2014冬至版&#xff0c;将lib文件放在编译器默认的lib文件夹&#xff0c;h头文件放在编译器默认的include文件夹即可 说明 C语言可以用系统内部的定时函数sleep和usleep定时(需…

聊聊redis分布式锁的8大坑

前言在分布式系统中&#xff0c;由于redis分布式锁相对于更简单和高效&#xff0c;成为了分布式锁的首先&#xff0c;被我们用到了很多实际业务场景当中。但不是说用了redis分布式锁&#xff0c;就可以高枕无忧了&#xff0c;如果没有用好或者用对&#xff0c;也会引来一些意想…

MyBatis 批量插入数据的 3 种方法!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone批量插入功能是我们日常工作中比较常见的业务功能之一&#xff0c;之前我也写过一篇关于《MyBatis Plus 批量数据插入功能&#xff0c;yy…

MongoDB: The Definitive Guide

第一章 简介 MongoDB是面向文档的数据库&#xff0c;不是关系型数据库。内置对MapReduce的支持&#xff0c;以及对地理空间索引的支持。 丰富的数据模型容易扩展&#xff0c;它所采用的面向文档的数据模型可以使其在多台服务器之间分割数据丰富的功能&#xff0c;索引、存储Jav…

Python联网下载文件

声明 Python版本2.7.3所需Py文件——urllib22.7.3版本的Python Shell即可直接执行&#xff0c;但需要联网若程序执行成功&#xff0c;则会下载以下网址的txt文本并打印在shell中 http://helloworldbook2.com/data/message.txt 本代码来源于《父与子的编程之旅——与小卡特一起…

如何给SpringBoot配置轻松加密?

在实践中&#xff0c;项目的某些配置信息是需要进行加密处理的&#xff0c;以减少敏感信息泄露的风险。比如&#xff0c;在使用Druid时&#xff0c;就可以基于它提供的公私钥加密方式对数据库的密码进行加密。但更多时候&#xff0c;比如Redis密码、MQ密码等敏感信息&#xff0…

C语言将循环小数/有限小数转换为分数

文章目录数学基础编程思路代码数学基础 早在小学的时候我就对循环小数非常感兴趣&#xff0c;加上初中和高中对循环小数可以说有一定基础研究&#xff0c;因此想到写一个将循环下小数转换为分数的程序&#xff0c;非常有意思&#xff0c;并且对初学者来说&#xff0c;它的输入…

升级了 Windows 11 正式版,有坑吗?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;今天磊哥去公司上班&#xff0c;惊喜的发现 Windows 提示更新了&#xff0c;并且是 Windows 11 正式版&#xff0c;这太让人…

C语言结构体的应用——万年历

文章目录万年历简述代码万年历简述 万年历——就是输入一个日期可以查询是星期几&#xff0c;这个功能看起来很普通&#xff0c;但是如果用程序时间的话&#xff0c;还是药费一番周折: 我们需要保存一个固定的日期&#xff0c;存放它是星期几&#xff0c;输入一个自定义的日期…

@Value竟然能玩出这么多花样

前言对于从事java开发工作的小伙伴来说&#xff0c;spring框架肯定再熟悉不过了。spring给开发者提供了非常丰富的api&#xff0c;满足我们日常的工作需求。如果想要创建bean实例&#xff0c;可以使用Controller、Service、Repository、Component等注解。如果想要依赖注入某个对…

C语言实现线性动态(单向)链表【详细步骤】

文章目录什么是链表为什么不用结构体数组链表的操作创建表删除元素插入元素代码及运行结果什么是链表 链表是数据结构里面的一种&#xff0c;线性链表是链表的一种&#xff0c;线性链表的延伸有双向链表和环形链表。在编程语言中优化数据结构可以在处理大数据时大大降低程序的…

移动前端经验小结

1. 移动端头部标签 head meta <!DOCTYPE html> <!-- 使用 HTML5 doctype&#xff0c;不区分大小写 --> <html lang"zh-cmn-Hans"> <!-- 更加标准的 lang 属性写法 http://zhi.hu/XyIa --> <head><!-- 声明文档使用的字符编码 -->…

再见收费的Navicat!操作所有数据库靠它就够了!

为了快速管理数据库&#xff0c;我们一般都会选择一款顺手的数据库管理工具。Navicat、DataGrip虽然很好用&#xff0c;但都是收费的。今天给大家推荐一款免费、功能强大的数据库管理工具DBeaver&#xff0c;希望对大家有所帮助&#xff01;DBeaver简介 DBeaver是一款开源的数据…

查找两个字符串中相同字符串_使两个字符串相同的最低成本

查找两个字符串中相同字符串Problem statement: 问题陈述&#xff1a; Given two strings string1 and string2 find the minimum cost required to make the given two strings identical. We can delete characters from both the strings. The cost of deleting a characte…

Matlab对指定参数的曲线进行非线性拟合

Matlab拟合曲线的方式 Matlab拟合曲线的方式有很多种&#xff0c;有三次样条插值、线性插值、多项式拟合等等。多项式拟合由于函数由f(x)anxnan−1xn−1...a1xa0f(x)a_nx^na_{n-1}x^{n-1}...a_1xa_0f(x)an​xnan−1​xn−1...a1​xa0​组成&#xff0c;若采用最小二乘法拟合&a…

MyBatis原生批量插入的坑与解决方案!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;前面的文章咱们讲了 MyBatis 批量插入的 3 种方法&#xff1a;循环单次插入、MyBatis Plus 批量插入、MyBatis 原生批量插入…

系统结构图 数据结构_数据结构图简介

系统结构图 数据结构What you are going to learn? 你要学什么&#xff1f; In this article, we learn about the introduction to Graphs in Data Structure and Algorithm. 在本文中&#xff0c;我们将了解图在数据结构和算法中的介绍 。 What are the components in Gra…

Matlab仿真PID控制(带M文件、simulink截图和参数分析)

文章目录0.符号说明1.如何根据连续系统建立差分方程1.1.获取连续系统的传递函数1.2.获取离散系统的传递函数1.3.转换为差分方程2.基本PID控制原理3.比较PID输出&#xff0c;分析参数产生的影响4.改进PID算法&#xff08;遇限削弱积分法&#xff09;5.simulink仿真0.符号说明 y…

再见 Postman!Apifox 才是 YYDS!

作为开软件开发从业者&#xff0c;API 调试是必不可少的一项技能&#xff0c;在这方面 Postman 做的非常出色。但是在整个软件开发过程中&#xff0c;API 调试只是其中的一部分&#xff0c;还有很多事情 Postman 无法完成&#xff0c;或者无法高效完成&#xff0c;比如&#xf…

Matlab【可视化作图】绘制线电压相电压辅助线

目录引言绘图原理采点绘图设置坐标轴标尺引言 学习电力电子的同学可能在私下里练习的时候非常需要三相线电压和相电压的辅助线。最近我随便找了一本书把Matlab可视化编程恶补了一下&#xff0c;给大家介绍一下这个波形辅助线是怎么做的。 三相线电压辅助线就是一组相位相差60的…