如何设计稳定性横跨全球的Cron服务

这篇文章主要来描述下Google是如何实现一套可靠的 分布式Cron服务,服务于内部那些须要绝大多数计算做业定时调度的团队。 在这个系统的实践过程当中,咱们收获了不少,包括如何设计、如何实现 使得他看上去像一个靠谱的基础服务。 在这里,咱们来讨论下分布式Cron可能会遇到哪些问题,以及 如何解决他。算法

Cron是UNIX中一个常见的工具,用来按期执行一些用户指定的随意Jobs。咱们先来分析下Cron的基本原则和他最多见的实现,而后咱们来回顾下像Cron这样的服务应该如何运行在一个大型的、分布式的环境中,这样即便单机故障也不会对系统可用性形成影响。 咱们将会介绍了一个创建在少许机器上的Cron系统,而后结合数据中心的调度服务,从而能够在整个数据中心中运行Cron任务做业。网页爬虫

在咱们在描述 如何运行一个靠谱的分布式Cron服务以前,让咱们先来从一个SRE的角度来回顾下Cron。安全

Cron是一个通用的工具,不管是admin仍是普通用户均可以用它来在系统上运行指定的命令,以及指定什么时候运行命令,这些指定运行的命令能够是按期垃圾回收,也能够是按期数据分析。 最多见的时间指定格式被称为“crontab”,他不只支持简单的时间周期(如 天天中午一次,每一个小时一次),也支持较复杂的时间周期,如每一个周6、每月的第30天等等。服务器

Cron一般只包含一个组件,被称为“crond”,他是一个deamon程序,加载全部须要运行的cron定时任务,根据他们接下来的运行时间来进行排序,而后这个守护进程将会等待直到第一个任务开始执行。在这个时刻,crond将会加载执行这个任务,以后将它放入队列等待下一次运行。网络

可靠性Reliability

从可靠性的角度来看一个服务,须要有不少注意的地方。
第一,好比crond,他的故障域本质上来讲只是一台机器,若是这个机器没有运行,不管是cron调度仍是加载的任务都是不可运行的。所以,考虑一个很是简单的分布式的例子 ——— 咱们使用两台机器,而后cron调度在其中一台机器上运行job任务(好比经过ssh)。而后产生了一个故障域了:调度任务和目标服务器均可能失败。架构

另一个须要注意的地方是,即便是crond重启(包括服务器重启),上面部署的crontab配置也不该该丢失。crond执行一个job而后就‘忘记’这个job的状态,他并不会尝试去跟踪这个job的执行状态,包括是否该执行是否已经执行。ssh

anacron是一个例外,他是crontab的一个补充,它尝试运行哪些由于服务器down而应该执行却没执行的任务。这仅限于每日或者更小执行频率的job,但对于在工做站和笔记本电脑上运行维护工做很是有用。经过维护一个包括最后执行时间的配置文件,使得运行这些特殊的任务更加方便。分布式

Cron的jobs和幂等性

Cron的jobs用来执行按期任务,可是除此以外,却很难在进一步知道他们的功能。让咱们先把要讨论的主题抛开一边,如今先来就Cron Jobs自己来作下探讨,由于 只有理解了Cron Jobs的各类各样的需求,才能知道他是如何影响咱们须要的可靠性要求,而这一方面的探讨也将贯穿接下来的文章。工具

有一些Cron任务是幂等性的,这样在某些系统故障的状况下,能够很安全的执行他们屡次,好比,垃圾回收。然而有些Cron任务却不该该被执行屡次,好比某个发送邮件的任务。性能

还有更复杂的状况,有些Cron任务容许由于某些状况而“忘了”运行,而某些Cron任务却不能容忍这些,好比,垃圾回收的Cron任务没5分钟调度一次,即便某一次没有执行也不会有太大的问题,然而,一个月一次的支付薪水的任务,却绝对不容许有失误。

Cron Jobs的大量不一样的类型使得不可能有一个通用的解决方案,使得它能够应对各类各样的Fail。因此,在本文中上面说的那些状况,咱们更倾向于错过某一次的运行,而不是运行它们二次或者更多。Cron 任务的全部者应该(也必须)监控着他们的任务,好比返回任务的调用结果,或者单独发送运行的日志给所属者等等,这样,即便跳过了任务的某次执行,也可以很方便的采起对应的补救动做。当任务失败时,咱们更倾向于将任务状态置为“fail closed”来避免产生系统性的不良状态。

