云原生模式
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 解决办法

在进入21世纪后成长起来的新一代公司,已经找到了更好的做事方法。谷歌公司是一个伟大的创新者,它和其他一些互联网巨头一起,发展出了新的IT模式。据估计,谷歌在其世界各地的数据中心里运行着200万台服务器,如果使用我之前描述的技术,谷歌不可能做到这一点。一定存在着另外一种不同的方式。

图2.4展示了一个系统的示意图,它几乎与前一节描述的“坏”系统相反。

图2.4 围绕这4个因素打造一个有效、可预测、稳定的系统

这个系统的目标如下:

■ 简单且可以频繁地在生产环境中进行发布。

■ 运维具有稳定性和可预测性。

你应该已经熟悉了这些因素的反面因素:

■ 碎片化的变化会造成发布缓慢和不稳定,可重复性则相反。

■ 有风险的部署会导致生产环境的不稳定及部署困难,安全部署则会带来敏捷性和稳定性。

■ 将依赖于不变环境的实践和软件设计,替换成为环境不断发生变化做好准备的,可以从根本上减少用于“救火”的时间。

在图2.4中,你会注意到一个名为“持续交付”(Continuous delivery,CD)的新实体。那些在新的IT运维模型上获得成功的公司,已经重新设计了它们的整个软件开发生命周期流程,并将持续交付作为主要的驱动力量。这样可以显著简化部署过程,并为整个系统带来很多好处。

在本节中,我将首先解释什么是持续交付,以及软件开发生命周期中的变化如何催生了持续交付,以及持续交付所带来的好处。然后,回到另外3个关键因素,详细介绍它们的主要特性和优点。

2.2.1 持续交付

亚马逊可能是频繁发布方面的极端例子。据说平均每一秒amazon.com就会发布一次代码到生产环境中。你可能会质疑在自己的业务中是否需要进行如此频繁的发布。当然,你可能不需要每天进行86000次发布,但是频繁发布能够给业务带来更好的敏捷性和可实施性,这两者都是衡量一个企业是否强大的标志。

在我们定义什么是持续交付之前,先了解一下什么不是持续交付。持续交付并不意味着将每次的代码更改都部署到生产环境中。它意味着可以在任何时候部署尽可能新的软件版本。开发团队会不断地向软件中添加新的功能,但是每添加一个功能,它们就会进行一个完整的测试流程(自动化的!),并将发布代码打包,从而确保软件已经准备就绪。

图2.5演示了这个测试流程。请注意,在每个流程的“测试”阶段之后没有“打包”阶段。因为打包和部署已经直接包含在了开发和测试过程中。

图2.5 每个开发/测试流程都不会进行部署,但每个流程都会生成可以交付的软件。之后,是否部署就看商业决策了

图2.5所示的流程与图2.6所示的传统软件开发实践形成了对比。在传统的软件开发过程中,因为前期进行了大量功能的开发工作,所以一个单流程所用的时间会长得多。在开发完一组预先确定的新功能之后,需要进行一个时间很长的测试环节,并为发布软件做好准备。

图2.6 传统的软件交付生命周期,在创建可以发布到生产环境中的构件之前,会提前进行大量的开发工作和一个长时间的测试流程

让我们假设图2.5和图2.6所示流程覆盖的时间跨度是相同的,每个流程从左边开始,“准备部署”阶段在最右边。如果你单独观察最右边的时间点,可能看不出有什么不同,大致相同的功能会在大致相同的时间点交付。但如果你深入研究,会发现两者之间具有显著的差异。

首先,在图2.5中,何时进行下一次软件发布依赖于商业决策,而不是由一个复杂、不可预测的软件开发过程决定的。例如,假设你了解到一个竞争对手计划在两周内发布一个与你的新产品类似的产品,公司因此决定立即发布自己的产品。你的业务部门说:“让我们发布吧!”如图2.7所示,将这一时间点叠加到前两张图上,就会看出显著不同。

