为什么要用分布式定时任务?

01、如何简单实现定时功能?

我是看视频入门Java的,那时候学Java基础API的时候,看的视频也带有讲定时功能(JDK原生就支持),我记得视频讲师写了Timer来讲解定时任务。

当时并不知道定时任务有什么实际作用,所以在初学阶段的我,从来没使用过Timer来实现定时的功能。

再后来,我学到并发了。那时候的讲师提到了ScheduledExecutorService这个接口,它比Timer更加强大,一般我们在JDK里可以用它来实现定时的功能

强就强在于ScheduledExecutorService内部是线程池,Timer是单线程,它能更合理的利用资源。

我学并发的时候,我也并不太关注它(它并不是并发的重点),所以我也没用过ScheduledExecutorService来实现定时的功能。

后来吧,要到学习做项目了,那时候视频有个Quartz课程。我记得理解了很久,最后我才反应过来了,原来写了这么多的代码就是用它来实现定时的功能。

至于比ScheduledExecutorServiceTimer好在哪里呢,最直观的是:它支持cron表达式。

为啥我会理解很久呢,因为Quartzapi太复杂了(它也有着自己的专业术语和概念性的东西)。不过这种跟着做项目的,我是一步一步跟着敲代码的。

Quartz相关的API我是记不住了,但那时候我理解了:原来我们写代码可以靠「组件包」来完成想要的功能,原来这就是cron表达式。

等到我大三的时候,我想用自己学过的知识点来写个小项目,也算是梳理一遍自己到底学了什么东西。于是,我想起了Quartz

那时候我已经学到了Spring/SpringBoot了。所以当我在网上搜SpringQuartz整合的时候,了解到了SpringTask,再后来发现了@Schedule注解。

只需要一个简单的注解,就能实现定时任务的功能,并且支持cron表达式。

那那那那,还要个锤子的Quartz啊!

02、实习&&工作 定时任务

等我工作了之后,我学到了一个新的名词「分布式定时任务框架」。等我踏入职场了以后,我才发现原来定时任务这么好使!

列举下我真实工作时使用定时任务的常见姿势:

1、动态创建定时任务推送运营类的消息(定时推送消息)

2、广告结算定时任务扫表找到对应的可结算记录(定时扫表更新状态)

3、每天定时更新数据记录(定时更新数据)

还很多人问我有没有用过分布式事务,我往往会回答:没有啊,我们都是扫表一把梭保证数据最终一致性的当然了,如果是面试的时候被问到,可以吹吹分布式事务。实际上是怎么扫表的呢?就是定时扫的咯。

另外,我当时简单看了下公司自研的分布式定时任务框架是怎么做的,我记得是基于Quartz进行扩展的,扩展有failover分片等等机制。

一般来说,使用定时任务就是在应用启动或者提前在Web页面配置好定时任务(定时任务框架都是支持cron表达式的,所以是周期或者定时的任务),这种场景是最最最多的。

03、为什么分布式定时任务

在前面提到Timer/ScheduledExecutorService/SpringTask(@Schedule)都是单机的,但我们一旦上了生产环境,应用部署往往都是集群模式的。

在集群下,我们一般是希望某个定时任务只在某台机器上执行,那这时候,单机实现的定时任务就不太好处理了。

Quartz是有集群部署方案的,所以有的人会利用数据库行锁或者使用Redis分布式锁来自己实现定时任务跑在某一台应用机器上;做肯定是能做的,包括有些挺出名的分布式定时任务框架也是这样做的,能解决问题。

但我们遇到的问题不单单只有这些,比如我想要支持容错功能(失败重试)、分片功能、手动触发一次任务、有一个比较好的管理定时任务的后台界面路由负载均衡等等。这些功能,就是作为「分布式定时任务框架」所具备的。

既然现在已经有这么多的轮子了,那我们作为使用方/需求方就没必要自己重新实现一套了,用现有的就好了,我们可以学习现有轮子的实现设计思想。

04、分布式定时任务基础

Quartz是优秀的开源组件,它将定时任务抽象了三个角色:调度器执行器任务,以至于市面上的分布式定时任务框架都有类似角色划分。

对于我们使用方而言,一般是引入一个client包,然后根据它的规则(可能是使用注解标识,又或是实现某个接口),随后自定义我们自己的定时任务逻辑。

看着上面的执行图对应的角色抽象以及一般使用姿势,应该还是比较容易理解这个过程的。我们又可以再稍微思考两个问题:

1、 任务信息以及调度的信息是需要存储的,存储在哪?调度器是需要「通知」执行器去执行的,那「通知」是以什么方式去做?

2、调度器是怎么找到即将需要执行的任务的呢?

针对第一个问题,分布式定时任务框架又可以分成了两个流派:中心化和去中心化

  • 所谓的「中心化」指的是:调度器和执行器分离,调度器统一进行调度,通知执行器去执行定时任务
  • 所谓的「去中心化」指的是:调度器和执行器耦合,自己调度自己执行

对于「中心化」流派来说,存储相关的信息很可能是在数据库(DataBase),而我们引入的client包实际上就是执行器相关的代码。调度器实现了任务调度的逻辑,远程调用执行器触发对应的逻辑。