大规模部署Cron

当从单机到集群部署Cron时,须要从新思考如何使Cron在这种环境下良好的运行。在对google的Cron进行解说以前,让咱们先来讨论下单机以及多机之间的区别,以及针对这变化如何设计。

扩展基础架构

常规的Cron仅限于单个机器,而大规模部署的Cron解决方案不能仅仅绑定到一个单独的机器。
假设咱们拥有一个1000台服务器的数据中心,若是即便是1/1000的概率形成服务器不可用都能摧毁咱们整个Cron服务,这明显不是咱们所但愿的。

因此,为了解决这个问题,咱们必须将服务与机器解耦。这样若是想运行一个服务,那么仅仅须要指定它运行在哪一个数据中心便可,剩下的事情就依赖于数据中心的调度系统(固然前提是调度系统也应该是可靠的),调度系统会负责在哪台或者哪些机器上运行服务,以及可以良好的处理机器挂掉这种状况。 那么,若是咱们要在数据中心中运行一个Job,也仅仅是发送一条或多条RPC给数据中心的调度系统。

然而,这一过程显然并非瞬时完成的。好比,要检查哪些机器挂掉了(机器健康检查程序挂了怎么办),以及在另一些机器上从新运行任务(服务依赖从新部署从新调用Job)都是须要花费必定时间的。

将程序转移到另一个机器上可能意味着损失一些存储在老机器上的一些状态信息(除非也采用动态迁移),从新调度运行的时间间隔也可能超过最小定义的一分钟,因此,咱们也必须考虑到上述这两种状况。一个很直接的作法,将状态文件放入分布式文件系统,如GFS,在任务运行的整个过程当中以及从新部署运行任务时,都是用他来记录使用相关状态。 然而,这个解决方案却不能知足咱们预期的时效性这个需求,好比,你要运行一个每五分钟跑一次的Cron任务,从新部署运行消耗的1-2分钟对这个任务来讲也是至关大的延迟了。

及时性的需求可能会促使各类热备份技术的使用,这样就可以快速记录状态以及从原有状态快速恢复。

需求扩展

将服务部署在数据中心和单服务器的另外一个实质性的区别是,如何规划任务所须要的计算资源,如CPU或MEM等。

单机服务一般是经过进程来进行资源隔离,虽然如今Docker变得愈来愈广泛,可是使用他来隔离一切目前也不太是很通用的作法,包括限制crond以及它所要运行的jobs

大规模部署在数据中心常用容器来进行资源隔离。隔离是必要的,由于咱们确定但愿数据中心中运行的某个程序不会对其余程序产生不良影响。为了隔离的有效性,在运行前确定得先预知运行的时候须要哪些资源——包括Cron系统自己和要运行的任务。这又会产生一个问题,即 若是数据中心暂时没有足够的资源,那么这个任务可能会延迟运行。这就要求咱们不只要监控Cron任务加载的状况,也要监控Cron任务的所有状态,包括开始加载到终止运行。

如今,咱们但愿的Cron系统已经从单机运行的状况下解耦,如以前描述的那样,咱们可能会遇到部分任务运行或加载失败。这时候辛亏Job配置的通用性,在数据中心中运行一个新的Cron任务就能够简单的经过RPC调用的方式来进行,不过不幸的是,这样咱们只能知道RPC调用成功,却没法具体知道任务失败的具体地方,好比,任务在运行的过程当中失败,那么恢复程序还必须将这些中间过程处理好。

在故障方面,数据中心远比一台单一的服务器复杂。Cron从原来仅仅的一个单机二进制程序,到整个数据中心运行,其期间增长了不少明显或不明显的依赖关系。做为像Cron这样的一个基础服务,咱们但愿获得保证的是,即便在数据中心中运行发生了一些“Fail”(如 部分机器停电或存储挂掉),服务依然可以保证功能性正常运行。为了提升可靠性,咱们应该将数据中心的调度系统部署在不一样的物理位置,这样,即便一个或一部分电源挂掉,也能保证至少Cron服务不会所有不可用。

Google的Cron是如何建设的

如今让咱们来解决这些问题,这样才能在一个大规模的分布式集群中部署可靠的Cron服务,而后在着重介绍下Google在分布式Cron方面的一些经验。

跟踪Cron任务的状态

