背景和目的
在日常的开发中,我们经常会遇到各种异步的处理,业务的补偿和定时的业务处理。这个时候,我们可能第一反应就是,起一个定时任务。没错,就是定时任务。
目前所在的这家公司,由于交易系统是在我职责范围之内,而交易系统中很多业务都是通过异步定时来处理的。之前发生了好几次由于定时任务触发异常导致的种种生产问题,不得不使我停下忙碌的脚步,静下来思索并总结。此文属个人经验总结,如有雷同,纯属巧合。好,那我们开始入正题。
定时任务的演变
“演变”可能说的有点严重。还记得我07年刚毕业的时候,在第一公司,那个时候印象还没接触定时任务,隐约在自己买的一本spring的书中,了解到spring-quartz。后来到了第二家公司,当时还是平安集团信息管理中心(后独立成平安科技),接触到了spring-quartz的应用场景。到现在,我们互联网技术不断发展,分布式任务调度层出不穷。基本可以概括如下:
spring-quartz阶段:
早期的方式一般采用quartz结合spring来实现集群中的定时任务的启动。有兴趣的朋友可以看看quartz的12张表的作用。大家一直认为quartz就是属于spring,其实是一个误解。只是我们当时的框架是ssh而已。
这个阶段,更多的是保险、金融、银行会用的比较多,做一些企业级应用。而企业级应用一般是集群部署。集群部署需要考虑的是定时任务的重复跑问题。quartz在这一点做的还是挺到位的。只是配置比较复杂。我还清晰的记得,当时在熟悉quartz的时候,那些oracle数据库的表,让我很头疼。看了很久才明白。其实他的思想还是挺不错的。基本是以节点任务抢占的方式来实现。用户数据库悲观锁的方式来实现共享资源的抢占。
山寨版实现:Thread.sleep
这个相对来说就比较简单了。用while循环,循环中用sleep来处理,当然,这种实现,最好用线程来处理,异步的启动。否则会阻塞应用。
spring注解Scheduler
spring的scheduler,其实是一种集成框架,支持JDK Timer、concurrent、quartz三种,这三种任务调度方案在实现机制和调用方法上都不同,但spring通过对其包装,使得基于spring能用统一的配置和编码风格来使用这三种schedule方案。
jdk的timer实现
jdk推陈出新,在1.3的时候推出了timer。主要包括任务 TimerTask 、任务队列:TaskQueue queue和任务调试。具体大家自行研究下。一段时间风靡。
但是官网上有这样子的一段话:
Timer类会自动启动一个新线程,而多个Timer类则会有开辟多个线程,同时Timer类的线程是非daemon(守护)线程,所以一旦启动除非明确cancel掉,是一直存在的。
因此,很容易出现oom。
分布式任务调度框架
随着电商互联网时代的到来,追求效率的攻城狮们,需要做分布式任务处理。不再满足于一台机器处理一个任务,而需要多台机器处理一个任务。用空间来换时间。
分布式任务调度框架(轮子)产生了。我清晰的记得,当时在一号店,研究了下淘宝的tbscheduler,而且还改写了部分源码的实现。现在想想,当时还做了部分的无用功,但是一定程度上加深了我对整体框架的认识和原理的熟悉。从此一发不可收拾的走上了架构这条路。
基本当前阶段主流的任务调度框架(我所说的可能是基于我的认知)如tbscheduler、es-job。原来还是基于spring进行实现。当然,不排除现在各家公司自己造轮子。开源出来的比较有名的就是这些了。es-job其实和tbscheduler原理一样。当初看到这个开源框架的时候,甚至怀疑是抄袭tbscheduler的。毕竟在11年的时候,就开始读tbscheduler的源码了。
以上,基本能概括目前的主流定时任务实现。
任务的部署方式
看了以上的演变过程,我想应该已经唤醒大家沉睡多年的记忆了^_^。
集群时代
时间回到07年左右,那个时候,保险金融业大多采用了小型机+pc server+oracle+ssh的架构,混搭。满天飞的存储过程,数据同步是etl和golden-gate。土豪的时代。
言归正传,weblogic的时代,采用集群方式部署。必然会考虑定时任务的启动问题,数据的重复跑问题,而quartz正好能解决规律。所以,那个时候,定时任务随着应用一起部署。
通过数据库的锁方式,规避了任务的重复自行,这个时候,任务是随机抢占,无状态。记录日志,和任务的批次执行结果,配合监控报警。基本完美解决了问题。
缺点很明显:实现复杂。我想,那12张表,就已经很折腾人了。
单机时代
随着电商、互联网的兴起,效率和速度,成了大家首要追求的目标。“天下武功,唯快不破”。这个时代,有个口号:去IOE。
去IOE,有几个原因:
1、成本。之所以放首要,是因为电商很多烧钱,成本放在第一位。印象中oracle的licence一个每年都是十几万,golden-gate跟着licence也是靠近10w。一般数据库都是集群部署,那么单db这块,每年就很多钱。更不用说小型机和EMC的存储设备了。
2、效率和性能。电商的场景和传统的场景有很大的差别,对于并发的要求、对于中间件的性能有很多的考量。而付费产品,很多不开源,且有使用限制,制约了平台的扩展性。因此,很多公司喜欢自己造轮子。这里最典型的就是bat中的A。
以上,是我理解的两个主要原因。可能还有其他的,我也不过多的去赘述了。
另外,除了以上,还有一些点很重要,我觉得有必要提下。那就是电商和互联网是不建议存储过程以及触发器和函数等操作的。也是基于资源和性能的考虑。
还记得一号店曾经进行过去存储过程、trigger和函数的行动。原因就是数据库资源太宝贵了,相比应用的优化空间,真的有限。而应用层面,有很多优化的措施,如缓存,水平扩容等等。且db在一定程度上是共享资源,小范围说,会影响某个业务域的集群,大了说,会影响整个公司的业务开展。
回到正题,这个时候,我们推崇的是简便,快捷,性能。所以定时任务单独部署,和应用解耦。
优点:
显而易见,和应用分离,任务挂或者发生死锁,不影响业务。
缺点:
单独部署,没有实现集群方式。任务一般作为补偿,非主链路手段。且有消息保证。(当然,也可以剥离开来进行集群,但是那个时候没想这么复杂),更多的是分离。
分布式时代
先上个图:
屌丝的福音。
因为再也不用去考虑任务的抢占,任务的效率问题了。分布式任务框架能很好的解决这些问题。
我这里总结下框架出现的原因:
1、效率:原来是并发执行,也就是一个cpu处理多个事件。这个时候,我们发现,其实资源没有被充分利用起来。可以进行并行执行。多核并行。我们想充分利用资源,提升执行效率。用空间来换事件。
2、规范:这里的规范是任务的实现规范。我想大家更多的是考虑的第一点。但是我当时在一号店去引入tbscheduler的动因,还有一个,就是书写的规范。但是各种任务书写形式,层出不穷,以上列举的可能都有出现。拍拍贷习惯叫收口,那么我姑且借用下,叫“收口”任务的实现方式。
分布式任务框架的出现,在一定程度上解决了如下问题:
1、部署的问题。
2、资源使用的问题。
3、任务分片的问题。
如下一一解释:
1、部署问题:原先的部署,涉及到数据库,应用。且要考虑任务的抢占。low之余多了一点复杂。现在框架集群部署,甚至支持docker。任务的注册采用zookeeper或者consul等开源框架,任务的调度由sdk植入应用。简单明了。
2、资源使用问题:
早期的定时任务,要么就一台机器使用,要么就轮着来。简单粗暴。但是资源使用率不高。
如今,手枪换大炮。可以给机器设置权重,资源多的机器,多执行任务。资源少的,执行一个或者跳过。
3、任务分片问题:
这个是分布式的理念,用空间换时间。我不做过多的赘述。
结合业务
相信以上说了这么多,大家尘封已久的记忆已经被彻底唤醒。那么,接下来,我将要结合具体的业务场景,来说说,我们在使用过程中需要注意的点。我将分几块进行说明:
一、任务和场景
二、任务和锁
以上,结合自己的一些经验和总结,希望对大家有帮助。