java定时器?Java架构师之路-如何去实现一个分布式定时任务
一、app定时软件是怎么运行的
每个操作系统都会有一个服务,就是时钟服务,所有程序开发人员根据服务接口,添加相应服务实现就可以了,时钟服务就会根据你的注册进行时钟调度。简而言之时钟服务就像看门大爷,你的服务就像你要接收的快递,你的注册就是告诉看门大爷我有个紧急快递,到了你给我打个电话。所以app只要注册系统时钟服务就好了
二、Java架构师之路-如何去实现一个分布式定时任务
虽说有现成的框架可以实现,不过还是一步一步地说一下思路。
需求为方便大家的理解,先给大家讲一个真实的需求,这是我在第二家公司的一个项目,定时任务每天凌晨执行,需求很简单:把原始的业务数据,加工处理成待发送的短信。
原始数据:姓名-小明,所在地-北京,电话-13800000000,账单最后还款日期-2018年4月30日。
加工后的数据是:亲爱的小明,您的账单最后还款日期为2018年4月30日,请提前缴费。然后把需要把这条短信发送到13800000000这个手机号上。
定时任务定时任务框架里面,最有名的就是quartz了,相信大部分Java程序员都用过。
我们项目最开始也用的是quartz,只有一个服务器跑定时任务。但是待处理的数据越来越多,定时服务执行的时间也越来越长,终于有一天,定时任务从晚上跑到了第二天白天也没有跑完,耽误了短信的发送。
有人就有疑问了,能不能直接把定时服务部署多套不就行了。但是部署多套quartz的话,就会出现问题:待处理的任务有可能会被重复执行。
应对这种问题,我们当时有两种处理方案:
方案一:定时服务只部署一套,但是定时任务的工作只是提取待处理的任务。
实际的业务处理服务集群化部署,然后由定式服务提取数据后,发送给业务处理服务器进行实际的处理。
方案二:这个是我当时自己想出的一个奇葩的方法,不过这个方案想明白了,对分布式定式服务的理解很有帮助!
定时任务程序部署多套,并且多套环境都是独立的IP。每套程序定时将IP写入到数据中(一分钟对表update一次,并更新时间戳)。多套服务选举出一台主服务器。主服务器把所有的待处理任务,尽可能平均分配给每一台服务器。(IP和待处理任务对应上,也就是每一条待处理任务只能让分配的IP处理)处理任务的时候,只处理自己IP对应的任务。一台服务器挂了,主服务器负责把它的IP从数据库中抹掉(三分钟没有对表进行更新的IP,删除掉),并重新分配这个IP对应的待处理任务。主服务器挂了,重新选举出主服务器。分布式定时任务框架我只用过Elastic-job,所以只给大家介绍一下这个框架。
任务分片:把一个任务拆分成几个独立的任务,然后由分布式服务器分别执行一个或者多个子任务。比如还是上面那个需求,那么可以按照【所在地】拆分任务,北京的待处理数据是一个子任务,天津的待处理数据是第二个子任务。
Elastic-Job并不直接提供数据处理的功能,实际的数据处理还是需要自己写,Elastic-Job会将分片任务分配到各个运行中的作业服务器。
其实发现了没有,Elastic-Job做的工作,就是我那个主服务器做的任务分配的工作,把所在地=北京的,分配给服务器1处理,把所在地=天津的,分配给服务器2处理;甚至包括监控每台作业服务器是否存活,挂掉一台重新分配待处理任务,也都是Elastic-Job来做的。
我将持续分享Java开发、架构设计、程序员职业发展等方面的见解,希望能得到你的关注。三、如何使用Java实现程序执行的超时判断
参照如下的:
Timer类是用来执行任务的类,它接受一个TimerTask做参数TimerTask是个抽象类,他扩展了Object并实现了Runnable接口,因此你必须在自己的Task中实现publicvoidrun()方法。这也就是我们需要执行的具体任务。
Timer有两种执行任务的模式,最常用的是schedule,它可以以两种方式执行任务:
1:在某个时间(Data),
2:在某个固定的时间之后(intdelay).这两种方式都可以指定任务执行的频率
我们指定一个线程A,调用对象B.wait(timeout),线程A就会阻塞,直到timeout到了,B醒来会使A继续执行。
其实Timer类是为多任务定时设计的,在实现里面,B是一个任务队列(实现上就是一个array),维护着所有使用当前Timer定时的任务,它们可是一堆货真价实的线程实例。每次线程A都取队列中距离当前时间最近的的定时任务,跟当前时间比较,然后wait(timeout)这段时间。线程唤醒的时刻也是队列中这个定时任务运行的时刻。然后线程继续取下一个定时任务,继续wait(timeout)。从这里我们能看出来,每次定时都有额外的时间开销,比如要维护队列等,所以Java的Timer类不保证实时。
Timer中最主要由三个部分组成:
任务TimerTask、任务队列:TaskQueuequeue和任务调试者:
TimerThreadthreadTimer对任务的调度是基于绝对时间的。
所有的TimerTask只有一个线程TimerThread来执行,因此同一时刻只有一个TimerTask在执行。
任何一个TimerTask的执行异常都会导致Timer终止所有任务。
由于基于绝对时间并且是单线程执行,因此在多个任务调度时,长时间执行的任务被执行后有可能导致短时间任务快速在短时间内被执行多次或者干脆丢弃多个任务。
由于Timer/TimerTask有这些特点(缺陷),因此这就导致了需要一个更加完善的任务调度框架来解决这些问题。
默认情况下,只要一个程序的timer线程在运行,那么这个程序就会保持运行。当然,你可以通过以下四种方法终止一个timer线程:调用timer的cancle方法。
你可以从程序的任何地方调用此方法,甚至在一个timertask的run方法里。
让timer线程成为一个daemon线程(可以在创建timer时使用newTimer(true)达到这个目地),这样当程序只有daemon线程的时候,它就会自动终止运行。
当timer相关的所有task执行完毕以后,删除所有此timer对象的引用(置成null),这样timer线程也会终止。调用System.exit方法,使整个程序(所有线程)终止。