向上面描述过的那样,咱们应该跟踪Cron任务的实时状态,这样,即便失败了,咱们也更加容易恢复它。并且,这种状态的一致性是相当重要的:相比错误的多运行10遍相同的Cron任务,咱们更能接受的是不去运行它。回想下,不少Cron任务,他并非幂等性的,好比发送通知邮件。

咱们有两个选项,将cron任务的数据统统存储在一个靠谱的分布式存储中,或者 仅仅保存任务的状态。当咱们设计分布式Cron服务时,咱们采起的是第二种,有以下几个缘由:

分布式存储,如GFS或HDFS,每每用来存储大文件(如 网页爬虫程序的输出等),而后咱们须要存储的Cron状态却很是很是小。将如此小的文件存储在这种大型的分布式文件系统上是很是昂贵的,并且考虑到分布式文件系统的延迟,也不是很适合。

像Cron服务这种基础服务,它须要的依赖应该是越少越好。这样,即便部分数据中心挂掉,Cron服务至少也能保证其功能性并持续一段时间。这并不意味着存储应该直接是Cron程序的一部分(这本质上是一个实现细节).Cron应该是一个可以独立运做的下游系统,以便供用户操做使用。

使用Paxos

咱们部署多个实例的Cron服务,而后经过Paxos算法来同步这些实例间的状态。

Paxos算法和它其余的替代算法(如Zab,Raft等)在分布式系统中是十分常见的。具体描述Paxos不在本文范围内,他的基本做用就是使多个不可靠节点间的状态保持一致,只要大部分Paxos组成员可用,那么整个分布式系统,就能做为一个总体处理状态的变化。

分布式Cron使用一个独立的master job,见图Figure 1,只有它才能更改共享的状态,也只有它才能加载Cron任务。咱们这里使用了Paxos的一个变体——Fast Paxos,这里Fast Paxos的主节点也是Cron服务的主节点。
图1

若是主节点挂掉,Paxos的健康检查机制会在秒级内快速发现,并选举出一个新的master。一旦选举出新的主节点,Cron服务也就随着选举出了一个新的主节点,这个新的主节点将会接手前一个主节点留下的全部的未完成的工做。在这里Cron的主节点和Paxos的主节点是同样的,可是Cron的主节点须要处理一下额外的工做而已。快速选举新的主节点的机制可让咱们大体能够容忍一分钟的故障时间。

咱们使用Paxos算法保持的最重要的一个状态是,哪些Cron任务在运行。对于每个运行的Cron任务,咱们会将其加载运行的开始以及结束 同步给必定数量的节点。

Master和Slave角色

如上面描述的那样,咱们在Cron服务中使用Paxos并部署,其拥有两个不一样的角色,master 以及 slave。让咱们来就每一个角色来作具体的描述。

The Master

主节点用来加载Cron任务,它有个内部的调度系统,相似于单机的crond,维护一个任务加载列表,在指定的时间加载任务。

当任务加载的时刻到来,主节点将会 “宣告” 他将会加载这个指定的任务,而且计算这个任务下次的加载时间,就像crond的作法同样。固然,就像crond那样,一个任务加载后,下一次的加载时间可能人为的改变,这个变化也要同步给slave节点。简单的标示Cron任务还不够,咱们还应该将这个任务与开始执行时间相关联绑定,以免Cron任务在加载时发生歧义(特别是那些高频的任务,如一分钟一次的那些)。这个“通告”经过Paxos来进行。图2 展现了这一过程。
图片描述

保持Paxos通信同步很是重要,只有Paxos法定数收到了加载通知,这个指定的任务才能被加载执行。Cron服务须要知道每一个任务是否已经启动,这样即便master挂掉,也能决定接下来的动做。若是不进行同步,意味着整个Cron任务运行在master节点,而slave没法感知到这一切。若是发生了故障,颇有可能这个任务就被再次执行,由于没有节点知道这个任务已经被执行过了。

Cron任务的完成状态经过Paxos通知给其余节点,从而保持同步,这里要注意一点,这里的 “完成” 状态并非表示任务是成功或者失败。咱们跟踪cron任务在指定调用时间被执行的状况,咱们一样须要处理一点状况是,若是Cron服务在加载任务进行执行的过程当中失败后怎么办,这点咱们在接下来会进行讨论。

master节点另外一个重要的特性是,无论是出于什么缘由master节点失去了其主控权,它都必须立马中止同数据中心调度系统的交互。主控权的保持对于访问数据中心应该是互斥了。若是不这样,新旧两个master节点可能会对数据中心的调度系统发起互相矛盾的操做请求。

