定时任务使用总结
相信定时任务大家都不陌生,可能在个人项目中使用定时任务会比较少,但是在业务复杂的公司中,定时任务是不可或缺的一部分,有时候也可以贯穿整个项目的流程运转。
什么是定时任务
crond类似于我们平时生活中的闹钟,可以定时叫你起床,而在项目中就是定时执行一段指定的代码
为什么要使用crond呢
1.如果对于一些数据实时性要求没那么高,我们可以把数据提前丢到缓存中,这个时候就需要使用定时任务去跑了,比如每天凌晨3点定时把数据同步到缓存,错峰同步避开白天人流量大的时候消耗资源
2.比如凌晨2点有抢购接口/或者业务开关需要进行变更开启,我们可以使用定时任务去进行变更,不用人为去守着变更,而且执行时间更准确(可以滚去睡大觉.jpg)
3.还可以进行数据的定时备份,比如备份配置文件,防止宕机的时候配置文件的恢复等等
定时任务实现方式
1.Thread
各位亲爱的朋友,没错,Thread真的可以做定时任务.
如果你去看过一些定时任务框架的源码,它们的底层也会使用Thread类(需要注意用try……catch捕获异常,否则出现异常,就直接退出循环)
1 | public static void init() { |
使用场景:比如项目中有时需要每隔10分钟去下载某个文件,或者每隔5分钟去读取模板文件生成静态html页面等等,一些简单的周期性任务场景。
优缺点:
- 优点:这种定时任务非常简单,学习成本低,容易入手,对于那些简单的周期性任务,是个不错的选择。
- 缺点:不支持指定某个时间点执行任务,不支持延迟执行等操作,功能过于单一,无法应对一些较为复杂的场景。
2.Timer
Timer 类是jdk专门提供的定时器工具,用来在后台线程计划执行指定任务,在 java.util 包下,要跟 TimerTask 一起配合使用。
1 |
|
优缺点:
- 优点:非常方便实现多个周期性的定时任务,并且支持延迟执行,还支持在指定时间之后支持,功能还算强大。
- 缺点:如果其中一个任务耗时非常长,会影响其他任务的执行。并且如果 TimerTask 抛出 RuntimeException , Timer 会停止所有任务的运行,所以
阿里巴巴开发者规范中不建议使用它
3.Scheduled 注解(常用)
由于xml方式太古老了,我们以springboot项目中注解方式为例
1.引入依赖
1 | <dependency> |
2.在springboot启动类上加上 @EnableScheduling 注解
1 |
|
3.使用 @Scheduled 注解定义定时规则
1 | import org.springframework.stereotype.Component; |
优缺点:
- 优点:
采用cron表达式,比较方便,spring框架自带的定时功能,springboot做了非常好的封装,开启和定义定时任务非常容易,可以满足绝大多数单机版的业务场景。单个任务时,当前次的调度完成后,再执行下一次任务调度。 - 默认单线程,如果前面的任务执行时间太长,对后面任务的执行有影响。不支持集群方式部署,不能做数据存储型定时任务。
5.spring quartz(常用)
quartz 是 OpenSymphony 开源组织在 Job scheduling 领域的开源项目,是由java开发的一个开源的任务日程管理系统。
quartz能做什么?
- 作业调度:调用各种框架的作业脚本,例如shell,hive等。
- 定时任务:在某一预定的时刻,执行你想要执行的任务
1.引入相关依赖
1 | <dependency> |
2.定时任务执行类继承QuartzJobBean
1 | public class DateTimeJob extends QuartzJobBean { |
3.调度配置
1 |
|
优缺点:
- 优点:默认是多线程异步执行,单个任务时,在上一个调度未完成时,下一个调度时间到时,会另起一个线程开始新的调度,多个任务之间互不影响。支持复杂的 cron 表达式,它能被集群实例化,支持分布式部署
- 缺点:相对于spring task实现定时任务成本更高,需要手动配置 QuartzJobBean 、 JobDetail和 Trigger 等。需要引入了第三方的 quartz 包,有一定的学习成本。不支持并行调度,不支持失败处理策略和动态分片的策略等。
以下两种配置参考方式
6.xxl-job(常用:分布式定时任务主流)
xxl-job 是大众点评(许雪里)开发的一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
xxl-job 框架对 quartz 进行了扩展,使用 mysql 数据库存储数据,并且内置jetty作为 RPC服务调用。
具体配置可以参考之前本人写的【初探xxljob】
优缺点:
- 优点:有界面管理定时任务,支持弹性扩容缩容、动态分片、故障转移、失败报警等功能。它的功能非常强大,很多大厂在用,可以满足绝大多数业务场景。
- 缺点:和 quartz 一样,通过数据库分布式锁,来控制任务不能重复执行。在任务非常多的情况下,有一些性能问题。
分布式定时任务
在前面提到Timer/ScheduledExecutorService/SpringTask(@Schedule)都是单机的,但我们一旦上了生产环境,应用部署往往都是集群模式的。
在集群下,我们一般是希望某个定时任务只在某台机器上执行,那这时候,单机实现的定时任务就不太好处理了。
Quartz是有集群部署方案的,所以有的人会利用数据库行锁或者使用Redis分布式锁来自己实现定时任务跑在某一台应用机器上;做肯定是能做的,包括有些挺出名的分布式定时任务框架也是这样做的,能解决问题。
但我们遇到的问题不单单只有这些,比如我想要支持容错功能(失败重试)、分片功能、手动触发一次任务、有一个比较好的管理定时任务的后台界面、路由负载均衡等等。这些功能,就是作为「分布式定时任务框架」所具备的。
分布式定时任务框架又可以分成了两个流派:中心化和去中心化
- 所谓的「中心化」指的是:调度器和执行器分离,调度器统一进行调度,通知执行器去执行定时任务
- 所谓的「去中心化」指的是:调度器和执行器耦合,自己调度自己执行
对于「中心化」流派来说,存储相关的信息很可能是在数据库(DataBase),而我们引入的client包实际上就是执行器相关的代码。调度器实现了任务调度的逻辑,远程调用执行器触发对应的逻辑。
谈谈定时任务使用场景
我现在公司对于定时任务的主要使用场景是更新缓存,清洗数据,定时推送,定时拉单等等
先说说更新缓存:就是每隔一段时间去执行操作,可能一天只需要更新一次当天的缓存数据,一般会选择放在凌晨人流量少的时候执行。
清洗数据和定时拉单就比较像,会比如每间隔15分钟去进行一次扫表,看看有没有需要进行执行的流程
定时推送就是你想的那样,定时推送消息或者定时执行代码的开关
对于以上的操作,我们可以分为两种,全量更新和增量更新
全量更新:这个比较常用,就是一次性全部查询出来,然后一次性更新到缓存里面去
1 | public void zxMachineBuild(){ |
注意:因为以上使用的是有过期时间的,如果正常跑的话一般会先删后增,如果是redis的话可以直接全部覆盖(使用场景一般是初始化的时候或者数据重跑)
增量更新:增量的基础就是全量,先全量更新后,再用增量方式同步更新,一般利用节点或者状态去进行更新
我们常用的更新方式
根据状态更新
1 | public void zxMachineBuild(String type){ |
根据节点更新:假设我使用redis来存储我的节点(一般会使用id作为节点,以下例子使用时间节点)
1 | public void zxMachineBuild(){ |
进阶(推荐)
1 | public void zxMachineBuild(){ |
使用以上方式的好处就是可以直接先锁定自己需要跑的部分,防止其他的定时任务抢占
1 | ## 取t_pao_lowprice_taobao数据出来,设置state=‘L’ 锁住 |
1 | ## lockname【对应代码的uuid】 状态为state=‘L’ 获取需要跑的数据,开线程去跑 |
总结
可能在个人项目中,对于定时任务的需求没有那么多,不会去重视这一块,但是在实际工作中,定时任务往往扮演着重要的角色
不同的定时任务有不同的优缺点,往往我们去选择适合自己的那种方式,就需要对于其种类有一定的了解,怎样才能更高效的去进行开发,尽量避免使用到一半因为其底层设计的原因导致BUG。
现在基本也使用E-Job(ElasticJob)或者X-Job(XXLJob)这种分布式定时任务,它们都有广泛的用户基础和完整的技术文档
- X-Job 侧重的业务实现的简单和管理的方便,学习成本简单,失败策略和路由策略丰富。推荐使用在“用户基数相对少,服务器数量在一定范围内”的情景下使用
- E-Job 关注的是数据,增加了弹性扩容和数据分片的思路,以便于更大限度的利用分布式服务器的资源。但是学习成本相对高些,推荐在“数据量庞大,且部署服务器数量较多”时使用
单机的定时任务现在基本不推荐使用了,维护起来比较麻烦
对于定时任务,个人理解程度有限,使用的场景往往更加复杂不能每点都考虑到,欢迎大家能提出自己的想法
比如分布式定时任务下如何去保证事务的一致性,单机定时任务又如何去保证数据不重复等等场景都需要去挖掘……
参考指南:

