第 1 章:你一直在用的那个词:定义什么是“云原生”

云原生软件强调高度分布式和持续变化的架构,通过冗余、弹性和模块化设计,帮助现代应用在不稳定环境下保持高可用性和敏捷性。本章将结合实际案例,系统阐释云原生的核心特征与应用场景。

这不是 Amazon 的错。2015 年 9 月 20 日,星期日,Amazon Web Services (AWS) 经历了一次重大的宕机事故。随着越来越多的公司在 AWS 上运行自己的关键系统,AWS 宕机可能会导致一系列影响深远的事故。在这种情况下,Netflix、Airbnb、Nest、IMDb 以及很多其他公司都经历了宕机,影响了服务的客户,最终导致企业利益受到损失。AWS 核心宕机持续了大约 5 个小时(或者更长,取决于你的计算方式),而受 AWS 影响的客户则花费了更长时间才将其系统从故障中恢复。

如果你是 Nest 公司,你向 AWS 付费是因为希望专注于为客户创造价值,而不是基础设施的问题。作为协议的一部分,AWS 负责保证其系统的正常运行,从而让你的系统也能够正常运行。如果 AWS 出现宕机,那么你很容易把你的宕机都归咎于 Amazon。

但是你错了。Amazon 不应该为你的宕机负责。

等等!不要把书扔到一边,请听我说完。我的话会直击问题的核心,并且解释了本书的初衷。

首先,让我先澄清一件事情。我并不是说 Amazon 和其他云供应商没有责任保证系统的正常运行,他们显然在做这件事情。如果供应商没有达到一定的服务水平,客户可以并且一定会找到替代的方案。云服务供应商通常会提供服务级别协议 (SLA) 的保证。例如,Amazon 保证其大部分服务可以达到 99.95% 的正常运行时间。

我想指出的是,你在基础设施上运行的应用程序应该比基础设施本身更稳定。你会说,那怎么可能?朋友们,这正是本书要教给你们的。

让我们来快速回顾一下 9 月 20 日的 AWS 宕机事件。Netflix 是受网络中断影响的众多公司之一,它是美国最大的互联网网站,占据了全部互联网带宽的 36%。但是,尽管 Netflix 宕机影响了很多人,该公司对 AWS 事件是这么说的:

Netflix 在受影响的地区确实经历了短暂的可用性问题,但我们避开了任何重大影响,因为“混乱金刚(Chaos Kong)”的演习让我们为此类事件做好了准备。通过定期模拟“某个区域中断”的实验,我们能够及早发现任何系统性缺陷并修复它们。当 US-EAST-1 变得不可用时,我们的系统已经足够强大到将流量转移到其他可用区域。

Netflix 很快就从 AWS 宕机事故中恢复过来,事故发生几分钟后就完全恢复了功能。Netflix 仍然在 AWS 上运行,即使 AWS 还在宕机,它依然能够完全正常运行。

请注意 Netflix 是如何能够恢复得如此之快?答案是冗余。

没有一个硬件可以保证 100% 的时间都是可用的,并且正如一直以来的实践经验,我们会在适当的地方增加冗余系统。AWS 正是这样做的,并且将这些冗余能力提供给它的用户。

特别的是,AWS 会在多个区域提供服务。例如,在撰写本书时,其 Elastic Compute Cloud platform (EC2) 产品正在全球多个地区运行和提供服务。在每个区域内,服务被进一步划分为许多可用区 (AZ),这些可用区被配置为相互之间的资源是隔离的。这种隔离机制限制了一个可用区失败,会对另一个可用区服务造成的影响。

下图描述了三个区域,每个区域包含四个可用区。

图 1.1 AWS 将其服务划分为区域和可用区。区域对应地理地区,而可用区在单个区域内提供进一步的冗余和隔离。
图 1.1 AWS 将其服务划分为区域和可用区。区域对应地理地区,而可用区在单个区域内提供进一步的冗余和隔离。

应用程序在可用区中运行,重点是,它可能在多个区域的多个可用区内运行。回想一下,之前我曾说过,冗余是系统正常运行的关键之一。

下图展示了应用程序在 AWS 上的不同部署方式。

图 1.2 AWS 上的应用程序可以部署到单个可用区(例如 IMDb 公司)中,也可以部署在单个区域的多个可用区(例如 Nest 公司)中,或者部署在多个可用区和多个区域(例如 Netflix 公司)中。这样提供了各种不同的弹性能力。
图 1.2 AWS 上的应用程序可以部署到单个可用区(例如 IMDb 公司)中,也可以部署在单个区域的多个可用区(例如 Nest 公司)中,或者部署在多个可用区和多个区域(例如 Netflix 公司)中。这样提供了各种不同的弹性能力。

在实际场景中,单个区域故障如 2015 年 9 月的 AWS 宕机,仅影响 us-east-1 区域。

图 1.3 如果应用程序采用了正确的架构和部署方式,即使出现大范围的服务中断(例如整个区域故障),也应当可以正常提供服务。
图 1.3 如果应用程序采用了正确的架构和部署方式,即使出现大范围的服务中断(例如整个区域故障),也应当可以正常提供服务。

在这个简单的图表中,很显然可以看到 Netflix 比其他公司更好地避免了宕机事故。它的应用程序可以在其他 AWS 区域运行,并且能够轻松地将所有流量引导到正常的实例上。虽然与其他区域的故障切换不是自动的,但是 Netflix 已经预料到(甚至实践了!) 可能会出现这样的中断,并且以此来设计软件架构,包括相应的运维手段。

请注意,云原生软件的设计目的是预测故障,并且即使当它所依赖的基础设施出现故障,或者发生其他变化时,也依然能够保持稳定运行。

应用程序的开发人员、支持和运维人员,必须学习和应用新的模式和实践,来创建和管理云原生软件,这也正是本书所教授的内容。你可能认为这并不是什么新鲜的知识,因为很多企业在核心业务(比如金融)中已经采用了双主模式的系统,不过我们要介绍的是实现这一目标的新方法。

在过去,实现这些故障切换的行为通常各不相同,因为我们部署的系统最初并没有按照底层系统会发生故障的前提去设计。实现所需 SLA 指标要求的知识,通常仅限于少数几个“专家大拿”知道,因此为了使系统能够应对底层基础设施的故障,需要进行精心的设计、配置和测试。

这与 Netflix 今天所做的事在哲学上存在一个根本的不同。对于前一种方法,变化或者失败被视为例外情况。相比之下,Netflix 和许多其他大型互联网公司,例如谷歌、Twitter、Facebook 和 Uber,都将变化或者失败视为正常规律。

这些企业已经改变了它们的软件架构和工程实践,让面向失败的设计成为它们构建、交付和管理软件过程中的一个组成部分。

请注意,失败是正常的规律,而不是例外。

当今的应用程序需求

数字体验已经不再是我们生活中的一个配角。它们在我们日常生活中的许多活动中都扮演着重要的角色。这种普遍性已经超越了我们对所使用的软件的期望:我们希望应用程序总是可用的,能够不断地升级,提供个性化的体验。在从构思到实现的生命周期开始时,我们必须就能够满足这些期望。作为开发人员,你也是负责满足这些需求的其中一员。下面我们来了解一下关键的需求有哪些。

零停机时间

2015 年 9 月 20 日的 AWS 停机,展示了现代应用程序的一个关键要求:它必须始终是可用的。即使是可以容忍应用程序短暂不可用的日子也已经一去不复返了。世界是一直在线的。尽管没有人希望计划外的停机出现,但它的影响已经达到了惊人的水平。例如,2013 年《福布斯》(Forbes)估计,Amazon 在一次 13 分钟的意外停机事故中损失了近 200 万美元。不管停机是否在计划中,都会导致收益和客户满意度的严重下降。

但是维护系统的正常运行不仅仅是运维团队的问题。软件开发人员或者架构师对设计和开发一个松耦合、组件化的系统同样负有责任,应该通过部署冗余组件来应对不可避免的故障,并设置隔离机制来防止故障在整个系统中传播。他们还必须让软件对计划事件(例如升级)也能够做到零停机时间。

缩短反馈周期

同样至关重要的是频繁发布代码的能力。在激烈的竞争和不断增长的消费者期望的双重驱动下,应用程序更新已经从每月数次,到每周数次,有时甚至一天数次。让客户感到兴奋毫无疑问是有价值的,但是持续发布的最大动力是为了降低风险。

从你对某个功能有了想法的那一刻起,你就承担了一定程度的风险。这个主意好吗?客户能使用它吗?它能以一种更好的方式实现吗?虽然你会尽可能去预测可能的结果,但现实往往是与预期不同的。获得诸如此类重要问题答案的最佳方法,是发布一个功能的早期版本并收集反馈。利用这些反馈信息,你可以做出调整,甚至完全改变想法。频繁的软件发布不仅缩短了反馈周期,也降低了风险。

在过去几十年里占主导地位的单体软件系统,无法做到频繁的发布。因为由独立团队构建和测试的各个子系统之间密切相关,所以需要把它们作为一个整体,进行测试后才能够进入到脆弱的打包过程。如果在集成测试阶段的后期发现了缺陷,那么这个漫长而艰苦的过程需要重新开始。为了实现将软件发布到生产环境的敏捷性,新的软件架构是必不可少的。

移动端和多设备支持

2015 年 4 月,在技术趋势评测和分析公司 Comscore 发布的一份报告中显示,移动设备的互联网使用率首次超过台式电脑。今天的应用程序需要支持至少两种移动设备平台(iOS 和 Android)和桌面系统(仍然占据了很大一部分使用量)。

此外,用户越来越希望他们的应用体验,在一天中可以从一个设备无缝切换到另一个设备上。例如,用户可能在苹果电视上看电影,然后在去机场的火车上改用移动设备观看节目。此外,移动设备的使用模式与桌面设备的使用模式有很大的不同。例如,银行必须能够满足移动设备用户频繁的应用程序刷新,他们可能正在等待查看每周的薪水是否到账。

正确设计应用程序对于满足这些需求至关重要。核心服务必须能够支持所有为用户提供服务的终端设备,并且系统必须能够适应不断变化的需求。

互联设备——也被称作物联网

互联网不再仅仅是将人类与系统连接起来。如今,数以亿计的设备也通过互联网连接,使得它们能够被连接的其他实体监控甚至控制。预计到 2022 年,仅家庭自动化的市场,即使其在所有物联网 (IoT) 连接设备中只占很小的一部分,也将成为一个将近 530 亿美元的市场。

联网的家庭拥有传感器和远程控制设备,例如运动探测器、照相机、智能恒温器,甚至照明系统。这些都是非常实惠的。几年前,在华氏零下 26 度的天气里,我的水管爆裂了,于是我用一个联网的恒温器和一些温度传感器,搭建了一套简易的恒温系统,花费总共不到 300 美元。其他物联网设备还包括汽车、家用电器、农业设备、喷气式发动机,以及我们大多数人口袋里携带的超级计算机(智能手机)。

物联网设备从两个基本方面改变了我们软件的特性。首先,互联网上的数据流量急剧增加。数以亿计的设备在一分钟内多次广播数据,甚至有的设备达到每秒多次。其次,为了收集和处理这些海量数据,计算能力的基础建设势必要发生重大的改变。当计算资源被放在“边缘”位置,更靠近连接设备时,它会变得更加分布式。数据量和基础架构之间的差异,需要新的软件设计和实践来解决。

数据驱动

考虑到我到目前为止提出的几个需求,你应该会以更全面的角度来考虑数据。数据量正在不断增加,数据源变得更加广泛分布,而软件的交付周期正在缩短。综上所述,这三个因素使得大型的、集中式、共享的数据库变得无法可用。

例如,一个装有数百个传感器的喷气式发动机,会经常与数据中心的数据库断开连接,同时由于带宽限制,无法在建立连接后立即将所有数据传输到数据中心。此外,共享数据库需要在众多应用程序之间进行大量的处理和协调,以便满足各种数据模型和交互场景,这是导致无法缩短发布周期的主要问题。

这些应用程序需要的不是单一的共享数据库,而是一个由更小的、本地化数据库组成的网络,以及能够在多个数据库管理系统之间管理数据关系的软件。这些新方法推动了对从软件敏捷开发管理一直到数据层面的各种要求。

最后,如果没有人使用新的数据,那么这些数据都没有价值。今天的应用程序必须越来越多地使用数据,通过更智能的应用程序为客户提供更高的价值。例如,地图应用程序使用来自物联网汽车和移动设备的 GPS 数据,以及道路和地形的数据,来提供实时交通报告和路线导航。过去几十年的应用程序不过都是精心设计的算法,经过仔细调整以适应预期的使用场景,而现在它们正在被不断变化的应用程序所取代,甚至这些程序可能会自动调整内部的算法和配置。

这些用户需求——持续的可用性、频繁发布的不断演进、易于扩展和智能化,都是过去的软件设计和管理系统所无法满足的。但是,什么样的软件能满足这些需求呢?

介绍云原生软件

你的软件需要全天候提供服务。你需要能够频繁地发布版本,满足用户随时的需求。用户的移动性和始终处于连接的状态,促使你的软件需要处理比以前更多、波动更大的请求。而连接设备(“物联网”)形成了一个空前庞大的分布式数据结构,需要新的存储和处理方法。这些需求,以及需要一个新平台来运行这些软件,直接导致了一种新的软件架构风格的出现,即云原生软件。

定义什么是“云原生”

云原生软件的特点是什么?让我们先进一步分析一下前面的需求,看看它们会导致什么样的结果。下图在顶部列出了各个需求,并向下展示了与结果之间的关系。

图 1.4 用户对软件的需求趋势开发向云原生架构和管理方式转变。
图 1.4 用户对软件的需求趋势开发向云原生架构和管理方式转变。

以下是详细内容:

  • 如果想让软件始终处于运行状态,必须对基础设施的故障和变更具有更好的弹性,无论这些故障和变更是计划内的还是计划外的。当它所运行的环境经历了一些不可避免的变化时,软件必须能够适应这些变化。如果能够正确地构建、部署和管理软件的独立模块,它们的组合可以降低任何故障的影响范围,这会促使你采用模块化的设计。因为你知道没有一个实体可以保证永远不会失败,所以会在整个设计中都考虑冗余的情况。

  • 你的目标是频繁发布,但是单体软件做不到这一点,太多相互依赖的模块需要耗费大量时间和复杂的协作。近年来,各种实践已经充分证明,由更小的、更松耦合、可独立部署和可发布的组件(通常称为微服务)组成的软件,可以支持更敏捷的发布模型。

  • 用户不再局限于只能坐在桌子前使用电脑,他们要求使用随身携带的移动设备。非人体设备,例如传感器和设备控制装置,同样也是连接在一起的。这两种情况都会导致大量的请求和大幅波动的数据,因此需要能够动态伸缩以及持续提供服务的软件。

