第二十二章:定时任务的使用

前言

上两章节,我们简单的讲解了关于异步调用和异步请求相关知识点。这一章节,我们来讲讲开发过程也是经常会碰见的定时任务。比如每天定时清理无效数据、定时发送短信、定时发送邮件、支付系统中的定时对账等等,往往都会定义一些定时器,进行此业务的开发。所以,本章节介绍下在 中定时任务如何使用及一点分布式定时服务的思考总结。

一点知识

基于JDK方式实现简单定时

Timer

ScheduledExecutorService

基于SpingTask实现定时任务

ThreadPoolTaskScheduler

SchedulingConfigurer

Cron表达式详解

自定义线程池

动态添加定时任务

基于Quartz实现定时调度

分布式调度服务浅谈

参考资料

总结

最后

老生常谈

一点知识

在 开发领域,目前可以通过以下几种方式进行定时任务:

Timer:jdk中自带的一个定时调度类,可以简单的实现按某一频度进行任务执行。提供的功能比较单一,无法实现复杂的调度任务。

ScheduledExecutorService:也是jdk自带的一个基于线程池设计的定时任务类。其每个调度任务都会分配到线程池中的一个线程执行,所以其任务是并发执行的,互不影响。

Spring Task: 提供的一个任务调度工具,支持注解和配置文件形式,支持 表达式,使用简单但功能强大。

Quartz:一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行、每天凌晨执行、每周五执行等等,还支持分布式调度,就是配置稍显复杂。

题外话:对于 ,早前用过1.6版本的,更新到2.x及以上版本后基本没怎么接触了,原来还有倒腾过结合 做了一些动态的定时抽取数据啥的还编写过一个 表达式编辑器,现在基本忘记了。。等有机会,再次深入学习后再来单独分享一些关于的 心得吧。

基于JDK方式实现简单定时

刚刚有介绍过,基于 方式一共有两种: 和 。接下来,就简单讲解下这两种方式。

Timer

是jdk提供的 类。

简单示例:

启动后,访问即可看见控制台周期性输出信息了:

相关API简单说明:

1、在特定时间执行任务,只执行一次

2、在特定时间之后执行任务,只执行一次

3、指定第一次执行的时间,然后按照间隔时间,重复执行

4、在特定延迟之后第一次执行,然后按照间隔时间,重复执行

5、第一次执行之后,特定频率执行,与3同

6、在delay毫秒之后第一次执行,后按照特定频率执行

参数:

delay: 延迟执行的毫秒数,即在delay毫秒之后第一次执行

period:重复执行的时间间隔

取消任务使用: 方法即可注销任务。

此类相对用的较少了,简单了解下。

ScheduledExecutorService

可以说是 的替代类,因为 不支持多线程,任务是串行的,而且也不捕获异常,假设某个任务异常了,整个 就无法运行了。

简单示例:

启动后,可看见控制台按设定的频率输出:

可同时设置多个任务,只需再次设置 即可。

常用方法说明:

ScheduleAtFixedRate:

参数说明:

command:执行线程

initialDelay:初始化延时

period:两次开始执行最小间隔时间

unit:计时单位

ScheduleWithFixedDelay:

参数说明:

command:执行线程

initialDelay:初始化延时

delay:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)

unit:计时单位

其他的方法大家可自行谷歌下。

基于SpingTask实现定时任务

使用 在 是很简单的,使用 注解即可轻松搞定。

0.启动类,加入 让注解 生效。

1.编写一个调度类,系统启动后自动扫描,自动执行。

2.启动后,控制台可就看见每5秒一次输出了:

使用都是简单的,现在我们来看看注解 的参数意思:

fixedRate:定义一个按一定频率执行的定时任务

fixedDelay:定义一个按一定频率执行的定时任务,与上面不同的是,改属性可以配合 , 定义该任务延迟执行时间。

cron:通过表达式来配置任务执行时间

Cron表达式详解

一个 表达式有至少6个(也可能7个)有空格分隔的时间元素。

依次顺序如下表所示:

简单举例:

0/1 * * * * ?:每秒执行一次

0 0 2 1 * ? : 表示在每月的1日的凌晨2点调整任务

0 0 10,14,16 ? :每天上午10点,下午2点,4点

0 0 12 * * ? : 每天中午12点触发

0 15 10 ? * MON-FRI : 周一至周五的上午10:15触发

自定义线程池

从控制台输出可以看见,多任务使用的是同一个线程。可结合上章节的异步调用来实现不同任务使用不同的线程进行任务执行。

0.编写配置类,同时启用 注解:

1.调度类上加入 。

再次启动程序,可看见控制台输出,任务已经是不同线程下执行了:

动态添加定时任务

