我所理解的分布式调度

对于规模以上的应用来说,调度系统已经是必不可少的组成部分,尤其在基于数据分析的后台应用大量增长的今天,健壮的调度任务管理已经是非常重要的一环,因此多花些时间来分析研究调度系统的设计对于日常的开发与运维具有比较重要的意义。

调度问题是怎么来的

当你的网站是个简单的blog,而且并不需要跟外部交互的时候,你大概不需要调度任务,因为此时网站需要处理的任务仅限于 即时交互 , 即用户想使用一个功能,你就立即给他就是了,如同你在简书上写一篇文章,一点保存,这篇文章立即就保存到网站的后台服务器中去了,这也是互联网刚出现时候的最早的应用模式。

之后因为网站发展的不错,用户多了起来,就发现需要大量处理一些非即时的任务,比如要定时将用户访问日志归档清理,这个时候一般情况下会在服务器启动一个定时任务,在每天固定时间发起清理归档,又如你想显示一下网站的访客流量、发表文章数、评论统计,由于并非每次用户或者后台管理员每次需要看的时候都去计算一遍,所以可能又需要启动另一个任务来去处理这些数据,这样的任务多了,就需要思考一个问题,哪些任务要先处理,哪些任务要后处理,每个任务要占用多少资源,从而任务调度问题开始出现。

调度什么时候变得复杂

在一个单机的系统,任务并不多的情况下,生活还依然是美好的,利用Linux自带的定时器或者系统框架提供的定时任务,实现好任务执行器,配置好任务触发的时间和频率,然后每天等待它自动触发,数据生成,任务搞定,似乎调度也没那么困难。

然而好景不长,伴随着网站的发展,你发现任务处理的越来越慢,甚至偶尔会有任务超时的情况,原因是每天用户产生的数据量越来越大,每次任务捞起的数量已经超载,依次执行完每个任务,可能已是最初执行时间的几倍。

这时稍有点经验你便会想到,任务没必要顺序执行啊,所以弄个线程池,起多个线程同时来捞数据然后执行,同时配置动态调整每次数据捞取的数量,增大执行频率,一系列组合拳打出去之后,调度任务又恢复正常,由于增加了并发数,甚至执行的比开始还更快了,就这样业务高峰便又顺利度过了。

调度什么时候变得更加复杂

之前所描述的情况基本上都在单机的情况,网站的QPS(每秒处理的任务数量)基本在500以下,通过一系列的参数调优,串行变并行,任务运行的很平稳。然而当任务变的规模更大,比如十倍于当前,一台机器已经不能处理所有的任务,这时候需要增加更多的机器,将任务分配到不同的机器上,于是便有了所谓的分布式调度的问题。

分布式是目前稍大型的网站不可避免的问题,这种处理方案有很多好处,比如可以利用廉价的机器,可以(理论上)无限水平拓展,同时也带来了一系列棘手的问题,机器之间的网络通信,如何把流量均匀的分布于不同流量,如果有机器宕机了如何处理,每个问题都已经是一个庞大的技术领域。

对于调度的分布式问题,首先要解决的便是如何把任务分发到不同的机器上,这要求调度系统通常要至少分为两层,第一层决定一共要处理哪些任务并把任务分发到哪些机器上处理,第二层接到任务后具体执行。虽然描述起来很简单,但是这个处理过程实际上需要大量支撑系统的支持,比如在任务分发时如何判断哪些机器还活着可以处理任务,这可能需要一个可以感知整个集群运行状态的配置中心,又比如任务如何分发,是采用消息还是实时服务接口,如果是用消息派发则需要消息系统,如果是实时服务,又需要类似dubbo这样的分布式服务框架。当系统达到这个复杂度,已经不是将任务捞起并处理这么单纯,而是多个关联系统复杂性的叠加。

分布式调度的难题

虽然分布式调度带来了复杂度的上升,但是它的水平拓展能力完美的适配了网站的规模发展,直到有一天你看到了类似这个错误:

org.springframework.transaction.CannotCreateTransactionException

数据库连接池已经打满,由于单个数据库的连接数是一定的,这意味着数据库变成了资源瓶颈,这时候给任务处理happy的加机器已经不能提高系统的整体处理能力,这就如同你要运走一群人,给你提供的车辆是无限的,但是汽油确是一定的。当然数据库也是可以拓展的,但是考虑到数据迁移的复杂性,这几乎将问题的复杂度提高了一个等级。因此,一个分布式系统的吞吐量,在很大程度上是与数据库处理能力做权衡的结果。

写在最后

分布式调度是一个巨大的话题,并且还在随着实际任务复杂度的提高而快速的更新,上面这些思考与总结也只是浮光掠影,还需要在实际工作中再深入体会与仔细研究。