其中一些属性表示了架构,即这样的软件应该由可冗余部署的独立组件组成。其他属性说明了交付软件的管理实践,即部署必须能够适应不断变化的基础设施,以及大幅波动的请求。如果我们把属性集合作为一个整体,然后对它进行分析,会得到如如图 1.5 所示的结论:

  • 将软件拆分成一组独立的组件,冗余地部署多个实例,这就意味着分布式。如果你的冗余副本彼此部署得很接近,那么受到本地故障影响的风险就会更大。为了能够高效地利用基础设施资源,当你想通过部署某个应用程序的多个实例来增加请求数量的时候,你必须能够将它们平均部署在可用的基础设施上,例如均匀部署在 AWS、Google Cloud Platform(GCP)和微软 Azure 等云平台上。只有这样,你才可以分布式地部署软件模块。
  • 可适应性软件的定义是“能够适应新的条件”,这里的条件指的是基础设施和相关的软件模块。它们本质上是联系在一起的:随着基础设施的变化,软件也在变化,反之亦然。频繁的发布意味着频繁的更改,而通过水平伸缩的运维方法来适应波动的请求,也意味着不断的调整。很明显,你的软件和它运行的环境在不断地变化着。
图 1.5 从架构和管理方面我们理解了云原生软件的核心特征:它是高度分布式的,必须在不断变化的环境中运行,并且软件本身也在不断地发展变化中。
图 1.5 从架构和管理方面我们理解了云原生软件的核心特征:它是高度分布式的,必须在不断变化的环境中运行,并且软件本身也在不断地发展变化中。

定义 云原生软件是高度分布式的,必须在一个不断变化的环境中运行,而且自身也在不断的变化中。

云原生软件的开发涉及到更多的细节(具体内容将在本书各章节中介绍)。但最终,它们都会回归到这些核心特征:高度分布式和不断变化。这将是你们在学习本书过程中的口头禅,我将不断向你们重复高度分布式和不断变化的概念。

云原生软件的思维模型

Adrian Cockcroft 曾经是 Netflix 的首席架构师,现在是 AWS 云架构战略的副总裁,他曾经谈到过操作一辆汽车的复杂性:作为一个司机,你必须在控制汽车的同时在街道之间导航,并且要完成确保不接触到其他司机的复杂任务。你之所以能够做到这一点,是因为你已经形成了一个思维模型,你在了解世界的同时,还可以在不断变化的环境中控制你的工具(在这里指的是一辆汽车)。

我们大多数人用脚来控制速度,用手来控制方向,两者协作共同决定我们的驾驶。为了改善导航效果,城市规划者们在街道布局上下了很多功夫(愿上帝保佑巴黎的所有人)。除了标志和信号灯等工具以外,再加上交通规则,它们为你提供了一个框架,你的旅程就是在这样的框架中从开始到结束。

开发云原生软件也是一件很复杂的事。在本节中,我将介绍一个模型来帮助梳理开发云原生软件时涉及的大量问题。我希望这个框架能够帮助你理解关键的概念和技术,从而使你成为熟练的云原生软件设计和开发人员。

我将从简单的部分开始,使用一些你肯定已经熟悉的核心技术来开发云原生软件,如图 1.6 所示。

一个“应用程序(App)”会实现关键的业务逻辑。这是你将编写大部分代码的地方,例如,你的代码将接受一个客户订单,验证仓库库存中是否有可用的商品,并向账单部门发送一个通知。

当然,这个应用程序需要调用其他组件来获取信息,或者执行操作,我将它们称之为“服务(Service)”。某些服务会存储状态——例如仓库库存服务,而其他一些服务可能是实现了系统中另一部分业务逻辑的应用程序,例如客户账单。

图 1.6 一个基本的软件架构的常见元素。
图 1.6 一个基本的软件架构的常见元素。

在了解这些简单的概念之后,现在让我们去构建一个表示云原生软件的拓扑图,如图 1.7 所示。你有一组分布式的模块,其中大多数都部署了多个实例。你可以看到大多数应用程序也都被当做服务,并且有些服务是明显有状态的。箭头表示了组件之间的依赖关系。

这张图说明了几个有趣的地方。首先,请注意这些部分(方框,以及表示数据库或存储的图标)总是带有两个名称:方框表示应用程序和服务,而存储图标表示服务和状态。图 1.7 中所示的简单概念,可以看做是软件中各种组件所承担的角色。