调度器「通知」执行器去执行任务时,可以是通过「RPC」调用,也可以是把任务信息写入消息队列给执行器消费来达到目的。

对于「去中心化」流派来说存储相关的信息很可能是在注册中心(Zookeeper),而我们引入的client包实际上就是执行器+调度器相关的代码。

依赖注册中心来完成任务的分配,「中心化」流派在调度的时候是需要保证一个任务只被一台机器消费,这就需要在代码里写分布式锁相关逻辑进行保证,而「去中心化」依赖注册中心就免去了这个环节。

针对第二个问题,调度器是怎么找到即将需要执行的任务的呢?现在一般较新的分布式定时任务框架都用了「时间轮」。

1、如果我们日常要找到准备要执行的任务,可能会把这些任务放在一个List里然后进行判断,那此时查询的时间复杂度为O(n)

2、稍微改进下,我们可能把这些任务放在一个最小堆里(对时间进行排序),那此时的增删改时间复杂度为O(logn),而查询是O(1)

3、再改进下,我们把这些任务放在一个环形数组里,那这时候的增删改查时间复杂度都是O(1)。但此时的环形数组大小决定着我们能存放任务的大小,超出环形数组的任务就需要用另外的数组结构存放。

4、最后再改进下,我们可以有多层环形数组,不同层次的环形数组的精度是不一样的,使用多层环形数组能大大提高我们的精度。

05、分布式定时任务框架选型

分布式定时任务框架现在可选择的还是挺多的,比较出名的有:XXL-JOB/Elastic-Job/LTS/SchedulerX/Saturn/PowerJob等等等。有条件的公司可能会基于Quartz进行拓展,自研一套符合自己的公司内的分布式定时任务框架。

我并不是做这块出身的,对于我而言,我的austin项目技术选型主要会关注两块(其实跟选择apollo作为分布式配置中心的理由是一样的):成熟、稳定、社区是否活跃

这一次我选择了xxl-job作为austin的分布式任务调度框架。xxl-job已经有很多公司都已经接入了(说明他的开箱即用还是很到位的)。不过最新的一个版本在2021-02,近一年没有比较大的更新了。

06、为什么AUSTIN需要分布式定时任务框架

回到austin的系统架构上,austin-admin后台管理页面已经被我造出来了,这个后台管理系统会提供「消息模板」的管理功能。

那发送一条消息不单单是「技术侧」调用接口进行发送的,还有很多是「运营侧」通过设置定时进而推送。

而这个功能,就需要用到分布式定时任务框架作为中间件支撑我的业务,并且很重要的一点:分布式定时任务框架需要支持动态创建定时任务的功能。

当在页面点击「启动」的时候,就需要创建一个定时任务,当在页面点击「暂停」的时候,就需要停止定时任务,当在页面点击「删除」模板的时候,如果曾经有过定时任务,就需要把它给一起删掉。当在页面点击「编辑」并保存的时候,也需要把停止定时任务。

嗯,所需要的流程就这些了

07、AUSTIN接入XXL-JOB

接入xxl-job分布式定时任务框架的步骤还是蛮简单的(看下文档基本就会了),我简单说下吧。接入具体的代码大家可以拉ausitn的下来看看,我会重点讲讲我接入时的感受。

官网文档:www.xuxueli.com/xxl-job/#%E…

1、自己项目上引入xxl-job-core的maven依赖

2、在MySQL中执行/xxl-job/doc/db/tables_xxl_job.sql的SQL脚本

3、从GiteeGitHub下载xxl-job的源码,修改xxl-job-admin调度中心的数据库配置,启动xxl-job-admin项目。

4、在自己项目上添加xxl-job相关的配置信息

5、使用@XxlJob注解修饰方法编写定时任务的相关逻辑

从接入或者已经看过文档的小伙伴应该就很容易发现,xxl-job它是属于「中心化」流派的分布式定时任务框架,调度器和执行器是分离的。

在前面我提到了austin需要动态增删改定时任务,而xxl-job是支持的,但我觉得没封装得足够好,只在调度器上给出了http接口。而调用http接口是相对麻烦的,很多相关的JavaBean都没有在core包定义,只能我自己再写一次。

所以,我花了挺长的时间和挺多的代码去完成动态增删改定时任务这个工作。

调度器和执行器是分开部署的,意味着,调度器和执行器的网络是必须可通的:原本我在本地是没有装任何的环境的,包括MySQL我都是连接云服务器的,但是现在我要调试就必须在网络可通的环境内,所以我不得不在本地启动xxl-job-admin调度中心来调试。

在启动执行器的时候,会开一个新的端口给xxl-job-admin调度中心调用而不是复用SpringBoot默认端口也是挺奇怪的?

08、总结

这篇文章主要讲了什么是定时任务、为什么要用定时任务、在Java领域中如果有定时任务相关的需求可以用什么来实现、分布式定时任务的基础知识以及如何接入XXL-JOB

相信大家对分布式定时任务框架有了个基本的了解,如果感兴趣可以挑个开源框架去学学,想了解接入的代码可以把我的austin项目拉下来看看。

主要的代码就在austin-cronxxl包下,而分布式应用的代码主要在austin-webMessageTemplateController跟模板的增删改查耦合在一起了。

作者:Java3y
链接:https://juejin.cn/post/7078829903558213639

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容