the slave

slave节点实时监控从master节点传来的状态信息,以便在须要的时刻作出积极响应。全部master节点的状态变更信息,都经过Paxos传到各个slave节点。和master节点相似的是,slave节点一样维持一个列表,保存着全部的Cron任务。这个列表必须在全部的节点保持一致(固然仍是经过Paxos)。

当接到加载任务的通知后,slave节点会将此任务的下次加载时间放入本地任务列表中。这个重要的状态信息变化(这是同步完成的)保证了系统内部Cron做业的时间表是一致的。咱们跟踪全部有效的加载任务,也就是说,咱们跟踪任务什么时候启动,而不是结束。

若是一个master节点挂掉或者由于某些缘由失联(好比,网络异常等),一个slave节点有可能被选举成为一个新的master节点。这个选举的过程必须在一分钟内运行,以免Cron任务丢失的状况。一旦被选举为master节点,全部运行的加载任务(或 部分失败的),必须被从新验证其有效性。这个多是一个复杂的过程,在Cron服务系统和数据中心的调度系统上都须要执行这样的验证操做,这个过程有必要详细说明。

故障恢复

如上所述,master节点和数据中心的调度系统之间会经过RPC来加载一个逻辑Cron任务,可是,这一系列的RPC调用过程是有可能失败的,因此,咱们必须考虑到这种状况,而且处理好。

回想下,每一个加载的Cron任务会有两个同步点:开始加载以及执行完成。这可以让咱们区分开不一样的加载任务。即便任务加载只须要调用一次RPC,可是咱们怎么知道RPC调用实际真实成功呢?咱们知道任务什么时候开始,可是若是master节点挂了咱们就不会知道它什么时候结束。

为了解决这个问题,全部在外部系统进行的操做,要么其操做是幂等性的(也就是说,咱们能够放心的执行他们屡次),要么咱们必须实时监控他们的状态,以便能清楚的知道什么时候完成。

这些条件明显增长了限制,实现起来也有必定的难度,可是在分布式环境中这些限制倒是保证Cron服务准确运行的根本,可以良好的处理可能出现的“fail”。若是不能妥善处理这些,将会致使Cron任务的加载丢失,或者加载屡次重复的Cron任务。

大多数基础服务在数据中心(好比Mesos)加载逻辑任务时都会为这些任务命名,这样方便了查看任务的状态,终止任务,或者执行其余的维护操做。解决幂等性的一个合理的解决方案是将执行时间放在名字中 ——这样不会在数据中心的调度系统里形成任务异变操做 —— 而后在将他们分发给Cron服务全部的节点。若是Cron服务的master节点挂掉,那么新的master节点只须要简单的经过预处理任务名字来查看其对应的状态,而后加载遗漏的任务便可。

注意下,咱们在节点间保持内部状态一致的时候,实时监控调度加载任务的时间。一样,咱们也须要消除同数据中心调度交互时可能发生的不一致状况,因此这里咱们以调度的加载时间为准。好比,有一个短暂可是频繁执行的Cron任务,它已经被执行了,可是在准备把状况通告给其余节点时,master节点挂了,而且故障时间持续的特别长——长到这个cron任务都已经成功执行完了。而后新的master节点要查看这个任务的状态,发现它已经被执行完成了,而后尝试加载他。若是包含了这个时间,那么master节点就会知道,这个任务已经被执行过了,就不会重复执行第二次。

在实际实施的过程当中,状态监督是一个更加复杂的工做,他的实现过程和细节依赖与其余一些底层的基础服务,然而,上面并无包括相关系统的实现描述。根据你当前可用的基础设施,你可能须要在冒险重复执行任务跳过执行任务 之间作出折中选择。

状态保存

使用Paxos来同步只是处理状态中遇到的其中一个问题。Paxos本质上只是经过一个日志来持续记录状态改变,而且随着状态的改变而进行将日志同步。这会产生两个影响:第一,这个日志须要被压缩,防止其无限增加;第二,这个日志自己须要保存在一个地方。

为了不其无限增加,咱们仅仅取状态当前的快照,这样,咱们可以快速的重建状态,而不用在根据以前全部状态日志来进行重演。好比,在日志中咱们记录一条状态 “计数器加1”,而后通过了1000次迭代后,咱们就记录了1000条状态日志,可是咱们也能够简单的记录一条记录 “将计数器设置为1000”来作替代。

