当前位置: 首页 > 科技观察

如何设计一个全局稳定的Cron服务_0

时间:2023-03-21 21:26:46 科技观察

这篇文章主要讲述了Google如何为内部团队实现了一套可靠的分布式Cron服务,这些团队需要对大部分计算工作进行定时调度。在这个系统的实践过程中,我们收获了很多,包括如何设计和实现它,让它看起来像一个可靠的基础服务。在这里,我们来讨论一下分布式Cron可能会遇到哪些问题,以及如何解决。Cron是UNIX中的一个常用工具,用于周期性地执行用户指定的一些任意任务。让我们先分析一下Cron的基本原理和最常见的实现,然后再回顾一下像Cron这样的服务应该如何运行在一个大型的、分布式的环境中,这样即使是单机故障也不会对系统的可用性造成影响。我们将介绍一个建立在少量机器上的Cron系统,然后结合数据中心的调度服务,让cron任务可以运行在整个数据中心。在我们描述如何运行可靠的分布式Cron服务之前,让我们从SRE的角度来回顾一下Cron。Cron是一个通用工具,管理员和普通用户都可以使用它在系统上运行指定的命令,并指定何时运行命令。这些指定的命令可以是定期垃圾收集或定期数据分析。最常见的时间规范格式称为crontab,它不仅支持简单的时间段(例如每天中午一次,每小时一次),还支持更复杂的时间段,例如每个星期六,每个星期六的第一天月等30天等。cron通常只包含一个组件,叫做crond,它是一个后台守护进程,加载所有需要运行的cron定时任务,按照它们下次运行的时间排序,然后守护进程会一直等到***一个任务开始执行。此时,cron将加载并执行该任务,然后将其排队等待下一次运行。ReliabilityReliability从可靠性的角度看服务需要多加注意。***,比如cron,它的故障域本质上只是一台机器,如果机器不运行,cron调度和加载的任务都无法运行。因此,考虑一个非常简单的分布式示例——我们使用两台机器,cron安排一个作业在其中一台上运行(比如通过ssh)。然后创建一个失败域:计划任务和目标服务器都可能失败。另外需要注意的是,即使crond重启(包括服务器重启),上面部署的crontab配置也不应该丢失。crond执行任务然后“忘记”任务的状态,它不会尝试跟踪任务的执行状态,包括它是否应该执行。一个例外是anacron,它是crontab的补充,并尝试运行本应执行但由于服务器停机而未执行的任务。这仅限于日常或不太频繁的任务,但对于在工作站和笔记本电脑上运行维护作业很有用。通过维护包含最新执行时间的配置文件,可以更方便地运行这些特殊任务。Cron的任务和幂等性Cron的任务用于执行周期性任务,但除此之外,很难进一步了解它们的功能。抛开要讨论的话题,现在来讨论Cron任务本身,因为只有了解了Cron任务的各种要求,才能知道它是如何影响我们需要的可靠性要求的,而这方面的讨论也会跑通过下一篇文章。一些cron作业是幂等的,因此它们可以在某些系统故障(例如垃圾收集)的情况下安全地执行多次。但是,某些Cron任务不应执行多次,例如发送电子邮件的任务。还有更复杂的情况,有些Cron任务允许由于某些条件“忘记”运行,而有些Cron任务不能容忍这些,例如垃圾收集的Cron任务每5分钟调度一次,即使某个时间是不执行不会有太大的问题,但是每月发一次工资的任务绝对不允许出错。各种不同类型的cron作业使得不可能有一个通用的解决方案来处理各种各样的故障。所以,在本文上面提到的情况下,我们更倾向于错过某个特定的运行,而不是运行两次或更多次。cron任务的拥有者应该(也必须)监控自己的任务,比如返回任务的调用结果,或者将运行日志单独发送给拥有者等等,这样即使跳过了某个任务的执行,可以方便快捷地采取相应的补救措施。当任务失败时,我们更愿意将任务状态设置为“失败关闭”以避免系统性不良状态。Cron的大规模部署在将Cron从单机部署到集群时,需要重新思考如何让Cron在这种环境下运行良好。在讲解Google的Cron之前,我们先讨论一下单机和多机的区别,以及如何针对这种变化进行设计。扩展基础架构虽然常规Cron仅限于一台机器,但大规模部署的Cron解决方案不能只绑定到一台机器。假设我们有一个1000台服务器的数据中心,如果服务器不可用哪怕是1/1000的机会都可以破坏我们整个Cron服务,这显然不是我们想要的。所以,要解决这个问题,我们就得把服务和机器解耦。这样,如果要运行一个服务,只需要指定它运行在哪个数据中心,剩下的就看数据中心的调度系统了(当然,前提是调度系统也应该是可靠的)。服务是在哪台或者哪台机器上运行的,能够很好的处理机器挂掉的情况。那么,如果我们要在数据中心运行一个任务,我们只需要向数据中心的调度系统发送一个或多个RPC即可。但是,这个过程显然不是瞬间的。比如检查哪些机器宕机(机器健康检查程序宕机怎么办),在其他机器上重新运行任务(服务依赖重新部署调用任务)等,都会需要一定的时间。将程序转移到另一台机器上可能意味着丢失一些保存在旧机器上的状态信息(除非也使用动态迁移),并且重新调度操作的时间间隔也可能超过定义的最小一分钟,因此我们还必须考虑以上两个情况。一种非常直接的做法是将状态文件放入一个分布式文件系统中,比如GFS,用于记录任务运行的整个过程和重新部署运行任务时的使用相关状态。但是,这个解决方案不能满足我们预期的时效性要求。例如,如果要运行一个每五分钟运行一次的Cron任务,那么重新部署所消耗的1-2分钟对于这个任务来说也是一个相当大的延迟。向上。时效性的需要可能促使使用各种热备份技术,这些技术允许快速状态记录和从原始状态快速恢复。需求扩展在数据中心部署服务与单台服务器的另一个本质区别是如何规划任务所需的计算资源,例如CPU或内存。单机服务通常使用进程来隔离资源。虽然现在Docker越来越普遍,但是用它来隔离一切,包括限制crond和它需要运行的任务,并不是很常见。数据中心的大规模部署往往使用容器来进行资源隔离。隔离是必要的,因为我们当然希望在数据中心运行的一个程序不会对其他程序产生不利影响。为了隔离有效,需要在运行前预测运行需要哪些资源——包括Cron系统本身和要运行的任务。这又产生了一个问题,如果数据中心暂时没有足够的资源,那么这个任务可能会延迟运行。这就要求我们不仅要监控Cron任务的加载,还要监控Cron任务从加载开始到运行结束的整个状态。既然我们想要的Cron系统已经解耦在单机上运行,??如前所述,我们可能会遇到一些任务运行或加载失败的情况。这时候,得益于任务配置的通用性,在数据中心运行一个新的cron任务可以简单的通过RPC调用来完成,但是遗憾的是,我们只能知道RPC调用是否成功,而无法知道具体的任务失败的地方,比如任务在运行过程中失败了,那么恢复程序也必须处理这些中间过程。就故障而言,数据中心远比单个服务器复杂。Cron已经从只是一个独立的二进制程序发展到运行在整个数据中心,其间增加了许多明显或不明显的依赖。作为像Cron这样的基础服务,我们要保证的是,即使数据中心发生了一些“Fail”(例如,某些机器断电或存储宕机),该服务仍然能够保证数据中心的正常运行。功能。为了提高可靠性,我们应该将数据中心的调度系统部署在不同的物理位置,这样即使一个或部分电源掉电,也能保证至少所有的Cron服务不会不可用.Google的Cron是如何构建的现在让我们来解决这些问题,以便在大规模分布式集群中部署可靠的Cron服务,然后重点介绍Google在分布式Cron方面的一些经验。跟踪cron任务的状态如上所述,我们应该跟踪cron任务的实时状态,这样即使它失败了,我们也可以更容易地恢复它。此外,此状态的一致性很关键:不运行同一个cron作业比错误地运行同一个cron作业10次更容易接受。回想一下,许多Cron任务不是幂等的,例如发送通知电子邮件。我们有两个选择,将所有cron作业数据存储在可靠的分布式存储中,或者只保存作业的状态。我们在设计分布式Cron服务的时候,采用了第二种方式,原因有几个:分布式存储,比如GFS或者HDFS,经常用来存储大文件(比如网络爬虫的输出等),然后我们需要存储的Cron状态非常非常小。在如此大的分布式文件系统上存储这么小的文件非常昂贵,考虑到分布式文件系统的延迟,也不是很合适。像Cron服务这样的基本服务应该需要尽可能少的依赖。这样,即使部分数据中心宕机,Cron服务至少可以保证其功能并继续运行一段时间。这并不意味着存储应该直接成为cron程序的一部分(这本质上是一个实现细节)。Cron应该是一个独立的下游系统,供用户操作。使用Paxos,我们部署多个Cron服务实例,然后使用Paxos算法同步这些实例之间的状态。Paxos算法及其替代方案(如Zab、Raft等)在分布式系统中非常常见。Paxos的具体描述超出了本文的范围。它的基本功能是保持多个不可靠节点的状态一致。只要大部分Paxos组成员可用,整个分布式系统就可以作为一个整体来处理状态变化。分布式Cron使用独立的主任务,如下图所示,只有它可以更改共享状态,也只有它可以加载Cron任务。我们这里使用Paxos的一个变种——FastPaxos,其中FastPaxos的主节点也是Cron服务的主节点。如果主节点挂了,Paxos的健康检查机制会在秒级内快速发现并选举出新的主节点。一旦选举出新的主节点,Cron服务也会选举出一个新的Cron主节点,这个新的Cron主节点将接管上一个主节点留下的所有未完成的工作。这里Cron的主节点和Paxos的主节点是一样的,只是Cron的主节点需要处理一些额外的工作。快速选举新主节点的机制使我们能够容忍大约一分钟的停机时间。我们使用Paxos算法维护的最重要的状态之一是哪些cron作业正在运行。对于运行的每个cron作业,我们将其加载运行的开始和结束同步到一定数量的节点。主节点和从节点角色如上所述,我们使用Paxos并部署在Cron服务中,它有两个不同的角色,主节点和从节点。让我们详细描述每个角色。主节点主节点用于加载cron任务。它有一个内部调度系统,类似于单机的crond,维护一个任务加载列表,在指定的时间加载任务。当加载任务的时间到来时,主节点会“宣布”将加载指定的任务,并计算该任务的下一次加载时间,就像crond一样。当然,就像cron一样,一个任务加载完成后,下一次加载的时间可能会被人为改变,这个改变也必须同步到从节点。仅仅识别cron任务是不够的,我们还应该将这个任务绑定到开始执行的时间,以避免加载cron任务时出现歧义(尤其是那些频率高的任务,比如一分钟一次)。这个“广告”是通过Paxos进行的。下图说明了这个过程。保持Paxos通信同步非常重要。只有当Paxosquorum收到加载通知后,才能加载并执行指定的任务。Cron服务需要知道每个任务是否已经开始,这样即使主节点挂了,它也可以决定下一步的动作。如果没有同步,就意味着整个Cron任务都运行在主节点上,从节点无法感知这一切。如果发生故障,任务很有可能会再次执行,因为没有节点知道任务已经执行。Cron任务的完成状态通过Paxos通知其他节点保持同步。这里要注意,这里的“完成”状态并不代表任务成功或者失败。我们在指定的调用时间跟踪cron任务的执行情况。我们还需要处理加载任务执行的过程中,如果cron服务失败了怎么办,这个我们接下来会讲到。主节点的另一个重要特点是,无论主节点因为什么原因失去其主宰权,都必须立即停止与数据中心调度系统的交互。掌握权的维护应该与访问数据中心互斥。否则,新旧主节点可能会向数据中心的调度系统发起相互冲突的操作请求。从节点实时监听主节点传来的状态信息,以便在需要时做出积极的响应。所有主节点的状态变化信息通过Paxos传递给各个从节点。与主节点类似,从节点也维护着一个所有cron任务的列表。这个列表必须在所有节点上保持一致(当然是通过Paxos)。从节点收到加载任务的通知后,会将本次任务的下一次加载时间放入本地任务列表中。这个重要的状态信息更改(同步完成)确保系统内cron作业的调度是一致的。我们跟踪所有活动的加载任务,也就是说,我们跟踪任务何时开始,而不是何时结束。如果某个主节点由于某种原因(例如网络异常等)挂掉或失去连接,则可能会选举一个从节点作为新的主节点。此选举过程必须在一分钟内运行以避免丢失cron作业。Onceelectedasthemasternode,allrunningloadtasks(orpartiallyfailed),mustbere-validatedfortheirvalidity.这可能是一个复杂的过程。cron服务系统和数据中心的调度系统都需要进行这样的校验操作。这个过程需要详细解释。故障恢复如前所述,主节点和数据中心的调度系统之间会通过RPC加载一个逻辑cron任务。但是,这一系列的RPC调用可能会失败,所以我们必须考虑到这一点,并妥善处理。回想一下,每个加载的cron作业都有两个同步点:开始加载和执行完成。这使我们能够分离不同的加载任务。尽管任务加载只需要一次RPC调用,但我们如何知道RPC调用实际上成功了呢?我们知道任务什么时候开始,但是如果主节点挂了,我们不知道它什么时候结束。为了解决这个问题,在外部系统中执行的所有操作要么必须是幂等的(即我们可以安全地多次执行它们),要么我们必须实时监控它们的状态,以便我们清楚地知道什么时候会完毕。这些条件显然增加了限制,执行起来也比较困难。但是,在分布式环境中,这些限制是保证Cron服务准确运行的基础,可以很好地处理可能的“失败”。未能正确处理这些将导致缺少cron作业加载,或加载多个重复的cron作业。大部分基础服务在数据中心(如Mesos)加载逻辑任务时都会对这些任务进行命名,方便查看任务状态、终止任务或进行其他维护操作。一个合理的幂等性解决方案是将执行时间放在名称中——这样就不会在数据中心的调度系统中造成任务突变操作——然后将它们分发到Cron服务的所有节点。如果Cron服务的主节点宕机了,新的主节点只需要简单的通过任务名称的预处理来查看自己对应的状态,然后加载缺失的任务即可。注意,当我们保持节点间内部状态一致时,我们实时监控调度加载任务的时间。同样,我们也需要排除与数据中心调度器交互时可能出现的不一致,所以这里我们以调度器的加载时间为标准。比如有一个很短但是执行频率很高的Cron任务,本来执行完了,但是准备通知其他节点情况的时候,master节点挂了,故障时间持续了特别长的时间——solongCron任务已经执行。执行成功。然后新的主节点检查这个任务的状态,发现已经执行了,然后尝试加载它。如果加上这个时间,那么master节点就知道这个任务已经执行过了,就不会重复第二次了。在实际的实施过程中,国家监管是一项比较复杂的工作,其实施过程和细节依赖于其他一些底层基础服务。但以上不包含相关系统的实现说明。根据您当前可用的基础架构,您可能需要在冒险重新执行任务和跳过它们之间做出选择。使用Paxos同步保存状态只是在处理状态时遇到的问题之一。Paxos本质上只是用一个日志来不断记录状态的变化,随着状态的变化日志是同步的。这有两个作用:第一,需要压缩日志以防止其增长;其次,日志本身需要保存在一个地方。为了避免它的极端增长,我们只对当前的状态进行快照,这样我们就可以快速重建状态,而不必根据之前所有的状态日志来重放它。比如日志中我们记录一个状态“计数器加1”,迭代1000次后记录1000条状态日志,但是我们也可以简单的记录一条“计数器置1000”的记录来做代入。如果日志丢失,我们只会丢失当前状态的快照。快照实际上是最关键的状态——如果我们丢失了快照,我们基本上不得不从头开始,因为我们丢失了从上次快照到丢失快照之间的所有内部状态。另一方面,丢失日志也意味着将Cron服务拉回到上一个带有记录的快照所指示的位置。我们有两个主要的数据存储选项:在外部可用的分布式存储服务中,或者在系统内部存储cron服务的状态。我们在设计系统时需要考虑这两件事。我们将Paxos日志存储在cron服务节点所在服务器的本地磁盘上。默认的三个节点意味着我们有三份日志。我们也把快照保存在服务器本身,但是因为它很重要,我们也在分布式存储服务中做了备份,这样即使有小概率三台节点机都出故障了,我们也可以服务恢复。我们不把日志本身存储在分布式存储中,因为我们觉得日志的丢失只是代表最近的一些状态丢失,这其实是我们可以接受的。存储在分布式存储中会带来一定的性能损失,因为它不断写入小字节,不适合分布式存储的使用场景。同时,三台服务器都出现故障的概率太小了,但是一旦出现这种情况,我们也可以自动从快照中恢复,只丢失最后一次快照到故障点的部分。当然,就像设计Cron服务本身一样,如何取舍也要根据自己的基础设施来决定。在本地存储日志和快照,在分布式存储中备份快照,这样即使有新的节点启动,也可以通过网络从其他已经运行的节点中获取信息。这意味着启动一个节点与服务器本身无关,重新调度一个新的服务器(比如重启)来扮演一个节点的角色本质上是影响服务可靠性的问题之一。运行大型cron还有其他一些小但同样有趣的情况会影响大型cron服务的部署。传统的Crons很小:最多几十个cron作业。但是,如果你在一个数据中心的一千多台服务器上运行cron服务,那么你就会遇到各种各样的问题。一个比较大的问题是分布式系统经常面临的一个经典问题:雷群问题,会导致Cron服务的使用出现大量尖峰。当谈到将cron作业配置为每天运行时,大多数人首先想到的是在半夜运行它,并以这种方式进行配置。如果一个cron作业在一台机器上执行,那很好,但是如果你的工作是执行一个涉及数千名工人的mapreduce作业,或者,一个数据中心的30个不同的团队,配置这样一个来运行日常任务,那么我们必须扩展crontab的格式。在传统的crontab中,用户指定cron任务的运行时间,通过定义“分钟”、“小时”、“月(或周)”、“月数”或星号(*)来表示每一个对应的值。比如每天早上运行,它的crontab格式是00***,表示每天0:00运行。在此基础上,我们还引入了问号(?)符号,表示在对应的时间轴上,任何时间都可以,Cron服务会自由选择合适的值,并在指定时间段内随机选择对应的值,使任务运行更加平衡。例如,0?***表示任务将在每天0-23点的随机时间的0分钟运行。尽管有这种变化,由cron任务引起的负载值仍然有一个明显的峰值,下图显示了谷歌的cron任务负载量。峰值往往表示需要在指定时间以固定频率运行的任务。总结Cron服务作为UNIX的一项基本服务已有将近10年的历史。目前,整个行业都在向大规模分布式系统发展。届时,代表硬件的最小单位将是数据中心,大量的技术栈需要随之改变,Cron也不例外。仔细查看Cron服务所需的服务特性,以及Cron任务的要求,将驱使我们进行新的设计。基于Google的解决方案,我们讨论了分布式系统中Cron服务的相应约束和可能的设计。该解决方案需要在分布式环境中有很强的一致性保证。其实现的核心是通过Paxos等通用算法在不可靠的环境下达成最终共识。Paxos的使用、对大规模cron作业失败的适当分析以及分布式环境的使用相结合,创建了一个在Google内部使用的强大的cron服务。