你会注意到,任何具有指向它的箭头的实体(指示组件是被另一个组件所依赖的)都是服务。没错,几乎所有的东西都是服务。即使是拓扑图中的根应用程序也依赖于软件的消费者。当然,应用程序指的是你编写代码的地方。我特别喜欢服务和状态的组合,这清晰表明了一些服务是没有状态的(你肯定听说过无状态服务,这里用“应用程序(App)”表示),而其他服务都会涉及到状态管理。

图 1.7 云原生软件在熟悉的概念上增加了极度的分布式化,无处不在的冗余化,而且处于不断地变化中。
图 1.7 云原生软件在熟悉的概念上增加了极度的分布式化,无处不在的冗余化,而且处于不断地变化中。

因此,我定义了云原生软件的三个部分,如图 1.8 所示:

  • 云原生应用程序——它依然是你编写的代码,是软件的业务逻辑。通过使用合理的模式,能够让这些应用程序之间形成良好的合力,因为很少有单个应用程序能够提供完整的数字化解决方案。应用程序位于箭头的起点或者终点(或者两者都有),因此必须通过实现某些行为让它参与到这种关系中。它还必须能够支持云原生的运维能力,例如水平扩展和升级等。

  • 云原生数据——这是在云原生软件中存储状态的地方。即使这张图也显示了云原生架构与以往架构的明显差异,过去的架构通常会使用一个集中的数据库来存储软件的大部分状态。例如,你可能在同一个数据库中存储用户配置、帐户详情、评论、订单历史记录、付款信息等等。云原生软件将代码分解成许多更小的模块(应用程序),同样数据库也被拆分成多个和分布式化。

  • 云原生交互——云原生软件是云原生应用程序和云原生数据的组合,这些实体之间的交互方式最终决定了数字化解决方案的功能和质量。由于我们的系统具有极度分布式和不断变化的特点,所以在许多情况下,这些交互很大程度上区别于以前的软件架构,甚至一些交互模式都是全新的。

请注意,尽管一开始我谈到了服务,但最终它们并不是这个思维模型中的三个实体之一。很大程度上,这是因为几乎所有东西都是服务,包括应用程序和数据。但更重要的是,我认为服务之间的交互比服务本身更加重要。服务在整个云原生软件模型中无处不在。

图 1.8 云原生软件模型中的关键实体:应用程序、数据和交互
图 1.8 云原生软件模型中的关键实体:应用程序、数据和交互

建立了这个模型之后,让我们回到 1.1 节中介绍的现代软件需求,并考虑它们对云原生软件的应用程序、数据和交互的影响。

云原生应用程序

与云原生应用程序有关的问题包括以下几点:

  • 应用程序可以通过添加或删除实例来进行容量伸缩。我们将其称为水平扩容/水平缩容,它与以前架构的纵向扩容模型有很大的不同。如果部署正确,应用程序的多个实例也可以在不稳定的环境中提供一定的弹性。
  • 一旦一个应用程序有了多个实例,即使只有一个实例出现某种程度的故障,

那么将故障实例与整个应用程序集群隔离开,可以让你更容易地执行恢复操作。你可以简单地创建一个新的应用程序实例,并将其与所依赖的有状态服务连接起来。

  • 当应用程序部署了许多实例,并且它们所运行的环境不断变化时,云原生应用程序的配置会面临独特的挑战。例如,如果一个应用程序有 100 个实例,那么将一个新的配置放到一个已知的文件位置,然后重新启动应用程序的日子就一去不复返了。我们无法用这种传统的方式将配置更新到实例中,因为它们在分布式环境中会到处移动。
  • 云环境的动态特性要求你改变管理应用程序生命周期的方式(不是软件交付的生命周期,而是应用程序启动和关闭的过程)。你必须重新审视如何在新的上下文中启动、配置、重新配置和关闭应用程序。

云原生数据

虽然应用程序是无状态的。但是处理状态也是软件解决方案中重要的一部分,并且在极度分布式和不断变化的环境中也需要解决数据处理问题。云原生的数据问题包括以下几个方面:

  • 你需要打破数据的统一管理。在过去的几十年里,组织投入了大量的时间、精力和技术来管理大型的、统一的数据模型。因为在许多领域存在类似的概念,所以许多软件系统也按照管理单个实体来实现。例如,在医院中,病人的概念与许多环境相关,包括临床/护理、计费、经验调查等等,所以开发人员通常会创建一个模型(一个数据库)来管理病人信息。但是,这种方法不适用于现代化的软件,它发展缓慢而且脆弱,并且缺少松耦合架构的敏捷性和健壮性。你需要创建一个分布式的数据架构,就像创建一个分布式的应用程序架构一样。

  • 分布式的数据架构由一些独立的、适用于特定场景的数据库(支持多种持久化方式)和一些数据源来自其他地方、仅用于数据分析的数据库组成。缓存就是云原生软件中的一种关键模式和技术。

  • 当多个数据库中存在多个实体 (例如前面提到的“病人”) 时,你必须解决公共信息在多个不同实例之间的同步问题。

  • 最终,分布式数据架构的核心就是将状态作为一系列事件的结果来处理。事件源模式会捕获状态更改的事件,将事件收集成统一日志并分发给其他数据成员。