使用注解的方式,无法实现动态的修改或者添加新的定时任务的,这个使用就需要使用编程的方式进行任务的更新操作了。可直接使用 或者 接口进行自定义定时任务创建。

ThreadPoolTaskScheduler

是 的核心实现类,该类提供了大量的重载方法进行任务调度。这里简单示例下,具体的大家自行搜索下,用的少不太了解呀。

0.创建一个 类。

1.编写一个控制类,动态设置定时任务:

2.启动后,访问接口,即可看见控制台每3秒输出一次:

SchedulingConfigurer

此类十个接口,直接实现其 方法即可。

0.编写配置类:

1.启动后,控制台也可以看见每3秒输出一次:

基于Quartz实现定时调度

由于本章节是基于 版本的,所以没有基于 的 配置,这里直接引入了 相关依赖包来集成。

题外话:原本使用 时,一般上都是通过 文件,配置其 类进行具体执行任务的配置,指定执行的对象和方法。然后通过设置 或者 设置定时器,最后通过 加入调度的 。所以,我们就使用 方式进行简单集成下。

0.加入pom依赖

1.编写配置类。

2.启动后,可以看见控制台以每3秒执行一次输出:

关于 的详细用法,再次不表了。好久没有使用过了。有机会再来详细阐述吧。

分布式调度服务浅谈

在单机模式下,定时任务是没什么问题的。但当我们部署了多台服务,同时又每台服务又有定时任务时,若不进行合理的控制在同一时间,只有一个定时任务启动执行,这时,定时执行的结果就可能存在混乱和错误了。

这里简单的说说相关的解决方案吧,一家之言,希望大家能提出自己的见解,共同进步!

剥离所有定时任务到一个工程:此方案是最简单的,在定时任务相对较小,并发任务不多时,可以使用此方案。简单也容易维护。当定时任务牵扯的业务越来越多,越来越杂时,维护量就成本增加了,工程会越来越臃肿,此方案就不实用了。

利用 集群方案:本身 是支持通过数据库实现集群的,以下是其集群架构图:

其实现原理也相对简单:通过数据库实现任务的持久化,保存定时任务的相关配置信息,以保证下次系统启动时,定时任务能自动启动。同时,通过数据库 机制,控制一个任务只能被一个实例运行,只有获取锁的实例才能运行任务,其他的只能等待,直到锁被释放。这种方式有些弊端,就是依赖了数据库,同时也需要保证各服务器之间的时间需要同步,不然也是会混乱的。

现在 也有基于 的集群方案,有兴趣的可以搜索下。

分布式锁:可通过使用 或者 实现一个分布式锁的机制,使得只有获取到锁的实例方能运行定时任务,避免任务重复执行。可查看下开源的基于 实现的分布式锁项目: 。github地址:https://github.com/redisson/redisson有兴趣的同学可以了解下。

统一调度中心:

可构建一个 的定时服务,只有 相关配置,比如 , 或者 服务,甚至是统一注册中心下的服务类,如dubbo服务等。而具体的任务执行操作都在各自业务方系统中,调度中心 ,具体实现还是在业务方。这种方案相对来说比较通用,实现起来也简单。就是需要业务方进行约定编程,或者对外提供一个api接口。

当然,为了实现定时任务的自动发现和注册功能,还是需要规范一套规则来实现自动注册功能。简单来说,以 服务为例,可以定义一个 ,调度中心只需要获取所有实现此接口的服务,同时通过服务的相关配置(调度时间、失败策略等)进行相关定时操作。或者编写一个服务注册与发现的客户端,通过 获取到实现此接口的所有实现类,上送到调度中心。

而且,统一调度中心,还可以对所有的定时任务的调度情况进行有效监控,日志记录等,也可以约定接口,让定时任务回传定时结果,做到全局把控的目的。

以上就是对分布式调度的一点理解,有错误的地方还望指正,有更好的方案也希望能分享下。

参考资料

https://www.cnblogs.com/yank/p/3955322.html

https://blog.csdn.net/tsyj810883979/article/details/8481621

https://www.cnblogs.com/javahr/p/8318728.html

http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html

https://spring.io/guides/gs/scheduling-tasks/

总结

本章节主要是讲解了通过不同的方式实现定时任务。对于定时任务而言,本身是门大学问,一俩篇文章是讲不完的。像 和 都是很强大的调度器,两者很相似,像如何实现任务的动态修改调度周期,动态停止相关任务,调度任务的监控,这些本文章都没有涉及。还希望有相关需求的同学自行搜索相关资料了。

最后

目前互联网上很多大佬都有 系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是自己实践的。若文中有所错误之处,还望提出,谢谢。

老生常谈

个人QQ:

微信公众号:

完整示例:chapter-22

系列

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180819G0JDLE00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券