如果你使用的软件开发方式支持持续交付,那么可以立即发布软件的第三个迭代版本(图2.7的上图中以斜体字显示的时间点)。虽然计划的所有功能还没有开发完毕,但第一个上市的产品(只含有一些重要功能)会有很强的竞争优势。再看看图2.7的下半部分,你会发现对应的企业非常不幸,IT流程成为拦路虎,而不是赋能者,从而让竞争对手的产品率先打入市场!

这种迭代式的流程还提供了另一个重要的好处。当可以将“准备部署”的软件版本经常提供给客户时,你就有机会收集反馈,并用来改进产品的后续版本。你必须重视在早期迭代之后收集的反馈,它可用来纠正之前的错误假设,甚至可用来在随后的迭代中完全改变方向。我见过许多Scrum项目失败,因为它们严格遵循项目开始时定下的计划,不允许根据早期迭代的反馈结果来改变那些计划。

图2.7 持续交付特性允许由业务部门(而不是IT部门)来决定何时交付软件

最后,不得不承认,我们不擅长估算构建软件所需的时间。一部分原因是因为我们天生乐观。我们通常会对正常的情况进行规划,即编写第一行代码之后就可以正常运行。(当我这样说的时候,你马上就意识到了它的荒谬之处,对吧?)我们还会假设自己会完全专注于手头的任务,我们每一整天都在编写代码,直到完成任务。事实上,我们可能会迫于压力,在市场需求或其他因素的驱使下,同意更加激进的时间计划,导致通常在我们开始之前就已经落后于时间计划了。

意想不到的挑战总是会出现。可能你会低估网络延迟对一部分功能的影响。而且,与计划中简单的请求/响应通信协议相反,你需要实现一个更复杂的异步通信协议。或者当你正在开发新功能时,还需要抽身去支持已发布软件的版本升级。你的计划目标几乎永远不可能在一个已经很紧张的时间安排中完成。

对于传统的开发流程,这些因素会让你错过原计划的发布时间点。图2.8的第一行描绘了理想中的软件发布计划,第二行显示了实际花费在开发上的时间(比计划的要长),最后两行显示了你可以做出的选择。一个选择是坚持计划的发布时间点,为此而压缩测试阶段的时间(打包阶段的时间通常无法压缩),代价当然是牺牲软件的质量。另一个选择是保持质量标准并推迟发布日期。这两种选择都不令人愉快。

图2.8 当开发进度拖延后,你必须在两个都不让人满意的选择中挑选一个

我们将“未预期的”开发延迟与更短的迭代流程进行一下对比。如图2.9所示,你会看到计划的发布时间点包含6次迭代。当实际开发时间比预期长时,你会看到出现了一些新选择。你可以选择按计划发布一个功能有限的版本(选择1),也可以选择稍微延期或者延期更长到下一个版本(选择2和选择3)。关键在于,这样企业面临的选择要灵活得多,也更容易让人接受。通过本节中介绍的系统,当你可以低风险、高频率地部署时,就可以快速、连续地完成这两个版本。

图2.9 为持续交付而设计的更短的迭代流程,既能够让发布流程变得更敏捷,又可以保证软件的质量

总的来说,漫长的发布流程会给软件交付过程带来巨大的风险,业务部门缺少控制何时将产品发布到市场的能力,而企业则常常处于既要面对短期市场压力,又要实现提高软件质量、增强软件功能这一长期目标的两难境地。

注意 快速迭代为系统释放了大量的压力。持续交付的出现,使得业务部门可以决定如何以及何时将产品推向市场。

我用了相当长的篇幅来讨论持续交付,因为它确实是一个新的、功能强大的软件开发及运维方式的核心。如果你的企业还没有拥抱持续交付,那么你应该努力推动这件事情。如果没有这些新的方法,你将软件推向市场的能力就会受到阻碍,甚至你所构建的软件架构,也都与这些实践方式有微妙和直接的联系。软件架构是本书的主题,我们将围绕此话题进行深入讨论。