云原生交互

最后,当你将所有这些部分组合在一起时,又会产生一组新的云原生交互的问题:

  • 当一个应用程序有多个实例时,需要某种路由系统才能够访问它。这里势必会使用到同步的请求/响应模式,以及异步的事件驱动模式。
  • 在高度分布式、不断变化的环境中,你必须考虑访问失败的情况。自动重试是云原生软件中的一种常用模式,但如果管理不当,它们可能会对系统造成严重破坏。当应用自动重试模式时,断路器是必不可少的。
  • 由于云原生软件是多个服务的组合,所以单个用户请求会涉及到调用大量相关服务。如果想合理管理云原生软件,确保提供良好的用户体验,就必须管理好组合中每个服务及其之间的交互。像指标、日志这种我们已经开发了几十年的东西,必须针对新的架构加以调整。
  • 模块化系统最大的优点之一是让其各个部分更容易独立地演化。但是,因为这些独立的部分最终会组合成一个更大的整体,所以它们之间的底层交互协议必须适合云原生环境,例如,一个支持并行部署的路由系统。

这本书介绍了满足这些需求的新的模式和实践。

让我们通过一个具体的例子,来让你更好地理解我在这里提出的这些问题,并让你能够了解到我的思路。

云原生软件实战

让我们从一个熟悉的场景开始。假设你在 Wizard 银行有一个银行账户。你偶尔会去当地的分行,同时你也是该银行网上银行的注册用户。在过去一两年的大部分时间里,你都只能通过家里的固定电话接收来电,有一天你终于决定改变这一点。因此,你需要更新你在银行(以及许多其他机构)的电话号码。

网上银行允许你编辑自己的用户信息,其中包括主要电话号码和任何备份电话号码。登录站点后,你将被导航到用户信息(Profile)页面,输入新的电话号码,然后单击提交(Submit)按钮。随后你会收到更新已被保存的确认信息,于是这次用户体验到此为止。

让我们来看看,如果这个网上银行程序采用云原生的架构,它会是什么样子。下图描述了这些关键的元素:

  • 因为你还没有登录,所以当你访问“用户信息(User Profile)”应用程序时,它会将你重定向到“身份验证(Authentication)”应用程序。注意,每个应用程序都部署了多个实例,所以用户请求会通过一个路由器被发送到其中一个实例。
  • 作为登录的一部分,身份验证应用程序会在一个有状态的服务中创建和存储一个新的身份验证令牌(auth token)。
  • 然后,使用新的身份验证令牌将用户重定向回“用户信息(User Profile)”应用程序。这一次,路由器会将用户请求发送到“用户信息(User Profile)”应用程序的另一个实例。(剧透警告:在云原生软件中不要使用粘性会话(sticky session)!)
  • “用户信息(User Profile)”应用程序会通过调用“认证(Auth)API”服务来验证身份令牌。同样,因为有多个实例,所以请求会被路由发送到其中一个实例。回想一下,有效的身份令牌会被存储在一个有状态的“认证令牌(Auth Token)”服务中,不仅“身份验证(Authentication)”应用程序可以访问,而且“认证(Auth)API”服务的任何实例也可以访问。
  • 由于这些应用程序(“用户信息”和“身份验证”)的实例可能由于各种原因而发生改变,因此必须有一个协议能够不断用新的 IP 地址来更新路由器。
  • 然后,“用户信息(User Profile)”应用程序向“用户(User)API”服务发出一个下游请求,来获取当前用户的个人信息数据,包括电话号码。然后,“用户信息(User Profile)”应用程序会向用户的有状态服务发出一个请求。
  • 在用户更新了他们的电话号码并点击了提交 (Submit) 按钮之后,“用户信息 (UserID)”应用程序会将新数据发送到一个“事件日志 (event log)”中。
  • 最后,“用户(User)API”服务的一个实例会接收并处理这个更改事件,并向“Users”数据库发送一个写请求。

这里发生了很多事情!

不要害怕——你现在不需要完全理解它。

这个图只是提供了一个关键概念的概述。

图 1.9 网上银行软件由应用程序和数据服务组成。其中用到了许多种交互协议。
图 1.9 网上银行软件由应用程序和数据服务组成。其中用到了许多种交互协议。

是的,这已经很多了,但我还想再增加一些。

但当你回到银行分行,银行柜员核实你的当前联系方式时,你会希望柜员有你更新后的电话号码。但是网上银行软件和柜员使用的软件是两个不同的系统。这个设计是没问题的,它提供了敏捷性、弹性和许多我认为对现代系统很重要的优点。下图展示了完整的产品架构。

银行柜员软件的结构与网上银行软件并没有明显不同,它也是云原生应用程序和数据的组合。但是,你可以想象的到,每个数字解决方案都要处理甚至存储用户的数据,或者说是客户的数据。在云原生软件中,即使数据处理也会更倾向于松耦合。这体现在网上银行软件中的用户(Users)状态服务和银行柜员软件中的客户(Customers)状态服务。