若是日志丢失,咱们也仅仅丢失当前状态的一个快照而已。快照实际上是最临界的状态 —— 若是丢失了快照,咱们基本上就得从头开始了,由于咱们丢失了上一次快照与丢失快照 期间全部的内部状态。从另外一方面说,丢失日志,也意味着,将Cron服务拉回到有记录的上一次快照所标示的地方。

咱们有两个主要选择来保存数据: 存储在外部的一个可用的分布式存储服务中,或者,在内部一个系统来存储Cron服务的状态。当咱们设计系统时,这两点都须要考虑。

咱们将Paxos日志存储在Cron服务节点所在服务器本地的磁盘中。默认的三个节点意味着,咱们有三份日志的副本。咱们一样也将快照存储在服务器自己,然而,由于其自己是很是重要的,咱们也将它在分布式存储服务中作了备份,这样,即便小几率的三个节点机器都故障了,也可以服务恢复。

咱们并无将日志自己存储在分布式存储中,由于咱们以为,丢失日志也仅仅表明最近的一些状态丢失,这个咱们实际上是能够接受的。而将其存储在分布式存储中会带来必定的性能损失,由于它自己在不断的小字节写入不适用与分布式存储的使用场景。同时三台服务器全故障的几率过小,可是一旦这种状况发生了,咱们也能自动的从快照中恢复,也仅仅损失从上次快照到故障点的这部分而已。固然,就像设计Cron服务自己同样,如何权衡,也要根据本身的基础设施状况来决定。

将日志和快照存本地,以及快照在分布式存储备份,这样,即便一个新的节点启动,也可以经过网络从其余已经运行的节点处获取这些信息。这意味着,启动节点与服务器自己并无任何关系,从新安排一个新的服务器(好比重启)来担当某个节点的角色 其本质上也是影响服务的可靠性的问题之一。

运行一个大型的Cron

还有一些其余的、小型的,可是一样有趣的一些case或能影响部署一个大型的Cron服务。传统的Cron规模很小:最多包含数十个Cron任务。然而,若是在一个数据中心的超过千台服务器来运行Cron服务,那么你就会遇到各类各样的问题。

一个比较大的问题是,分布式系统经常要面临的一个经典问题:惊群问题,在Cron服务的使用中会形成大量的尖峰状况。当要配置一个天天执行的Cron任务,大多数人第一时间想到的是在半夜执行,而后他们就这么配置了。若是一个Cron任务在一台机器上执行,那没有问题,可是若是你的任务是执行一个涉及数千worker的mapreduce任务,或者,有30个不一样的团队在数据中心中要配置这样的一个天天运行的任务,那么咱们就必需要扩展下Crontab的格式了。

传统的crontab,用户经过定义“分钟”,“小时”,“每个月(或每周)第几天”,“月数”来指定cron任务运行的时间,或者经过星号(*)来表明每一个对应的值。如,天天凌晨运行,它的crontab格式为“0 0 * * *”,表明天天的0点0分运行。咱们在此基础之上还推出了问号(?)这个符号,它标示,在这个对应的时间轴上,任什么时候间均可以,Cron服务就会自由选择合适的值,在指定的时间段内随机选择对应的值,这样使任务运行更均衡。如“0 ? * * *”,表示天天0-23点钟,随机一个小时的0分来运行这个任务。

尽管加了这项变化,由cron任务所形成的load值仍然有明显的尖峰,图3表示了google中cron任务加载的数量。尖峰值每每表示那些须要固定频率在指定时间运行的任务。
图片描述

总结

Cron服务做为UNIX的基础服务已经有接近10年。当前整个行业都朝着大型分布式系统演化,那时,表示硬件的最小单位将会是数据中心,那么大量的技术栈须要对应改变,Cron也不会是例外。仔细审视下Cron服务所须要的服务特性,以及Cron任务的需求,都会推进咱们来进行新的设计。

基于google的解决方案,咱们已经讨论了Cron服务在一个分布式系统中对应的约束和可能的设计。这个解决方案须要在分布式环境中的强一致性保证,它的实现核心是经过Paxos这样一种通用的算法,在一个不可靠的环境中达成最终一致。使用Paxos,正确对大规模环境下Cron任务失败状况的分析,以及分布式的环境的使用,共同造就了在google内部使用的健壮的Cron服务。


原文:https://queue.acm.org/detail....

相关文章
相关标签/搜索