现在让我们回到图2.4,了解其他因素如何支持我们的运维目标(简单及频繁地发布、软件的稳定性)。

2.2.2 可重复性

在前面,我们讨论了碎片化的变化会对软件开发造成的不利影响。因为必须不断适应各个部署环境之间的差异,以及部署构件之间的差异,所以部署过程变得非常困难。这些不一致使系统在生产环境中保持正常运行变得非常困难,因为不管是环境还是软件的某个功能发生变化,都必须进行特殊处理。如果你无法可靠地重现故障时的环境和配置,系统的稳定性就会一直无法保证。

如果你想改变这种情况,关键是实现可重复性。以汽车装配流水线的生产过程为例,每次你把方向盘安装到汽车上,都在重复同样的过程,如果某些参数相同(稍后会对此进行详细说明),并且执行的过程也相同,那么结果就是可预测的。

可重复性会带来两个巨大的好处:有助于系统部署和保证系统的稳定性。正如在前面所说的,迭代流程对于频繁发布来说是必不可少的,并且通过在每次开发/测试的迭代过程中控制变化风险,可以缩短整个新功能的交付时间。而且,一旦系统在生产环境中运行,无论你是处理故障,还是进行扩容,基于完全可预测性的部署能力会减轻系统的巨大压力。

那么如何实现这种可重复性呢?软件的优点之一是很容易改变,而且可以快速实现,但这也正是过去我们制造出大量不可控变化的原因。为了实现所需的可重复性,你必须遵守一定的规则。具体来说,需要做到以下几点:

■ 控制部署软件的环境。

■ 控制正在部署的软件,也可称为可部署构件。

■ 控制部署流程。

控制环境

在装配流水线上,你可以用完全相同的方式来摆放正在组装的部件和使用的工具,从而实现对环境的控制。这样你不需要每次都费力地去找规格为3/4英寸的套筒扳手,因为它总是放在同一个位置。在软件中,你需要通过两个主要机制来让系统的运行环境保持一致。

首先,你必须从标准化的机器镜像开始。在构建环境的过程中,必须始终从一个已知的起点开始。其次,为了部署软件而对基础镜像进行的更改,也必须通过编程的方式来实现。例如,从一个基础Ubuntu镜像开始,并且软件需要Java开发工具包(JDK),那么可以通过脚本将JDK安装到基础镜像中。此模式也经常被称为基础设施即代码(Infrastructure as Code)。当需要一个环境的新实例时,可以从基础镜像开始并执行脚本,这样就可以保证每次都拥有相同的环境。

这个流程一旦建立,之后对环境的任何更改也必须按照同样的方式进行。如果运维人员经常通过SSH进入计算机更改配置,那么就违背了严格管理环境的原则。你可以使用多种技术来控制初次部署后的环境变更,例如,禁止通过SSH访问正在运行的环境;或者不禁用SSH,但是设置为当有人通过SSH进入时,计算机会自动下线。后者非常有用,因为该方式允许进入机器检查问题,但不允许对运行中的环境进行任何更改。如果需要对运行中的环境进行更改,只能更新标准的机器镜像及应用运行时环境的代码,这两者都通过某个源代码控制系统或者类似的系统进行管理。

可以由不同的人来负责创建标准化的机器镜像和基础设施即代码的脚本,但是作为应用程序的开发人员,你必须使用这样的系统。在软件开发生命周期早期是否使用这样的实践方式,对企业是否能够在生产环境中有效地部署和管理软件有着重要影响。

控制可部署构件

这里花点时间来解释一个显而易见的事实:环境之间总是有差异的。在生产环境中,你的软件会连接到在线客户数据库,假设URL为http://prod.example.com.cn/cutomerDB;在预发布环境中,它连接的是该数据库的一个副本,已经脱敏了个人身份信息,假设URL为http://staging.example.com.cn/cleansedDB;在一开始开发的时候,可能连接的是一个模拟的数据库,假设URL是http://local-host/mockDB。显然,每个环境中的连接信息都是不同的。如何处理代码中的这些差异呢?