图 1.10 虽然 Wizard 银行的各个软件是独立开发和管理的,但是在用户看来就是一个软件。
图 1.10 虽然 Wizard 银行的各个软件是独立开发和管理的,但是在用户看来就是一个软件。

因此,我们的问题是,如何协调这些不同存储之间的公共数据?你的新电话号码将如何显示在银行柜员的软件上?

在下图中,我向模型中添加了另一个概念,我将其称为“分布式数据协调”。这里的描述并不意味着任何实现细节。我并不是建议使用一个规范化的数据模型、一主多从式的数据管理技术,或者任何其他解决方案。目前,这只是对问题的一个描述,我们很快会来研究如何解决。

图 1.11 分散的、松耦合的数据架构需要将数据聚合管理。
图 1.11 分散的、松耦合的数据架构需要将数据聚合管理。

我们已经介绍了很多知识,图 1.9、1.10 和 1.11 的内容都很多,你可能还不能很好的理解,不过没关系,我希望你了解的是云原生软件的一些关键概念:

  • 云原生软件架构包含很多的组件。
  • 有一些专门用来处理系统变更的协议。

我们将在接下来的章节中深入讨论所有的细节。

云原生和世界和平

我在这个行业已经工作了很长时间,也看到了一些技术进步想解决所有的问题。

例如,当 20 世纪 80 年代后期出现面向对象编程时,有些人认为软件会自己编写软件。尽管这些乐观的预测都没有成真,但毫无疑问,许多大肆宣传的技术提升了软件的许多方面,例如易于构建和管理、更好的健壮性等等。

云原生软件架构在今天已经变得非常流行,通常被称为微服务(虽然我在这里提前剧透了,它们也不会带来世界和平)。即使它们确实开始占据软件开发的主导地位(我相信它们会的),它们也不是万能的。让我们稍后更详细地来讨论这个问题,但是让我们先来讨论一下“云(Cloud)”这个词。

云和云原生

“云(Cloud)”这个词可能会给人带来困惑。当我听到一个公司的老板说,“我们正在向云迁移”,他们通常是指正在将一些或者甚至所有的应用程序,转移到其他的数据中心,例如 AWS、Azure 或者 GCP。这些云提供了与本地数据中心(机器、存储和网络)相同的功能,因此这种“向云迁移”只需对当前软件和运维方式进行少许的变更即可。

但是这种方法不会带来更好的软件弹性、更好的管理方式,或者更多的敏捷性。事实上,由于云服务的 SLA 通常低于本地数据中心的 SLA,所以在许多方面可能会出现降级。简而言之,迁移到云上并不意味着你的软件就是云原生的,也不会具有任何云原生软件的价值。

正如我在本章前面所分析的,消费者的新期望和新的计算环境(即云计算环境)迫使软件的构建方式发生了变化。当你拥抱新的架构模式和运维实践时,所开发的软件就会非常适合于在云环境中工作,如同天生源自于此。

注意“云”是指我们进行计算的地方。而“云原生”指的是如何实现。

如果云原生是关于如何实现的,那么是否意味着你可以在当前环境下实现云原生的解决方案?你说对了,我合作过的大多数企业都是先在自己的数据中心实现云原生架构。这意味着他们内部的基础设施需要支持云原生的软件和运维方式。我将在第 3 章来讨论这种基础设施。

尽管云原生很棒(我希望当你读完这本书的时候,你也会这么想),但是它并不意味着一切。

什么不是云原生

我相信你应该可以理解,不是所有的软件都应该是云原生的。当你学习这些模式时,你会发现有些新方法很有用,而有些则不那么有用。如果软件所依赖的服务总是位于一个固定的位置,并且从来不发生改变,那么就不需要实现一个服务发现协议。有些方法在带来重大价值的同时,也会产生新的问题,比如调试多个分布式组件中的程序逻辑就可能很困难。下面将介绍三个最常见的不适合使用云原生架构的原因。

首先,有的时候软件和计算基础设施不需要云计算。例如,如果软件不是分布式的,并且很少变化,那么完全不用做到像大规模运行的 web 或移动应用一样的稳定性。例如,嵌入在越来越多的物理设备(例如洗衣机)中的代码,甚至可能没有计算和存储资源来支持对现代架构关键的冗余机制。我的象印(Zojirushi)电饭煲中的软件,可以根据车载传感器的报告来调整烹饪的时间和温度,它也不需要能在不同处理器上运行的软件。如果这种软件或者硬件的某个部分出现了故障,最糟糕的结果就是菜炒坏了,而我需要叫个外卖而已。

