十四、管理关键状态:利用分布式共识来提高可靠性
跨物理区域分布式运行系统可以解决很多的系统灾备问题,以保障在灾难来临时系统仍然能正常运行。但是却带来维护系统一致状态视图的需求,而这个问题的解决常常是复杂且难以实现的。
一组服务进程可能想要可靠地对以下问题产生共识:
- 哪个进程目前是该组织进行的leader?
- 本组中都包含哪些进程?
- 是否已经将某个消息成功地插入了某个分布式队列?
- 某个进程目前是否还持有租约?
- 数据存储中的某个键对应的值是什么?
在构建可靠的、高可用的系统过程中,我们发现分布式共识系统适合用来维护某个强一致的系统状态。这个分布式共识系统主要解决了在不稳定的通信环境下一组进程之间对某项事情达成一致的问题。
CAP理论,描述了一个分布式系统不可能同时满足以下三个要求:
- 每个节点(副本)上所见的数据是一致的;
- 每个节点都可以访问数据;
- 可以承受网络分区问题;
ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据库,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。
BASE,Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
- 基本可用,是指分布式系统在出现不可预知故障的时候,允许损失部分可用性;
- 软状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时;
- 最终一致性,强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性;
很多分布式系统的问题最后都归结为分布式共识问题的不同变种,包括领导人选举、小组成员信息、各种分布式锁和租约机制、可靠的分布式队列和消息传递,以及在任何一种需要在多个进程中共同维护一致的关键状态的机制。所有这些问题都应该仅仅采用经过正式的正确性证明的分布式共识算法来解决。
分布式共识算法基础知识:
- 分布式共识问题有很多变种,当维护分布式软件系统时,我们关注的是异步式分布式共识在消息传递可能无限延迟的环境下的实现;
- 分布式共识算法可能是崩溃不可恢复的,也可能是崩溃可恢复的,显然后者对实际工作更有价值;
- 分布式共识算法需要应对拜占庭式和非拜占庭式问题,在分布式系统领域拜占庭问题指的是当某个进程由于程序Bug或恶意行为发送了不正确消息的问题,这种问题的处理成本更高(3m+1);
- 理论上讲,在有限时间内(不稳定的网络条件下)解决异步分布式共识问题是不可能的;
- 在实际操作中,我们通过保证给系统提供足够的健康的副本,以及良好的网络连接状态来保障分布式共识算法在大多数情况下是可以在有限时间内达成共识的;
- 最初的分布式共识问题的解决方案是Lamport的Paxos协议,后续也有其他的不同协议能够解决这个问题,如Raft、Zab(Zookeeper Atomic Broadcast)以及Mencius;
Paxos概要:协议示例
算法中的参与者主要分为三个角色,同时每个参与者又可兼领多个角色:
- proposer 提出提案,提案信息包括提案编号和提议的value;
- acceptor 收到提案后可以接受(accept)提案;
- learner 只能"学习"被批准的提案;
算法保重一致性的基本语义:
- 决议(value)只有在被proposers提出后才能被批准(未经批准的决议称为"提案(proposal)");
- 在一次Paxos算法的执行实例中,只批准(chosen)一个value;
- learners只能获得被批准(chosen)的value;
有上面的三个语义可演化为四个约束:
- P1:一个acceptor必须接受(accept)第一次收到的提案;
- P2a:一旦一个具有value v的提案被批准(chosen),那么之后任何acceptor 再次接受(accept)的提案必须具有value v;
- P2b:一旦一个具有value v的提案被批准(chosen),那么以后任何 proposer 提出的提案必须具有value v;
- P2c:如果一个编号为n的提案具有value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于n的任何提案,要么他们已经接受(accpet)的所有编号小于n的提案中编号最大的那个提案具有value v;
分布式共识的系统架构模式
分布式共识算法是很底层、很原始的,它们仅仅可以让一组节点一次共同接受一个值。很多成功使用分布式共识算法的系统常常是作为该算法实现的服务的一个客户端来使用的,例如zookeeper、Consul以及etcd。通过将共识系统作为一个服务原语提供,而非以类库方式让工程师链接进他们的应用程序。
可靠的复制状态机RSM
一个复制状态机RSM是一个能够在多个进程中用同样顺序执行同样的一组操作的系统。
在RSM系统上进行的操作是通过共识算法来全局排序的,复制状态机是实现在共识算法逻辑之上的一个系统。
可靠的复制数据存储和配置存储
可靠的复制数据存储是复制状态机的一个应用。复制数据存储在关键路径中使用到了共识算法。性能、吞吐量和扩展能力在这种设计中非常重要。使用共识算法的数据存储可以对“读”操作提供多种不同的一致性语义。
使用领头人选举机制实现高可用的处理系统
分布式系统的领头人选举是跟分布式共识等价的问题。复制多份服务并使用一个唯一的领头人(leader)来进行某种类型的工作是很常见的设计。唯一的领头人是一种保证粗粒度互斥性的方法。
领头人的工作通常是负责协调某个工作者池中的工作者进程。在这种类型的组件里,不像复制数据存储那样,共识算法并不处在系统的关键路径中,所以共识算法的吞吐量不是系统的主要问题。
分布式协调和锁服务
barrier(屏障)是分布式系统中的一种原语,可以用来阻挡一组进程继续工作,直到某种条件得到满足。使用barrier实际上将一个分布式计划分成数个逻辑阶段执行。例如MapReduce服务中,可以使用barrier来确保整个Map阶段已经完成,再开始Reduce阶段的计算。barrier可以由一个单独的协调者进程实现,但这会导致系统中出现单点故障源。barrier也可以使用一个RSM系统来实现,例如Zookeeper就可以实现这种屏障模式。
lock(锁),是分布式系统中另外一个很有用的协调性原语,可以用RSM实现。分布式锁是一个应该被小心使用的底层系统原语,大多数应用程序应该使用一种更高层的系统来提供分布式事务服务。
可靠的分布式队列和消息传递
队列是一种常见的数据结构,经常用来给多个工作进行分发任务。采用队列模式的一个问题是,如果队列不可用,整个系统都将无法工作。
利用RSM来实现队列可以将危险性最小化,从而使得整个系统更加可靠。
原子广播,是分布式系统的一个原语,意思是整个系统的参与者都可以可靠地接收到消息,并且以同样的顺序来处理这些消息。原子性广播和分布式共识本质上是一个问题。
分布式共识系统的性能问题
可能影响到分布式共识系统的系统负载包括:
- 吞吐量,在负载峰值时,单位时间内提出提议的数量;
- 请求类型,需要修改状态的写请求的比例;
- 读请求的一致性要求;
- 如果数据大小可变,请求的大小;
部署策略也有很多可变之处:
- 局部区域部署,还是广域部署?
- 采用的是哪种仲裁过程,大部分进程的分布情况如何?
- 该系统是否使用分片、流水线和批处理技术?
很多共识系统都会选举出一个指定的领头人进程,同时要求所有请求都必须发往该特殊节点。对于不同地理位置的客户端来说,距离较远的节点有更高的往返周期RTT(Round Trip Time),性能差距会非常大。
复合式Paxos
Multi-Paxos协议采用了一个强势领头人进程的概念,除非目前没有选举出任何的领头人进程,或者发生了某种错误,在正常情况下,提议者只需要对满足法定人数的进程发送一次消息就可以保障系统达成共识。这种强势领头人在很多共识协议中都适用,在系统需要传递大量消息的时候非常合适。在分布式共识算法第一阶段成功后可以建立一个新的有序视图,即一个领头人租约,该视图只要保持不变,就可以跳过协议的第一阶段,拥有视图的提议者可以简单的发送Accept消息,一旦收到多数参与者的回应,就可以保证系统达成了共识。
组内的另外一个进程可以在任何时间提交消息,并成为一个新的提议者,但提议者角色的切换会带来性能损失。一方面是系统需要额外的时间重新执行协议的第一阶段部分;更重要的是,更换提议者可能会造成一种“提议者决斗”的状况,陷入一种活跃死锁的场景。
所有实用的共识系统都必须解决这种冲突问题,通常要么选举一个固定的提议者进程(leader),负责发送系统中的所有提议;要么使用一个轮换机制,给每个进程划分特定的提议槽。
对于使用领头人机制的系统来说,领头人选举机制必须仔细调优,以在没有领头人的时候系统不可用或多个领头人互相冲突的危险中取舍。其中设置正确的超时时间和后退策略是非常重要的。如果多个进程没有检测到领头人进程,同时试图成为领头人,那么很可能没有一个进程能够成功。在系统中引入随机变量是最佳选择。Raft协议就利用这些机制实现了一个非常完善和考虑周全的领头人选举机制。
应对大量的读操作
许多系统都是读操作居多,针对大量读操作的优化是这些系统性能优化的关键。复制数据存储的优势在于数据同时在多个地点可用。即,如果不是所有的读请求都需要强一致性,数据就可以从任意一个副本来读取。
如果需要保证读取的数据是最新的,则至少要满足以下条件中的一条:
- 进行一次只读的共识操作;
- 从一个保证有最新数据的副本读取数据;
- 使用法定租约协议(quorum lease),在该协议下某些副本被授予部分或全部数据的一个租约,用一些写性能上的损失换来了强一致性的本地读操作的可能;
法定租约
Quorum lease是一种专注于降低操作延迟和提高读操作的吞吐量的技术手段。在经典的Paxos协议或其他的分布式协议中,进行强一致性的读操作需要一次分布式共识操作,或者需要系统提供一个稳定的领头人进程。在很多系统中,读操作相比写操作要多得多,所以上述两种情况都极大地限制了系统的延迟和吞吐量。
法定租约技术针对数据的一部分给系统中的法定人数进程发放了一个租约,这个租约是带有具体时间范围的(通常很短)。在这个租约有效时间内,任何对该部分数据的修改操作都必须要被法定租约中的所有进程响应。如果租约中的任何一个副本不可用,那么该部分数据在租约过期前将无法被修改。法定租约对大量读操作的系统是非常有用的,尤其是当数据的某一部分是被集中在某一个地理区域的进程所读取的时候。
分布式共识系统的性能与网络延迟
分布式系统的写性能面临2个主要的物理限制,一个是网络往返时间RTT,另一个是数据写入持久化存储的时间。
如果是在局域网环境中,一个分布式共识系统的性能是和一个异步的leader--follower系统类似,很多传统数据库都使用该系统进行数据复制操作。然而分布式共识系统通常需要副本运行在较远距离上,这样可保障副本处于多个不同的故障域中。
很多共识系统使用TCP/IP作为通信协议,TCP/IP是面向连接的,同时针对消息的FIFO提供了强保障。但是在发送任何数据前,都需要建立新的TCP/IP连接,即3次握手协议。此外TCP/IP的慢启动机制也限制了连接的初始带宽,常见的TCP/IP窗口大小在4-15KB之间。
TCP/IP的慢启动问题对共识组中的进程来说可能不是问题,因为这些进程会互相建立一个连接并保持这些连接。但对于拥有几千个副本的数据存储系统以及更大规模的客户端来说,建立TCP/IP连接所带来的成本是无法接受的。一个解决方案是使用地域性的代理池,如下图:
该代理池的进程与共识组建立持久的TCP/IP连接,以降低客户端的开销。同时代理池也是包装分片逻辑和负载均衡逻辑以及共识系统的服务发现逻辑的好地方。
快速Paxos协议:性能优化
快速Paxos协议是原协议的一个变种,意在优化Paxos算法在广域网中的性能。
使用快速Paxos协议的每个客户端可以直接向组内的接收者们发送propose消息,而不需要像传统Paxos或复合Paxos那样通过一个领头人进程发送。
即,使用一个从客户端到所有接收者的并发消息来替代经典Paxos协议中的两个消息发送操作:
- 从客户端到提议者的一个消息;
- 从提议者到组内其它成员的一个并发消息;
快速Paxos协议在特定的网络使用场景下,反而会比经典paxos协议要慢(如果客户端到接收者的RTT很高,而接收者之间的互相连接却很快)。
同时,对于借助批处理操作以提升系统吞吐量的设计,快速Paxos协议将使得这种设计更加难以实现。
稳定的领头人机制
复合Paxos协议是通过选举一个稳定的领头人进程来提高性能的,Zab和Raft协议是其他两个例子,它们也是通过选举稳定的领头人进程来提高性能。
这种解决方案也会存在以下问题:
- 所有的改变操作都必须经过领头人,这使得距离领头人较远的客户端必须要增加额外的网络延迟;
- 领头人进程的出口网络带宽是系统性能的瓶颈,因为该领头人的Accept消息包含了每个提议的所有数据,而其它进程发送的仅仅是包含了交易数字的消息;
- 如果领头人进程正好处在一台有性能问题的机器上,那整个系统的吞吐量都会受到影响;
批处理
出于增加系统吞吐量的目的,我们可以采取流水线机制结合批处理机制使用。采取流水线机制,使得多个提议可以同时进行。流水线中的一批请求仍然是用一个视图序号和交易序号全局排序的。
磁盘访问
在复合Paxos协议中,一次共识操作的延迟中,有以下几个操作:
- 提议者的一次硬盘写入操作,发出Accept前;
- 并行消息发送给接收者;
- 每个接收者的磁盘写操作(并行);
- 回复消息的传递;
其中1、3是磁盘访问延时,2、4是网络延时。
磁盘访问性能优化:
- 分布式共识算法经常用来实现一个RSM,RSM需要保留一份交易日志,以便于灾难恢复。共识算法的日志可以和RSM交易日志合并,以避免不停地向磁盘上两个不同地方交替写入。
- 利用磁盘的写缓存,批量将日志的写操作刷新到硬盘,以提高磁盘访问性能。
- 在提议者中将多个客户端的操作批处理为一个操作,从而提高系统吞吐量。
分布式共识系统的部署
系统设计者部署共识系统时,最重要的决策在于选择副本的数量和对应的部署位置。
- 一般来说,共识系统都要采用”大多数“的法定进程,即一组2f+1副本组成的共识组可以同时接受f个副本失败而继续运行。
- 如果需要容忍拜占庭式失败,则需要3f+1个副本来承受f个副本失败。
- 针对非拜占庭式失败的情况,最小的副本数量为3,如果共识系统中大部分的副本已经无法访问,以至于无法完成一次法定进程,那么该系统理论上已经进入一个无法恢复的状态。
- 分布式日志是生产系统中很重要的一部分,Raft描述了一个管理分布式日志的方法,准确定义了在分布式日志中的各种空洞应该如何填补。
- 系统性能和仲裁过程中不需要投票的副本数量有直接关系,系统能够承受的失败和落后的副本数量越多,那么整体的系统性能就可能越好。因为系统中的一小部分副本可以落后,从而使得其他参与仲裁过程的副本跑得更快。
- 副本的部署成本,对于一个很大的集群会成为一个问题。
于是,针对任何系统的副本数量的考虑都是基于以下几个因素中的一个进行妥协:
- 对可靠性的要求;
- 计划内维护操作的频率;
- 危险性;
- 性能;
- 成本;
副本的位置
关于共识集群进程的部署位置的决策主要来源于以下两个因素的取舍:
- 系统应该承受的故障域数量;
- 系统的延迟性要求;
一个故障域是指系统中可能由于一个故障而同时不可用的一组组件。
常见的故障域包括:
- 物理机器;
- 数据中心中用同一个供电设施的一个机柜;
- 数据中心中的数据个机柜,使用同一个网络设备连接;
- 受单个光纤影响的一个数据中心;
- 处于同一个地理区域的一组数据中心,可能被同一个自然灾害所影响;
对延迟的敏感性和对灾难的承受程度,每个系统之间差别很大。某些共识系统架构并不需要特别高的吞吐量,或者特别低的延迟。
有的时候,不断增加系统可承受的故障域大小并不一定合理。例如,如果共识系统的所有客户端都在某个故障域内分布,那在更广泛范围内部署一个分布式系统的价值就是可疑的了。
我们在考虑副本位置时,还应该将灾难恢复考虑在内。当使用分布式系统处理一些关键数据时,即使已经有了可靠的共识系统部署在不同的故障域范围内,也必须经常将数据备份在其他地方。
因为有两个故障域是我们永远无法避免的:软件本身和系统管理员的人为错误。
理想情况下,客户端和共识系统之间的RTT应该是最小化的,当决定副本位置的时候,要特别注意客户端的可见性能因素。
容量规划和负载均衡
当设计某个部署场景时,我们必须保证有足够容量应对系统负载。
- 在分片式部署时,可以通过调整分片的数量来调整容量。
- 对于那些可以从副本读取数据的系统来说,可以通过添加副本来提高读性能。
- 增加更多的副本也有成本,在使用强势领头人进程的算法中,增加副本会增加领头人进行的负载,在P2P协议中增加一个副本则会增加其他所有进程的负载。
- 如果写操作有足够容量,而大量的读操作正在给系统造成压力,增加副本可能是最好的选择。
需要注意的是,对于一个采取“大多数”法定仲裁过程的系统,增加新的副本有可能反而会降低系统的可用性,例如zookeeper,使用5个副本时,系统最多可以在2个副本,也就是40%不可用的情况下,继续正常工作。当使用6个副本时,仲裁过程需要使用4个副本,系统最多可以在2个副本,也就是33%不可用的情况下,继续正常工作。
如果客户端集中于某个区域,那么最好将系统副本放置在离客户端近的地方。
当考虑将副本放在哪里时,还需要考虑负载均衡机制,以及系统如何应对过载情况。
很多分布式共识系统采用领头人机制来提高性能,然而领头人进程会使用更多的计算资源,尤其是网络容量。
运行高度分片的共识系统的组织可能会发现,保证领头人进程在数据中心之间分布均衡是很有必要的,这样可以保证系统不会由于一个数据中心的出口造成瓶颈。
将共识组置于不同的数据中心中的另外一个劣势是当领头人所处的数据中心出现大规模故障时,整个系统会剧烈变动,所有领头人都应该切换到另外一个数据中心,要么是平均分配,要么会涌入同一个数据中心。在这两种情况下,其它两个数据中心的网络流量会突然增大。
仲裁组的组成
当决定在哪里放置共识组的副本时,另外一个重要因素是要考虑地理位置的分布(更准确地说,副本直接的网络延迟)对性能的影响。
一个做法是将副本分布得最均匀化,使得副本之间的RTT基本类似。然后地理位置的分布可能会给这种做法造成一定困难,尤其是当跨大陆与跨大洋的时候。
如果考虑跨越北美洲和欧洲部署的某个系统,试图平均分布副本是不可能的。系统中永远会有一段延迟高的链路,因为跨大西洋的链路要比跨大洲的链路速度慢。无论如何,在某个区域中的操作要跨越大西洋进行一次共识操作。
然而系统设计者仍然有办法实现更好的性能,例如上图使用5个副本,将其中2个副本放置于美国中心地带,1个放置于东海岸,另外两个放置于欧洲。这样的分布可基本保证共识过程是在北美洲的副本上完成,而不需要等待欧洲的回复。而从欧洲起始的共识过程可以仅仅跟美国东海岸的副本完成。东海岸的副本就像两个可能的仲裁组的关键轴,将两个组连接在了一起。
此时可以发现处于东海岸的副本是分布式共识系统中的一个关键弱点。在这种情况下,层级型仲裁过程可能更有用。如下图所示,9个副本被部署为3组,每组3个。仲裁过程可以由多数组完成,而每个组只有在多数成员可用的情况下才可用。也就是说一个副本可以在中央组中出现故障,而不会对系统整体性能产生影响。
当然,运行更多数量的副本需要更高的成本。在高度分片、大量读操作可以被副本服务填充的系统中,我们可以通过使用更少的共识组来应对这种成本。
对分布式共识系统的监控
分布式共识算法是Google很多关键系统的核心。所有的重要生产系统为了更好地检测故障或者进行故障排除,都需要监控。
经验告诉我们,分布式共识系统的某些区域需要特别关注:
- 每个共识组中的成员数量,以及每个成员的状态(健康或不健康)
- 始终落后的副本;
- 领头人角色变化的次数;
- 共识交易记录数字,系统管理员需要知道目前系统是否正在处理交易,大多数共识算法采用一个递增的共识交易数字来代表目前系统的进度,这个数字在系统健康时应该随着时间不断增长;
- 系统中的提议数量,以及系统中被接受的提议数量;
- 吞吐量和延迟;
- 针对提议接收时间的延迟分布;
- 系统不同部分之间观察到的网络延迟;
- 接收者在持久化日志上花费的时间;
- 系统每秒处理的字节数;
十五、分布式周期性任务系统——Google Cron系统的构建过程
跟踪Cron任务的状态
需要记录关于Cron任务的一些状态信息,并且必须能够在系统发生故障的时候快速恢复。
跟踪任务的状态有两个选项:
- 将数据存储在一个可用度很高的外部分布式存储上;
- 系统内部自行存储一些(很小量)的状态信息;
在Google的分布式Cron系统中,使用的是第2个选项。原因是:
- 分布式文件系统,通常用来存储非常大的文件,小型写操作在分布式文件系统上的开销很高,而且延迟也很高;
- 基础服务的依赖会带来许多副作用,而Cron服务设计为可以独立于下游系统而运行,即使数据中心的一部分出现故障,Cron服务也应该能够持续工作一段时间;
Paxos协议的使用
Google Cron服务部署了多个副本,同时采用Paxos分布式共识算法保证它们状态的一致。各个副本利用Paxos协议同步的最重要的状态信息就是哪些Cron任务已经被启动了。该服务需要不停地以同步方式通知大多数副本每个计划任务的启动和结束信息。
领头人角色和追随者角色
Cron服务使用Paxos协议分配两个角色:领头人和追随者。
领头人角色
- 领头人进程是唯一一个主动启动Cron任务的进程。该领头人进程内部有一个内置调度器,与单机服务crond类似。该调度器按预定的启动时间排序维护一个Cron任务列表。领头人进程在第一个任务的预期执行时间之前一直处于等待状态。
- 当到达预定启动时间时,领头人进程宣布它将要开始启动该Cron任务,同时计算新的启动时间,和普通的crond实现一样。这些启动信息的变更信息必须同步给其他所有的跟随者角色。全部通信都是基于Paxos协议之上完成。
- Paxos通信的同步性是很重要的,Cron任务的实际启动在得到Paxos法定仲裁过程结束之前不会进行。如果这些操作是异步进行的,可能意味着整个任务的启动过程都在领头人进程上完成,而没有通知其它的副本。当遇到故障切换时,新的领头人副本可能会重新进行这次启动,因为它们不知道这个任务已经被启动过一次了。
- 同样的,Cron任务启动过程的完成也需要通过Paxos协议同步通知给其他的副本。要注意的是,这里记录的启动信息仅表示Cron服务在某个时间试图进行一次启动操作,并不关心这次启动是否真正成功了。
- 另外,作为领头人角色非常重要的一点是,当其由于各种原因失去领头人角色时,该进程必须立刻终止一切和数据中心任务分发系统之间的交互。领头人角色必意味着对数据中心级别任务分发服务的独占性。
追随者角色
- 追随者也必须要维护系统中所有Cron任务的列表,这个列表必须在所有副本中保持一致,所有的状态改变都是从领头人角色基于Paxos协议传递的。
- 如果领头人进程崩溃,或者由于某个原因不再正常工作,某个追随者进程将会被选举为新的领头人进程,该选举过程必须要在一分钟内完成,这样可以避免大幅延迟或跳过某个任务的执行。
- 一旦一个新的领头人进程被选举出来,所有的未完成状态的启动过程必须被结束。
解决部分性失败情况
领头人进程和数据中心调度系统之间的单个任务启动过程可能在多个RPC中间失败,我们的系统应该能处理这种状况。
首先,在每个任务启动过程中都有两个同步点:
那么要判断启动任务相关的RPC是否成功发送,至少要满足下面条件之一:
- 所有需要在选举过后继续的,对外部系统的操作必须是幂等的(这样我们可以在领头人选举过后重新进行该操作);
- 必须能够通过查询外部系统状态来无疑义地决定某项操作是否已经成功;
在实际的实现中,实现者可能要在双重启动风险和错过启动的风险中进行抉择。
保存状态
使用Paxos协议来达成共识只是状态问题的一部分。Paxos协议基本上是一个只能新增的日志,在每次状态变化后同步地新增。这就意味着:
- 日志需要定期压缩,以防无限增长;
- 日志必须要存储在某个地方;
一种处理办法是可以简单地将目前的状态进行一次快照。在日志丢失的情况下,只会丢失上次快照之后的信息。
我们将Paxos日志存储在服务副本的本地磁盘上,同时我们也将快照信息保存在本地磁盘上,这些快照信息也会备份到另一个分布式存储上去。不将日志直接存储在分布式文件系统上,主要是考虑到大量小文件的写操作会引起很严重的性能问题。
运维大型Cron系统
一定要小心任何大型分布式系统都存在的问题:惊群效应。
当人们想要配置一个每日任务时,通常会配置该任务在午夜时运行,当你的任务会在数千个主机上执行时就不行了,尤其是当有其他30个团队也按同样的配置来运行每日任务呢。
Google的解决办法是对crontab格式做了一些扩展,增加了一个问号“?”表示任意一个时间值都可以接受。Cron系统则可以根据需求自行选择一个时间执行,尽可能将这些任务分散得更均匀。
虽然如此,但由Cron任务带来的系统负载仍然是非常尖锐的。
十六、数据处理流水线
数据处理流水线
- 通过一个程序读取输入,执行某种模式变换,然后输出新的数据,一般还会有类似于Cron的周期性的调度程序进行控制。
- 对大数据进行周期性的或者是持续性的变形操作的程序通常被称为simple, one-phase pipeline。由于大数据与生俱来的海量级别和处理的复杂性,这种程序通常会被串联起来执行,一个程序的输出作为另一个程序的输入。我们将这样构建的程序称为multiphase pipeline 。
- 一条数据流水线中串联的程序数量多少称为该流水线的深度depth,一个很深的流水线可能包含几百个程序。
周期性流水线模式的挑战
周期性流水线在工作进程数量可以满足数据量的要求,以及计算容量足够执行需要时,相对比较稳定。因为周期性流水线非常实用,所以Google为些还编写了一系列程序框架,如MapReduce和Flume。
然后SRE的实践经验表明,周期性的数据流水线是非常脆弱易坏的,整个流水线的稳定性会随着数据量的自然增长等种种变化而出现各种问题,像是任务运行超时、资源耗尽、某些分块处理卡住导致整体运维压力上升等。
工作分发不均造成的问题
处理大数据的一个关键思想是利用embarrassingly parallel算法将巨大的工作集切割为一个个可以装载在单独的物理机器上的小块。
- 但有时候工作分块所需要的处理资源是不等的,例如在一个按客户分块的工作集中,某些客户的工作分块可能会比其他的都大。由于单个客户已经是系统的最小分块单元,整个数据流水线的运行时间就至少等于处理最大的客户的工作分块所需的时间。
- 卡住的工作分块,一般是由于资源在集群中分配不均衡,或是资源分配过量导致的。也可能是由于某些实时性操作在流式数据上实现的困难性导致的,如排序式传输的数据。整个流水线处理完毕要依赖于其中性能最差的分块的完成。
- 当集群服务监控系统或工程师发现了以上问题后,他们采取的一些处理措施经常会让事情变得更糟,例如将整个任务杀掉,使整个任务重启,所有的工作分块都需要重新进行计算,这样前一次运行消耗的时间、CPU以及人力成本就全都浪费了。
分布式环境中周期性数据流水线的缺点
因为使用广泛,Google的集群管理解决方案里包括了针对这种数据流水线的一套调度机制。
因为周期性任务经常是作为低优先级批处理任务出现的,针对它们单独进行调度很有必要。Borg系统会综合成本、机器负载,将批处理任务指派给可用的机器去处理。但这种低优先级可能会造成启动很慢,且容易产生被高优先级任务挤占的风险。对于一个每天运行一次的流水线来说,这并不成为问题,几个小时的延迟都是可以承受的。随着执行频率的提高,问题会接踵而至。
以上问题的解决办法是为正常运行提供足够的服务容量。
监控周期性流水线的问题
因为周期性的流水线不会持久性的运行,所以存在获取实时监控数据的难度。但实时监控整个流水线的全局运行指标与运行时性能指标对运维很重要。
惊群效应
对一个足够大的周期性流水线来说,每一次运行,可能有几千个工作进程立即启动。如果一个服务器上有太多的工作进程,或者这些工作进程配置错误,并用错误的方法重试,可能会导致服务器的过载,甚至于分布式集群服务也会过载,网络基础设施等也会出现问题。
人工干预也有可能使问题变得更严重,经验不足的工程师采取的错误处理等。
Google Workflow
Workflow使用领头人-追随者的分布式系统设计模式,以及流式系统设计模式(system prevalence),这样的组合可以实现超大规模的交易性数据流。同时提供保障“仅运行一次”的语义来保障正确性。
根据流式系统的工作原理,Workflow可以被认为是分布式系统中与用户界面设计中的MVC模式相同的一种模式。
- 模型由一个被称为“主任务”的进程持有。
- 主任务使用流式系统模式将所有的任务状态保存在内存中,同时同步地将每一次修改以日志方式记录在持久化磁盘上。
- 视图是那些作为整个流水线子组件,不断向主任务更新它们所见的系统状态的工作进程。
- 为了获取更好的性能,主任务通常只会保存具体工作的指针,而真正的输入和输出数据会保存在一个常见的文件系统或其他存储系统中。
- 工作进程是完全无状态的,可以任一时间被抛弃。
- 控制器是可选的,可以加入进来支持一系列的辅助活动,如对流水线的实时伸缩,对状态的快照,以及工作周期的管理,回滚流水线的状态,甚至于是在紧急时刻负责停止一切系统行为。
Workflow中的执行阶段
- 我们可以将工作进程进一步划分为更小的任务组,而将流水线的深度随意增加。
- 每个任务组负责处理该执行阶段的数据,可以对某一小块数据进行任意操作。
- 工作进程处理前序阶段产生的工作单元,同时生产出新的输出单元。输出可以终止整个执行,也可以作为其他处理阶段的输入。
- 在系统中,我们可以很容易地保证所有的工作都被精确地执行了一次且仅一次。
Workflow的正确性保障
- 配置文件本身作为屏障,这样可以保障工作进程的输出永远与配置一致;
- 所有的工作结果都必须由当前拥有租约的进程提交;
- 工作进程的输出文件都是全局唯一命名的;
- 客户端和服务器会在每次操作的时候校验“主任务”的令牌;
Workflow系统特殊的地方在于每个任务都是独特的、不可变的。这样的两个特性使用得Workflow系统免于许多大型任务分发系统面临的困难。
十七、数据完整性:读写一致
数据完整性意味着用户可以维护对服务的访问,用户对数据的访问能力是最重要的,所以这种访问能力的完整性非常重要。
为提供更高的数据完整性策略,大多数的应用都是在优化以下5项的某种组合:
- 在线时间
- 延迟
- 规模,某个服务的用户数量
- 创新速度
- 隐私,在这里仅将隐私的定义限制为仅仅针对数据删除:用户删除掉服务的数据后,数据必须在合理的时间内被真正摧毁。
很多云应用都是基于ACID和BASE某种组合的API上不停地演变来满足上述5个要求的。
备份与存档
- 备份,是可以直接被应用程序重新加载的数据;
- 存档,是将数据长时间安全保存,以满足审核、取证和合规要求;
针对云平台环境下的备份与恢复使用需求
为了保证扩展性,每个服务商都需要提供一定数量的API,这些API需要支持以下特性:
- 数据本地性和缓存
- 本地和全局的数据分布
- 强一致性与/或最终一致性
- 数据持久性、备份与灾难恢复
如果云服务商没有预先解决这些问题,那么应用程序就必须要自己识别和解决。
保障数据完整性和可用性:Google SRE的目标
- 数据完整性是手段,数据可用性是目标,从用户的角度看,仅仅保障数据完整性而没有保障数据的可用性是没有意义的;
- 交付一个恢复系统,而非备份系统,按系统可用性要求去定义和维持一个合理的必要的可用性SLO;
- 造成数据丢失的事故类型基本上是由3种因素的24种组合:
- 根源问题,某种无法恢复的用户数据丢失是由这几个因素造成的:用户行为、管理员的错误、应用程序的bug、基础设施中的问题、硬件故障和部署区的大型事故
- 影响范围,大规模、小范围
- 发生速度,迅速、缓慢
有效的故障恢复计划必须覆盖所有这些因素的全部有效组合。
Google 据实践总结出,最常见的用户可见数据丢失场景是由于数据删除和软件bug造成的引用完整性问题。其中最困难的场景是,在数周、甚至数月后才在生产环境中发现软件bug造成了长时间的数据损坏或丢失。
分级备份策略
- 昂贵的本地快照可以进行快速恢复,所以我们可以几小时进行一次快照,然后将它们保留数天时间;
- 完整备份和增量备份可能每两天进行一次,保存更长时间;
- 在使用云计算API之前,一定要先考虑该API可选的数据恢复能力;
- 第一级备份是那些备份频率很高,且可以快速恢复的服务;
- 第二级备份的频率较低,只保留一位数或两位数字 的天数,保存在当前部署点的随机读写分布式文件系统上;
- 第三级备份会使用冷存储,如离线的磁盘或磁带;
Google SRE保障数据完整性的手段
- 第一层:软删除
- 应用中实现一个回收站机制,作为用户错误的主要防护手段;
- 软删除机制是针对开发者错误的主要防范手段,以及用户错误的次要防范手段;
- 在面向开发者的服务中,懒删除机制是针对内部开发者的错误的主要防范手段,是针对外部开发者错误的次要防范手段;
- 第二层:备份和相关的恢复方法
- 备份不重要,恢复才重要,对恢复提供支持是主导备份系统 设计的关键;
- 在设计备份还原系统时,必须考虑:
-
- 使用哪种备份和还原方法;
- 通过全量或者增量备份建立恢复点的频率;
- 备份的存储位置;
- 备份的保留时间;
- 复制机制,那些保存了备份的存储系统都应该具有复制机制,以避免数据丢失。
- 存储更多数据没有那么简单,很多常规技术在处理例如700PB的数据存储时就变得不可用了。
-
- 处理海量数据最有效的方式是给数据建立 一个可信点,也就是一部分数据校验过之后,由于时间等原因变成不可变数据了,这样下次可以仅针对增量数据进行校验。
- 另一个方法是利用分布式计算,将数据合理分片、保证每个分片之间的独立 性,N个任务并进进行数据复制或校验任务。
- 第三层:早期预警,越早检测到数据丢失,数据的恢复就越容易,也越完整。
- 带外数据检验系统,大部分情况下被实现成一系列MapReduce任务或是Hadoop任务;
- 开发一套数据检验流水线可能会在短期内降低业务功能开发的速度,但会在更长时间内保障其他业务开发可以进行得更快,容易造成数据损坏的bug没有那么容易流入生产环境中;
- 大规模部署带外检测器可能成本较高,所以需要保证每天运行的校验器是持续寻找那些毁灭性问题,以便满足延迟和成本的要求;
- 一个有效的带外数据校验系统需要下列元素:
-
- 检验任务的管理;
- 监控、报警和监控台页面;
- 限速功能;
- 调试排错工具;
- 生产应急应对手册;
- 校验器容易使用的数据检验API;
- 确保数据恢复策略可以正常工作
- 要持续对数据恢复流程进行自动化的测试,要把该测试作为正常运维操作的一部分;
- 当数据恢复流程无法完成时,要自动发送报警;
- 数据恢复计划需要覆盖以下各点:
-
- 备份数据是否完整、正确?
- 是否有足够的资源完成整个恢复过程?
- 整个数据恢复过程能否在合理的时间内完成?
- 是否在数据恢复过程中监控状态信息?
- 恢复过程是否依赖于某些无法控制的元素?
SRE的基本理念在数据完整性上的应用
- 保持初学者的心态,大规模部署的、复杂的服务中会产生很多无法完全被理解的bug,永远不要认为自己对系统已经足够了解,也不要轻易将某些场景定性为不可能。我们可以通过“信任仍要验证”、“纵深防御”等手段来保护自己。
- 信任但要验证,如部署使用带外校验系统。
- 不要一厢情愿
- 不经常使用的系统组件一定会在你最需要的时候出现故障;
- 数据恢复计划必须通过经常性的演习来保障可用性;
- 由于人类天生不适合持续性、重复性地活动,自动化手段是必备的;
- 纵深防御,最好的数据完整性保障手段一定是多层的、多个保障手段彼此覆盖,能够用合理的成本来覆盖非常广泛的失败场景。
十八章、可靠地进行产品的大规模发布
发布协调小组与发布检查列表
Google通过建立一个SRE内部的专职顾问团队来指导新产品和新功能的发布与迭代工作,以达到最小化故障和最大化产品性能的指标,这个团队被称为发布协调小组。这个团队中的工程师被称为发布协调工程师(Launch Coordination Engineering, LCE)。
LCE团队使用以下几个办法确保发布流程平稳:
- 审核新产品和内部服务,确保它们和Google的可靠性标准以及最佳实践一致,同时提供一些具体的建议来提升可靠性;
- 在发布过程中作为多个团队之间的联系人;
- 跟进发布所需任务的进度,负责发布过程中所有技术相关的问题;
- 作为整个发布过程中的一个守门人,决定某项发布是否是安全的;
- 针对Google的最佳实践和各项服务的集成来培训开发者,充分利用内部的文档和培训资源来加速开发速度;
LCE团队会在整个服务生命周期的不同阶段进行
审核(Audit),大部分审核工作是在新产品或新服务发布之时进行的。
LCE工程师的角色职责
发布协调团队由直接招聘的工程师和其他有经验的SRE组成。除了对SRE技术的要求之外,要求成员有很强的沟通和领导能力,可以将分散的团队聚合在一起达成一个共同的目标。同时还需要偶尔处理冲突问题,并且为其它工程师提供建议和指导。
专职的发布协调小组提供了以下优势:
- 广泛的经验,作为一个真正的跨产品小组,LCE成员会参与到全公司的产品线活动中去,丰富的跨产品知识与很多团队的良好关系使得LCE团队是公司中最适合进行知识共享的媒介。
- 跨职能的视角,LCE对发布过程具有整体视角,这使得该团队可以协调多个分散的小组,包括SRE、研发团队和产品团队等。这种全局视角对复杂发布流程来说更重要,在Google这些发布通常需要协调七八个不同时区的团队共同完成。
- 客观性,作为一个中立的建议方,LCE在SRE、产品研发组、产品经理和市场运营团队之间起到了平衡与协调的作用。
- LCE也非常注重可靠性,因为其本身也是SRE组织的一部分。
建立发布流程
在Google 10几年的发布实践中,发现一些好的发布流程所具有的一些共性特征:
- 轻量级,占用很少的开发时间。
- 鲁棒性,能够最大限度地避免简单错误。
- 完整性,完整地、一致地在各个环节内跟踪重要的细节问题。
- 可扩展性,可以应用在很多简单的发布上,也可以用在复杂的发布过程中。
- 适应性,可以适用于大多数常见的发布,以及可以适用全新的发布类型。
为了达成以上特性,需要有意采用几个手段:
- 简化,只确保基本信息正确,不为所有可能性做准备。
- 高度定制,允许有经验的工程师针对每次发布定制自己的流程。
- 保证通用路径能快速完成,识别出几类发布流程所具有的共同模式,为其提供一个快速简化通道。
LCE必须不断地优化整个发布的体验,确保不会过于繁琐而让有的工程师有意去绕过流程,在成本与收益上保持平衡。
发布检查列表
发布检查列表是用来减少失败的重要手段,并且可以在多个职能部门之间保证一致性和完整性。常见的例子如民航飞机起飞前的检查列表、手术之前的检查列表。同样地,LCE使用发布检查列表来评估每次发布过程。
- 该发布列表可以帮助LCE工程师评估发布过程,并且给发布团队提供具体的待办事项,以及更多信息的链接。
- 发布检查列表中的问题必须是非常重要的、精挑细选的,理想情况下要求都必须有之前发布的经验教训来证明。
- 向列表中增加新的问题必须经过公司副总的批准,每一年或半年需要对列表内容的有效性进行维护。
- LCE工程师基于检查列表问题发给的指令必须非常具体、可行,开发者可以在合理的时间内完成。
例如:
- 问题:是否需要一个新的域名?
-
- 待办事项:与市场部门协调想要的域名,并且去申请注册。通过此链接XXX向市场部门提申请。
- 问题:是否存储持久化信息?
-
- 待办事项:确保实现了备份,通过此链接XXX了解备份实现的细节问题。
- 问题:该服务是否有可能被某个用户滥用?
-
- 待办事项:在服务中实现限速和用户配额管理,通过此链接XXX了解有关的共享服务。
以下是Google在2005年使用的一个发布检查列表的版本样例:
以下为中文版本:发布协调检查列表
架构
- 架构草图,服务器类型,客户端请求类型
- 编程性客户端的请求
物理机与数据中心
- 物理机数量与带宽数量,数据中心,N+2冗余,网络QoS
- 新的域名,DNS负载均衡
流量预估、容量以及性能
- HTTP流量与带宽预估,发布时的峰值,流量的组成,6个月的预测
- 压力测试,端到端测试,每个数据中心最高延迟下的容量
- 对其他我们关注的服务的影响
- 存储容量
系统可靠性与灾难恢复
- 当下列情况发生时,服务会怎么样:
-
- 物理机故障,机柜故障,集群故障
- 两个数据中心之间的网络故障
- 对每种需要联系其他服务器(后端)的服务器来说:
-
- 如何检测后端故障,后端故障如何处理
- 如何不在影响客户端和用户的情况下重启服务器
- 负载均衡,速度限制,超时,重试,以及错误处理
- 数据备份/恢复,灾难恢复
监控与服务器管理
- 监控内部状态,监控端到端行为,警报的管理
- 监控监控系统
- 有关财务的警报和日志
- 在集群环境下运行服务的技巧
- 不要在代码中给自己发送海量邮件,会导致邮件服务器崩溃
安全
- 安全设计评审,安全代码评审,垃圾邮件风险,验证,SSL
- 发布之前的可见/可访问性控制,各种类型的黑名单
自动化与人工任务
- 更新服务器、数据、配置文件的方式和变更管理
- 发布流程,可重复的构建过程,金丝雀测试,分阶段发布
增长问题
- 空余容量,10倍增长,增长型的警报
- 扩展性的瓶颈,线性扩展,与硬件性能的同步扩展,所需要的变更
- 缓存,数据分片/重新分片
外部依赖
- 第三方系统,监控,网络条件,流量配比,发布时的流量峰值
- 优雅降级,如何避免意外对第三方服务造成过载
- 与合作伙伴、邮件系统,以及Google内部服务良好对接
发布时间与发布计划
- 不可改变的截止日期,外部事件,星期一或者星期五
- 该服务标准的运维流程,以及其他服务的运维流程
推动融合和简化
- 将常见的功能整合于一套通用的基础设施类库可以避免反复的重造轮子。同时也让这些基础设施有足够的关注,以不断提高它们的工程质量和服务质量。
- 常见的基础设施服务包括限速功能和用户配额、服务器数据推送功能、新版本发布功能等,经过多年的优化和加固,这些基础设施可以帮助消除容量、性能和扩展性方面的不确定性。
- LCE会推荐现有的基础设施作为服务的基础构建单元,这些标准化的服务单元可以大幅简化发布检查列表,例如关于限速功能要求的长篇大论可以简化为一句话:“利用系统X实现限速功能”。
发布未知的产品
- 当进入某个新的产品空间或垂直领域的时候,LCE可能需要创建一个全新的检查列表,而这通常还要引入相关的领域专家。
- 当起草新的检查列表时,应该重点关注一些宽泛的主题,如:可靠性、故障模式和流程等。
起草一个发布检查列表的方法与注意事项
具体的检查列表细节每个公司都会不同,因为这些具体事项必须跟公司内部的服务和基础设施相关。下面以Google LCE检查列表中的一些主题为例,描述一些注意事项。
架构与依赖
针对系统架构的评审可以确定该服务是否正确地使用了通用基础设施,并且确保这些基础设施的负责人加入到发布流程中来。Google拥有很多内部服务,它们经常作为新产品的构建组件。在接下来的容量规划中,依赖列表可以用来保证该服务的相关依赖都有足够的容量。
示范问题:
-
- 从用户到前端再到后端,请求流的顺序是什么样的?
- 是否存在不同延迟要求的请求类型?
待为事项:
-
- 将非用户请求与用户请求进行隔离。
- 确认预计的请求数量。单个页面请求可能会造成后端多个请求。
集成
很多公司的对外服务都运行在一个内部生态系统中,经常包括自己的特质与陷阱,所以以下这些内容在每个公司的实现都会不同:
- 给服务建立一个新的DNS
- 为服务配置负载均衡系统
- 为服务配置监控系统
容量规划
容量规划与冗余度和可用性都有直接关系,如果我们需要3个相互复制的部署点来服务100%的峰值流量,那就需要维护4个或5个部署点,这其中包括冗余节点。数据中心和网络资源通常需要很长的准备时间,需要足够提前申请才能获取到。
示范问题:
-
- 本次发布是否与某种类型的推广活动有关?
- 发布过程中和之后预计的流量和增速是多少?
- 是否已经获取到该服务需要的全部计算资源?
故障模式
针对新服务进行系统性的故障模式分析可以确保发布时服务的可靠性。在检查列表的这一部分中,我们可以检查每个组件以及每个组件的依赖组件来确定当它们发生故障时的影响范围。
- 该服务是否能够承受单独物理机故障?
- 单数据中心故障?
- 单网络设备故障?
- 网络故障?
- 如何应对无效输入,是否有针对DoS的保护?
- 如果某个依赖组件发生故障,该服务是否能够在降级模式下继续工作?
- 该服务在启动时能否应对某个依赖组件不可用的情况?
- 在运行时能否处理依赖不可用和自动恢复情况?
示范问题:
-
- 系统设计中是否包括单点故障源?
- 该服务是如何处理依赖系统的不可用性的?
待办事项:
-
- 为请求设置截止时间,防止由于请求持续时间过长导致资源耗尽。
- 加入负载丢弃功能,在过载情况中可以尽早开始丢弃新请求。
客户端行为
在传统网站中,很少需要将用户的合理滥用行为考虑进来,因为每条请求都是由于用户行为触发的,请求的速率会受到用户单击速度的限制 。双倍的负载需要双倍的用户来产生。
但这个原则当考虑到某些客户端是在没有用户输入的情况下,执行操作的时候就不适用了,例如手机客户端APP周期性地将数据同步到云端,或者使用了自动刷新功能的网站等。
在这几种场景中,客户端的滥用行为很容易影响到服务的稳定性。
示范问题:
待办事项:
-
- 确保客户端在请求失败之后按指数型增加重试延时。
- 保证在自动请求中实现随机延时抖动。
流程与自动化
Google鼓励工程师们使用标准工具来自动化一些常见流程,然而自动化不是完美的,一些需要人工执行的步骤总是不可避免。
- 为了保障可靠性,我们应该尽量减少流程中的单点故障源,包括人在内。
- 这些自动化之外的流程步骤,应该在发布之前文档化,确保工程师还记得各种细节的时候就完全转移到文档中,这样才能在紧急情况下派上用场。
- 流程文档应该做到能使任何一个团队成员都可以在紧急事故中处理问题。
示范问题:
待办事项:
-
- 将所有需要手动执行的流程文档化。
- 将迁移到另外一个数据中心的流程文档化。
- 将构建和发布新版本的流程自动化。
开发流程
- Google是版本控制系统的重度用户,几乎所有的开发流程都和版本控制系统深度整合。很多最佳实践的内容都围绕着如何有效使用版本控制系统而展开。例如,我们大部分的开发都是在主线分支上进行的,但是发布版本是在每个发布的分支上进行的。这种方式使用得在分支上修复每次发布的bug更简单。
- Google还利用版本系统做一些其他的事情,例如存放配置文件等。版本控制系统具有很多优势,如跟踪历史、修改记录以及代码审核,这些也都适用于配置文件。在某些案例中我们也会自动将版本配置系统中的配置文件推送到生产环境中。工程师只要提交一个修改请求就可以自动发布到线上。
待办事项:
-
- 将所有代码和配置文件都存放到版本控制系统中。
- 为每个发布版本创建一个新的发布分支。
外部依赖
有时候某个发布过程依赖于某个不受公司控制的因素,尽早确认这些因素的存在可以使我们为它们的不确定性做好准备。
例如,服务依赖于第三方维护的一个类库,或者另外一个公司提供的服务或数据。当第三方提供商出现故障、Bug、系统性错误、安全问题,或者未预料到的扩展性问题时,尽早计划可以使我们有办法避免影响到直接用户。
在Google产品发布的历史上,我们曾经使用过过滤/重新代理服务、数据编码流水线以及缓存等机制来应对这些问题。
示范问题:
-
- 这次发布依赖哪些第三方代码,数据,服务,或者事件?
- 是否有任何合作伙伴依赖于你的服务?发布时是否需要通知他们?
- 当我们或者第三方提供商无法在指定截止日期前完成工作时,会发生什么?
发布计划
在大型分布式系统中,很少有能瞬间完成的事件。即使能做到,为了保障可靠性,这样快速发布也并不是一个好主意。
- 复杂的发布过程需要做很多的准备工作。
- 备用方案是发布计划的另一个方面。
待办事项:
-
- 为该服务发布制定一个发布计划,将其中每一项任务对应到具体的人。
- 针对发布计划中的每一步分析危险性,并制定对应的备用方案。
可靠发布所需要的方法论
Google在多年稳定运行系统中研发了一系列方法论,其中的某个方法论非常适用于安全发布产品。这些方法论可为你在日常运维中提供很多优势,但是在发布阶段就实践它们也是很重要的。
1、灰度和阶段性发布
根据某个事先定义的流程,几乎所有的Google服务的更新都是灰度进行的,在整个过程中还穿插一些校验步骤。新的服务可能会在某个数据中心的几台机器上安装,并且被严格监控一段时间。如果没有发现异常,服务则会在某个数据中心的所有机器上安装,再次监控,最后再安装到全球全部的服务器上。
- 发布的第一阶段通常被称为“金丝雀”(起源来早期煤矿工人带金丝雀下井检测有毒气体)。
- 金丝雀测试是嵌入到很多Google内部自动化工具中的一个核心理念,包括那些修改配置文件的工具。负责安装新软件的工具通常都会对新启动的程序监控一段时间,保证服务没有崩溃或者返回异常。如果在校验期间出现问题,系统会自动回退。
2、功能开关框架
一些Google产品中加入了功能开关框架,这些框架被设计为将新功能逐渐发布给0%~100%的用户。每当用户增加这个框架时,该框架都会被仔细调优,以便未来大部分的功能上线时不需要LCE再参与。这种框架通常需要满足以下几个要求:
- 可以同时发布多个改动,每个改动仅针对一部分服务器、用户、实体,或者数据中心起作用。
- 灰度式发布到一定数量的用户,一般在1%~10%之间。
- 将流量根据用户、会话、对象和位置等信息发送到不同的服务器上。
- 设计中可以自动应对新代码出现的问题,不会影响到用户。
- 在严重Bug发生,或者其他副作用场景下可以迅速单独屏蔽某个改变。
- 可度量每个改变对用户体验的提升。
Google的开关功能框架主要有两类:
- 主要面向用户界面修改的。
- 可以支持任意服务器端和逻辑修改的。
对用户界面修改来说,最简单的功能开关框架是一个无状态的HTTP重写器,运行在前端服务器之前,只对某些Cookie起作用,或者是其他的某种HTTP请求/回复的属性。某个配置机制可以将新代码和一个标识符关联起来,同时实现某种黑名单和白名单机制(例如Cookie哈希取模之后的某个范围)。
有状态服务一般会将功能开关限制为某个已登录用户的标识符,或者某个产品内被访问的实例,例如文件ID、某个表格、存储物件等。有状态的服务一般不会重写HTTP请求,而是通过代理或者根据需求转发到其他服务器上的手段来测试复杂功能或者新的业务逻辑。
3、应对客户端滥用行为
- 最简单的客户端滥用行为就是某个更新间隔的设置问题,一个60s同步一次的新客户端会比600s同步一次的旧客户端造成10倍的负载。
- 重试逻辑也有一些常见问题会影响到用户触发的行为,或者客户端自动触发的行为。一般需要增加指数型增长的重试延迟。同时仔细考虑哪些错误值得重试,例如4xx HTTP错误就不应该重试。
- 有意或无意的自动请求的同步性会造成惊群效应,这是另外一个常见的滥用行为的例子。某个APP认为夜里2点是下载更新的好时候,结果造成夜里2点时有大量请求发往下载服务器,而其它时间没什么请求。这种情况,每个客户端应该引入一定随机性。
- 服务器端控制客户端行为的能力也是一个重要工具。将新功能对应的代码在激活之前提前发布到客户端,通过加入这种功能,使得在问题出现时,中止发布更容易。我们可以简单地将该功能关闭,修复代码,再发布新版APP。
4、过载行为和压力测试
过载行为是一个复杂的故障模式。有以下几种引发过载的因素:
- 产品意料之外的成功,经常是某个服务发布时造成过载的最常见因素 。
- 负载均衡问题。
- 物理机故障。
- 同步客户端行为。
- 外界的攻击。
从理论上很难预测某个服务的过载反应,因此压力测试是非常重要的,不管是从可靠性角度还是容量规划角度,压力测试对大多数发布来说都很重要。