我知道你不会将这些字符串硬编码到代码中。你可能会提取代码中的参数,并将这些值放入某种类型的属性文件中。这迈出了很好的第一步,但是经常会遇到一个问题:属性文件以及不同环境的参数值,经常会被编译到可部署的构件中。例如,对于Java程序来说,application.properties通常会被打包到一个JAR或者WAR文件中,然后被部署到某一个环境中。这就是问题所在。由于打包了与环境有关的配置,所以在测试环境中部署的JAR文件与在生产环境中部署的JAR文件不同,如图2.10所示。

图2.10 即使将与环境有关的参数放到属性文件(包括可部署构件中的属性文件)中,也会在整个软件开发生命周期中产生不同的构件

一旦你为软件开发生命周期的不同阶段构建了不同的构件,可重复性就可能受到影响。控制软件构件变更的基本原则就是确保构件之间唯一的区别是属性文件的内容,我们现在必须将这一点与构建过程本身结合起来。不幸的是,由于每个JAR文件都是不同的,无法通过比较文件的哈希值来验证部署到预发布环境中的构件是否与部署到生产环境中的构件完全相同。如果其中一个环境发生了变化,或者其中一个属性值发生了变更,都必须更新属性文件,这意味着需要打包一个新的可部署构件,以及进行一次额外的部署。

为了实现高效、安全和可重复的生产环境运维,在整个软件开发生命周期中使用单个可部署构件是很重要的。在开发期间构建并通过回归测试的JAR文件,就应该是部署到测试环境、预发布环境和生产环境中的同一个JAR文件。为了实现这一点,需要按照正确的方式来组织代码。例如,属性文件不包含任何与环境有关的值,而是定义一组需要后续注入值的参数。然后可以在适当的时候给这些参数赋值,或者通过正确的来源生成值。作为开发人员,你可以决定如何从环境中抽取适当的可变因素。这样只需要创建一个可部署的构件,在整个软件开发生命周期中使用它,从而改善敏捷性和可靠性。

控制流程

在保证环境一致性,以及创建一个可以贯穿整个软件开发生命周期的可部署构件之后,剩下的就是要确保这些部分以可控、可重复的方式组合在一起。图2.11演示了我们期望的结果:在软件开发生命周期的所有阶段中,可以根据需要放心地增减任意数量、完全一致的运行副本。

图2.11 我们期望的结果是能够让运行在标准化环境中的应用程序保持一致。注意,应用程序在所有环境中都是相同的,运行时环境在生命周期的各个阶段中都是标准的

图2.11里没有碎片化的差异。可部署的构件、应用程序,在所有部署过程和环境中都是完全相同的。运行时环境虽然在不同的阶段有差异(由不同深浅的灰色表示),但是底层是相同的,只是使用了不同的配置,例如连接的数据库地址。在软件开发生命周期的任何一个阶段中,所有的配置都是相同的,它们的颜色完全一样。这些一致化的运行单元,正是由我一直讨论的两个实体组成的,即标准化的运行时环境和单个可部署的构件,如图2.12所示。

图2.12 自动化组合标准的基础镜像、受控的环境配置和单一可部署的构件

在图2.12这幅简单的图背后隐藏着许多内容。怎样才是一个良好的基础镜像,以及如何将它提供给开发人员和运维人员?环境配置的来源是什么,何时将其注入应用程序的上下文?应用程序应当何时“安装”到运行时环境中?我将在书中一一回答这些及相关问题,但现在我的主要观点是:将各个部分组合起来并确保一致性的唯一方法就是自动化。

尽管在软件的开发阶段应用持续集成工具相当普遍(例如,通过构建管道编译已提交代码并运行一些测试),但是它在整个软件开发生命周期中并没有得到广泛应用。从代码提交到在测试环境和生产环境中部署,整个过程都应当自动化。