其次,有时云原生软件的特点并不适合解决当前的问题。例如,许多新的模式为系统提供了最终一致性,于是在分布式软件中,系统中的某个部分更新的数据可能不会立即反映到系统的所有其他部分。最终所有数据都将变成一致,但是这个过程可能需要几秒钟甚至几分钟才能完成。有时这种情况是可以接受的,例如,由于网络故障,你获得的电影推荐并不能立即反映另一个用户最新的五星评级,这也不是什么大问题。但有时这样是不能接受的,例如银行系统不能允许用户在一家分行提取了所有资金,并关闭了其银行账户,但是随后又从 ATM 机上提取出额外的资金。最终一致性是许多云原生模式的核心,这意味着当我们需要强一致性时,就不能使用那些模式了。

最后,有时虽然你现有的软件不是云原生的,但是重写它也没有什么价值。信不信由你,大多数有几十年历史的组织,都有一部分 IT 资产运行在大型机上,而且可能还会继续在大型机上运行几十年。但这不仅仅是大型机的问题,许多软件运行在大量现有的 IT 基础设施上,这些基础设施都是基于比云更早的架构设计。只有当重写代码具有足够的商业价值时,你才应该这样做,但是即使有价值,你也需要对

这些工作排列优先级,通过几年的时间来逐渐更新各种产品。

云原生的好处

但这不是一个非此即彼的情况。大多数人在开发软件时都已经存在了很多其他的解决方案。即使你是在开发一个新的软件,它可能也需要与那些现有的系统进行对接,而且现有运行的软件几乎都不是云原生的。云原生的绝妙之处在于它最终是由许多不同的组件组成,即使其中一些组件的模式不是最新的,云原生的组件仍然可以与它们进行交互。

即使你的软件使用了旧的设计模式,应用云原生模式依然可以带来立竿见影的价值。例如,图 1.12 中包含了几个应用程序的组件。一名银行柜员可以通过用户界面访问到用户的帐户信息,然后调用在大型机上运行的应用程序的 API 接口。在这种简单的部署拓扑中,如果“账户(Account API”服务和大型机应用程序之间的网络被中断了,客户将无法进行提现。

图 1.12 在没有访问记录源的情况下发放资金是不允许的。
图 1.12 在没有访问记录源的情况下发放资金是不允许的。

但是现在让我们将一些云原生模式应用到这个系统中。例如,如果你在多个可用区中为每个微服务部署多个实例,通过同一个区域中的网络,其他可用区中的服务实例仍然可以访问到大型机的数据(如图 1.13 所示)。

图 1.13 通过使用一些云原生模式,例如冗余和适当的分布式部署,可以给非云原生的软件也带来好处。
图 1.13 通过使用一些云原生模式,例如冗余和适当的分布式部署,可以给非云原生的软件也带来好处。

跨不同的故障边界部署应用程序的多个实例,能够让云原生模式在混合(云原生和非云原生) 软件架构中带来好处。

同样值得注意的是,当你希望重构遗留代码时,不需要一下子就完成所有重构工作。例如,为了向云迁移,Netflix 将其面向客户的整个数字解决方案重构为云原生架构。这项工作最终历时 7 年,但在此过程中,Netflix 首先对部分单体的客户端 - 服务端系统进行了重构,并获得了立竿见影的效果。通过之前的银行示例,我们得到的经验是,在向云迁移的过程中,即使只有一部分解决方案是云原生的,也是有价值的。

无论你是否打算通过新的模式,在云环境中创建一个新的应用程序,还是打算从一个已有的单体程序提取和重构出一个云原生的部分,你都可以从中获得重要的价值。在 2010 年代早期的时候,尽管那时我们还没有使用云原生这个术语,但业界已经开始试验以微服务为中心的架构,几年间,许多模式都得到了改进。这种“新”趋势很容易被理解,也越来越得到广泛传播。我们已经看到了这些方法所带来的价值。

我相信这种架构风格会在未来的一二十年里占据主导地位。它与其他容易消失的架构风格的不同之处在于,它是计算基础设施从根本上演变的结果。过去二、三十年中占据主导地位的客户端 - 服务端模型,首次出现是在计算基础设施从大型机转移到众多小型计算机的时候,因此需要我们编写各种软件来适应这种计算环境。云原生架构的出现也类似这个过程,并且逐渐成为一种新的计算基础设施,即提供高度分布式且不断变化的软件定义计算、存储和网络抽象。

总结

本章系统阐述了云原生软件的核心特征及其对现代应用程序的影响。主要观点如下:

  • 云原生应用程序即使遇到基础设施不断变化甚至发生故障时,依然可以保持稳定。
  • 现代应用程序的关键要求包括快速迭代和频繁发布、零停机时间以及大量新设备的连接。
  • 云原生应用程序模型包含三个关键实体:云原生应用、云原生数据、云原生交互。
  • “云”是指软件运行的地方,而“云原生”指的是它如何运行。
  • 云原生并不是非此即彼的架构。组织中的一些软件可以采用许多新的云原生架构模式,也有一些软件可以仍然继续使用较老的架构,还有一些软件可以采用混合架构(新旧架构的结合)。

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区