zoukankan      html  css  js  c++  java
  • 一个著名的任务调度系统是怎么设计的?

    实习生张大胖

    这是个代码写得很烂的电商系统,只要运行一段时间,服务器就会出现Out Of Memory。

    别人都忙得四脚朝天,于是实习生张大胖被抓了壮丁去研究为什么会出现OOM。

    刚入行的张大胖技术水平一般,“装模作样”地看代码,研究日志,请教老员工,一个星期过去了,还是一无所获。

    周一例行的项目会议上, 大家似乎要看张大胖的笑话了,没想到他却提了一个歪招:“这个OOM问题非常复杂,一时半会儿也解决不了,要不我们定时重启服务器怎么样?”

    一脸严肃的项目经理老梁点点头:“以目前的情况看,也只能如此了。但是不能让服务中断,这样吧,公司有两台服务器,一台在凌晨1点重启, 另外一台在凌晨2点重启。”

    得到了领导的首肯,张大胖赶紧行动,周末他其实已经做了准备,研究了Linux上的crontab,它的格式是这样样子:

    每天凌晨一点重启系统,可以这么写:

    0 1 * * *  restart.sh

    (注:这里只是个简单的例子, 实际上crontab及其灵活)

    这个OOM的问题被张大胖灵机一动给解决了,或者说,被临时隐藏了。

    crontab达人的烦恼

    大家知道张大胖擅长crontab, 都把一些定时的任务扔给他去做: 什么定时统计报表,定时同步数据,定时删除表中的无效订单...... 等等。

    张大胖整天面对的就是crontab和脚本,都快要吐了。

    不仅如此,同事们还经常提出一些“变态”的需求:

    “大胖,那个定时任务运行得怎么样了?”

    “大胖,我想把那个定时任务给停掉。”

    “大胖,那个定时任务今晚别运行啊!”

    “......”

    张大胖真是烦死了,他心想,要是提供个界面让大家使用就好了, 可是crontab似乎并不支持。

    要不自己开发一个?

    有一次张大胖偶然发现了JDK中的Timer类,似乎也是做这些定时任务的, 不由地眼前一亮,但是仔细研究以后就发现,JDK的Timer还是太简单了,做点简单的定时任务还行, 对于复杂的情况,尤其是复杂的时间策略,还是力不从心。

    另起炉灶

    看来自己需要从头设计了, 小张用“正交”的原则设计出了Logger, Appender, Formatter这些类。

    我也可以使用同样的原则啊,小张能行,我凭什么不行?

    说干就干,先想想需求,非常简单,不就是定时地执行任务嘛!

    “任务”应该是正交中的一个“维度”,我可以抽象出一个接口叫做Task , 嗯,还是叫做Job吧。

    对使用者来说,他需要提供一个实现类出来,在实现类中描述要做什么事情,比如:生成报表,复制数据......

    “定时”该怎么处理? 定时,定时触发,干脆叫做Trigger吧。

    这个Trigger 可以指定什么时间开始,时间间隔,运行多少次, 能覆盖大部分需求了。

    可是张大胖转念一想,如果有人要求类似日历的重复间隔该怎么处理? 比如每月的第一天运行,或者每周的最后一天运行,该怎么办?  crontab特别适合描述这种情况,对,可以搞一个类似于crontab的Trigger。

    看来Trigger最好也是个接口,我来提供几个默认的实现,比如SimpleTrigger,CronTrigger,用户还可以扩展,这样就灵活了。

    Job和Trigger也是正交的关系, 两者可以互不影响,可以独立扩展,真是不错, 张大胖不仅得意起来,这设计也很简单嘛!

    但是怎么把这两个家伙结合起来?

    必须得有个“大管家”才行,这个大管家应该可以接受Job, 然后按照各种Trigger去运行,嗯,叫做调度器Scheduler应该不错。

    张大胖画了个草图,来展示三者之间的关系:

    设计得差不多了,可以进入开发阶段了, 因为是自己要写一个类似于框架的东西,让别人去使用,张大胖开发起来非常有激情,即使是利用晚上和周末的时间来写代码,也是像打了鸡血一样,根本不觉得累。

    一个月过去了,第一版新鲜出炉。

    这个版本不仅有核心的API像Job, Trigger, Scheduler ,张大胖还专门开发了一个界面,用来展示定时任务的进展,例如什么时间运行,运行了几次,失败了几次......等等。

    张大胖把它叫做“大胖定时任务调度系统”。

    持久化

    他兴奋地拿去让项目经理老梁看, 可是老梁并不感冒,面无表情地说:“你这个小软件有啥用啊。”

    张大胖被泼了一盆冷水,依然热情满满地推销:“用了我的这个定时调度系统,任何人都可以轻松地启动,停止任务, 咱们项目中所有的定时任务一目了然。 大家就不用找我来手工调整了。”

    老梁开玩笑地说:“奥,那你的实习工作就可以结束了,哈哈。”

    正巧CTO Bill经过,他饶有兴趣地看了一会,提了一个问题:“假设你这个大胖调度系统在运行的时候,机器突然间Down掉了,怎么处理?”

    张大胖一脸懵逼:“什么怎么处理,重启机器呗。”

    Bill 说: “之前的任务还能接着运行吗,比如说一个任务需要运行100次,在机器down掉之前运行了90次,重启后能不能从第91次运行?”

    张大胖有点发窘,不好意思地挠挠头:“这一点我还真没考虑到,我现在都是在内存中记录运行的情况,看来得做持久化了。”

    Bill 听到持久化这个词,知道张大胖已经Get到了,他说,你把这个持久化实现了,到时候直接向我汇报。

    得到了CTO的赏识,张大胖不敢怠慢,赶紧进行新的设计, 他抽象了一个叫做JobStore的接口,表示Job的存储,像什么Job,Trigger, Job运行情况都存储在其中。 

    下面有两个实现,分别对应内存存储和数据库存储。

     

    虽然SQL是标准的,但是不同的数据库还是有细微的差异, 张大胖觉得得把这些差异给封装起来, 他又提取了一个接口叫做DriverDelegate, 屏蔽了数据库细节,让DbJobStore使用。

    他还提供了一个缺省的实现StdJDBCDelegate,如果那些数据库还有独特的实现,那就写个子类就行了。

     

    高可用

    “大胖定时任务调度系统 2.0” 开发完成以后,张大胖仔细地想了一遍,似乎没有什么漏洞了,决定正式向CTO Bill去汇报。

    Bill 亲切地询问了张大胖加班加点设计和开发的情况,对他这种不计较个人得失,一心一意为公司谋福利的精神表示了高度的赞赏。

    张大胖受宠若惊。

    Bill话锋一转:“我们的系统最近用户越来越多,老板特别提出了高可用的需求,系统的各个组件也得达到高可用!”

    “高可用? 拿我的定时调度系统来说,就是说可以部署在多个机器上,一个down掉了,其他的还可以运行,对吧?” 张大胖一点就透。

    Bill 赞许地点点头:“你想好怎么去实现了吗?”

    “很简单啊,把定时调度系统部署到多个机器上,形成几个备份就行了!”

    张大胖还在白板上画了这么一个图:

    “那同一个时刻,有多少个Scheduler 在运行?”  Bill 终于抛出了重磅炸弹。

    张大胖现在明白Bill的疑问了了,三个实例都在运行,那一个Job就有可能运行多次,这肯定是不行的!

    他说道:“要不让三个实例A,B,C都去访问同一个数据库吧!”

    Bill说:“那三个实例访问同一份数据,肯定会出现冲突,互相覆盖,那就乱套了!”

    其实,实例A,实例B,实例C组成一个类似集群的东西,但是同一时刻,一个Job只能在一个实例上运行。

    比如Job X 从凌晨1点开始,每隔1小时运行一次,那1:00 的时候Job X可能在实例A上运行, 2:00的时候可能在实例B上运行, 3:00的时候可能在实例C上运行。

    也就是说,这三个实例部分地实现了负载均衡。

    张大胖说:“这可就难办了。难道让这三个实例A,B,C之间互相通信?”

    Bill说道:“那样有点麻烦,就变成一个分布式系统下的通信问题了,我们要不用这个数据库做点文章? 反正这个数据库已经存了Job的信息,Trigger的信息,我们就多加一个表吧,就叫LOCKS,这个表里边每一行记录都可以当做一个‘锁’来用。”

    张大胖表示不太明白。

    “很简单,就是数据库的‘行’锁嘛, 比如SELECT * FROM LOCKS where LOCK_NAME='TRIGGER' FOR UPDATE ,这就把那一行记录给锁住了, 别的事务只能等待当前事务commit以后才能访问。”

    张大胖还是不太明白。

    “比如,服务器A的实例A在一个事务中先执行了上面SQL, 就把那一行给锁住了,当服务器B的实例B也去执行同样的SQL的时候, 只能等待,对吧? 这不就相当于实例A获得了锁吗?”

    “原来如此,以后任何一个调度器实例想要获取Job的运行时间,设置Job的下一次运行时间的时候,都必须先获得这个锁。这样这些分布式的调度器就不会冲突了,只会运行一个特定时间的Job。 我这就去做个详细设计,再来汇报。”

    开源

    两个月后,“大胖定时任务调度系统 3.0” 开发完毕,在Bill的大力支持和推动下,成功地应用在了公司的项目中。

    灵活的设计和扩展性,加上持久化,集群等强大的功能, 系统受到了大家的欢迎。

    考虑到很多公司都会有类似的需求,Bill决定把系统开源, 只是“大胖定时任务调度系统”这个名字有点俗,还有点长,Bill把它改名为“Quartz”。

    Quartz从此流行开来。

  • 相关阅读:
    Selenium简单测试页面加载速度的性能(Page loading performance)
    Selenium Page object Pattern usage
    Selenium如何支持测试Windows application
    UI Automation的两个成熟的框架(QTP 和Selenium)
    分享自己针对Automation做的两个成熟的框架(QTP 和Selenium)
    敏捷开发中的测试金字塔(转)
    Selenium 的基础框架类
    selenium2 run in Jenkins GUI testing not visible or browser not open but run in background浏览器后台运行不可见
    eclipse与SVN 结合(删除SVN中已经上传的问题)
    配置Jenkins的slave节点的详细步骤适合windows等其他平台
  • 原文地址:https://www.cnblogs.com/peke/p/9212351.html
Copyright © 2011-2022 走看看