当我说这一切都应该是自动化的时,指的是所有一切。即使你不负责创建各个模块,但是组合过程必须自动化。例如,Pivotal Cloud Foundry(一个流行的云原生平台)的用户,会通过API从软件发布站点下载新的“干细胞”(Stem Cells)[2](部署应用程序的基础镜像),并通过构建管道完成运行时环境和应用程序构件的安装。另一个构建管道会负责最后将构件部署到生产环境中。实际上,当生产环境的部署也是通过构建管道进行时,服务器就不会被人直接接触,你的首席安全官(以及其他安全相关人员)也会感到很高兴。

但是,如果部署过程已经完全自动化,那么如何确保这些部署是安全的呢?这需要另一套新的理念。

2.2.3 安全部署

前面谈到了有风险的部署,企业用来控制风险的最常见手段就是通过复杂、缓慢的流程和耗时的测试来进行管理。起初,你可能会认为这是唯一的选择,因为想了解软件在生产环境中是否能正常工作的唯一方法就是先对它进行测试。但我觉得,这更像是Grace Hopper认为最危险的情况:我们一直都是这么做的。

诞生于云计算时代的软件公司向我们展示了一种全新的方式:它们在生产环境中进行试验。天啊!我在说什么?!让我再补充一个词:它们在生产环境中进行安全的试验。

让我们先来了解什么是“安全的试验”,然后再来讨论它对我们的部署和生产环境稳定性的影响。

大部分人都看过空中飞人表演,表演者松开挂在半空中的一个拉环,在空中旋转,然后抓住另一个拉环,他们通常都能完成高难度动作并娱乐观众。毫无疑问,他们的成功取决于正确的培训和合适的工具,以及大量的实践。但这些杂技演员知道事情有时会出错,所以他们会在安全网的保护下进行表演。

当你在生产环境中进行试验时,也需要有合适的“安全网”。这张“网”实际由运维实践和软件设计模式共同编织而成,再加上可靠的软件工程实践,比如测试驱动开发,就可以将失败的概率降到最低(但完全消除它并不是我们的目标)。预期出现失败(失败总会发生)大大降低了它成为灾难的可能性。也许一小部分用户会收到错误消息,需要刷新系统,但是整个系统仍然在运行。

提示 关键在于:所有的软件设计和运维实践,都是为了让你在必要时可以轻松、快速地在试验中回退,并返回到之前已知的工作状态(或者进入下一个工作状态)。

这是新旧思维方式的根本区别。在前一种方式下,你在投入生产环境前进行了大量测试,相信自己已经解决了所有问题。当这种错觉被证明是错误的时候,你就会陷入混乱。而在新的方式中,因为你已经预料到会失败,所以会特意提前给自己准备一条降低失败影响的退路。这一效果是立竿见影的,会让你的部署过程变得更简单、更快速,并且让系统在上线运行后具有更好的稳定性。

首先,如果你取消了在2.1.2节中介绍的复杂、耗时的测试过程,并且在通过基本的集成测试之后直接进入生产环境,那么将会节省大量的时间,并且很明显,发布行为可以更加频繁。这一发布过程可以被有意设计成轻量级的,同时它有助于进行更频繁的发布。有了合适的“安全网”之后,不仅可以避免灾难,还可以在几秒内迅速回到一个功能完备的系统。

当部署过程不需要太多资源且发布频率更高的时候,你就能够更好地解决当前生产环境中的问题,从而能够从整体上维护一个更稳定的系统。

让我们进一步讨论一下“安全网”是什么样子的,特别是开发人员、架构师和运维人员在其中所扮演的角色。你会看到三个紧密相关的部分:

■ 并行部署和版本化的服务

■ 进行必要的远程监控

■ 灵活的路由

在过去,部署某个软件的版本n,几乎总是意味着要替换掉版本n-1。此外,我们部署的是包含大量功能的大型软件,因此当意外发生时,结果可能是灾难性的。例如,整个核心业务的应用程序可能会经历大面积停机。

安全部署的核心是并行部署。与用新版本完全替换一个正在运行的软件版本不同,你可以在部署新版本的同时让已有的版本继续运行。一开始,只有一小部分流量被路由到新的版本,然后你可以观察会发生什么。你可以根据各种条件来控制哪些流量被路由到新的版本,例如请求来自何处(例如,来自某个地理位置或者引用页)或者用户是谁。

要想了解新的部署是否产生了积极的结果,你需要收集一些数据。新的版本正在运行且没有崩溃?是否引入了新的延迟?点击率是增加了还是减少了?

如果一切顺利,那么你可以继续将更多流量路由到新的版本。在任何时候出现了问题,你都可以将所有流量切回以前的版本。这是一条让你可以在生产环境中进行试验的退路。

图2.13显示了这种实践的核心流程。

图2.13 数据会告诉你如何并行部署应用程序的多个版本。你可以通过数据对访问应用程序的流量进行控制,从而可以在生产环境中安全地部署新的软件

但是,如果忽略了正确的软件工程规范,或者应用程序没有采用正确的架构模式,那么这一切都无法实现。实现这种A/B测试的要点如下:

■ 软件构件必须实现版本控制,并且版本必须对路由可见,以便恰当地切分流量。此外,由于会对数据进行分析,以确定新的部署是否稳定,以及是否实现了所需的结果,所以所有数据必须与软件的相关版本关联,以便后续进行对比。

■ 用于分析新版本工作情况的数据可以有多种形式。一些度量指标完全独立于任何实现细节,例如,请求和响应之间的时间延迟。而其他一些指标会关注正在运行的进程,报告正在使用的线程或内存数量等信息。最后,一些与业务有关的指标会用来判断部署效果,例如每笔在线交易的平均金额。有些数据可能由软件运行的环境自动提供,因此不必编写代码来生成它们。数据度量指标的可用性是首要考虑的问题,请思考一下如何生成支持生产环境试验的数据。

■ 显然,路由是并行部署的一个关键因素,而路由算法属于软件的一部分。有时算法很简单,比如将流量按照一定百分比发送到新的版本,也可以通过对基础设施的一些组件进行配置来实现路由。有些时候,可能需要更复杂的路由逻辑,并需要编写代码来实现。例如,可能需要测试一些地理位置上的优化,并且只想将相同地理位置的请求发送到新版本。或者,可能只是希望向高级客户公开某个新特性。路由逻辑无论是由开发人员来实现,还是通过环境配置来实现,它都是开发人员要考虑的头等大事。

■ 最后,我在之前提过要创建更小的部署单元。与其像电子商务系统那样一次部署很多功能,例如,类目服务、搜索引擎、图像服务、推荐引擎、购物车和支付模块等,不如将部署内容限定在一个更小的范围内。你可以很容易地想到,与支付相关的模块相比,新的图像服务对业务而言风险要小得多。应用程序的正确组件化(或者用现在常用的名词“基于微服务的架构”),与系统的可运维程度直接相关。[3]

虽然运行应用程序的平台可以提供一些必要的安全部署支持(第3章将会详细讨论这部分内容),但是所有4个因素—版本控制、指标、路由和组件化,都是开发人员在设计和构建应用程序时必须考虑的。云原生软件的要求远不止这些(例如,在架构中考虑降级以避免故障在整个系统中蔓延),但这些都是安全部署的关键因素。

2.2.4 变化是一定的

在过去的几十年里,已有大量的证据表明:基于“环境变化都是由我们人为、有意识地造成的”这种假设的运维模型是行不通的。实际上,对意外变化的响应占据了IT部门的大量时间,甚至依赖于估计和预测的传统软件开发生命周期过程,也被证明是有问题的。

正如本章所描述的新的软件开发生命周期过程,我们要构建能够适应变化的机制,这样可以给系统带来更大的弹性。需要注意的是,当涉及生产系统的稳定性和可预测性时,要识别这些能力是什么。这个概念有点难以理解,有点“抽象”,请容我多解释一下。

我们的目的不是为了更好地预测意外情况,或者为故障处理安排更多的时间。例如,将开发团队一半的时间分配到对紧急事件的响应上,对于解决冲突的根本原因毫无作用。你对意外事件做出响应,让一切恢复正常,然后就交差了,继续等待下一个意外事件的发生。

“完成了。”

这就是问题的根源。你认为在完成部署、处理紧急事件或者变更防火墙配置之后,就完成了任务。你这个“完成了”的想法,本质上是把变化当成了导致你完不成任务的原因。

提示 你需要放弃去“完成”一件任务的想法。

让我们来谈谈最终一致性。与其创建一组指令让系统进入一种“完成”状态,实现了最终一致性的系统永远不会期望“完成”。不如,让系统永远在工作,并达到一种平衡状态。这种系统的关键抽象是期望状态和实际状态。

系统的期望状态就是你希望它看起来的样子。例如,假设你希望用1台服务器运行一个关系数据库,用3台应用程序服务器来运行RESTful Web服务,再用2台Web服务器运行面向用户的Web应用程序。这6台服务器都已经正常联网,防火墙规则也已设置妥当。图2.14所示的拓扑结构演示了系统的期望状态。

图2.14 所部署系统的期望状态

你可能希望,在某些时候,甚至在大多数情况下,系统已经部署完成并且运行良好。但是在进行新的部署后,你肯定不会认为系统依然能保持正常。而是,你会将实际状态(当前在系统中运行的模型)视为最重要的事情,并使用本章已经介绍过的一些指标来构建和维护它。

实现了最终一致性的系统会不断地将实际状态与期望状态进行比较,当出现偏差时,会执行一些操作使它们重新保持一致。例如,假设在图2.14所示的拓扑图中,有一个应用程序服务器掉线了,出现这种情况的原因有很多,机器硬件故障、应用程序内存不足,或者网络分区切断了应用程序服务器与系统其他部分的连接。

图2.15演示了期望状态和实际状态的对比情况。显然,实际状态和期望状态并不匹配。要让它们保持一致,必须将另一个应用程序服务器恢复并添加到网络拓扑中,而且必须在其上安装和启动应用程序(回想一下之前关于可重复部署的讨论)。

图2.15 当实际状态与期望状态不匹配时,实现了最终一致性的系统会通过一系列操作使它们重新保持一致

对于以前很少接触最终一致性的人来说,这看起来有些高深莫测。我的一位专家同事回避使用“最终一致性”这个术语,因为他担心会引起客户的恐惧。但是建立在这个模型上的系统越来越多,有许多工具和资料可以帮助实现这类解决方案。

我要告诉你的是:这对于在云环境上运行的应用程序来说,是绝对、完全必要的。我曾经说过,事物总在变化,所以与其去应对它,不如去拥抱它。你不应该害怕最终一致性,而是应该拥抱它。

让我来澄清一下:虽然我在这里提到的系统不一定是完全自动化的,但是需要一个实现核心部分自动化的平台(下一章将详细介绍平台的作用)。我希望你做的,是以一种让系统自我修复并适应不断变化的方式,来设计和构建系统。本书的目的就是教你做到这一点。

在不断变化的情况下保持系统功能的完整性,是我们设计软件的最终目标,而变化对系统稳定性和可靠性的影响也是显而易见的。一个能够自我修复的系统,其正常运行的时间比每次出故障都需要人工干预的系统要长得多。将部署作为一种新的期望状态,可以极大地简化部署过程并降低风险。坚持“变化是一定的”的思维模式,可以从根本上改变在生产环境中管理软件的方式。