购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第1章
可扩展系统简介

在过去20年里,软件系统的规模、复杂性和容量都出现了前所未有的增长。这种增长速度在未来20年内几乎不可能放缓——现在几乎无法想象未来系统的样子。但是,我们可以确定的一件事情是,越来越多的软件系统将需要在满足持续增长需求的基础上来构建——更多请求、更多数据和更多分析——作为系统设计的主要驱动力。

可扩展 (scalable)是软件工程中用来描述软件系统能够适应增长的术语。在本章中,我将探讨可扩展能力的确切含义,俗称 可扩展性 (scalability,对此术语你肯定耳熟能详)。我将借助一些案例,用确凿数据来说明当代应用程序的功能和特征,然后再引出我们今天常规构建的大型系统的起源。最后,我将介绍实现可扩展、可复制和最优化系统的两个基本原则,它们将会在本书的其余部分以各种形式重复出现,并用来检查架构的可扩展性与其他质量属性之间不可磨灭的联系。

1.1 可扩展性的定义

直观地说,可扩展性是一个非常简单的概念。维基百科对它的定义( https://oreil.ly/JsYXf )是“可扩展性是通过添加更多资源来处理越来越多的工作的系统属性”。我们都知道如何扩展一个高速公路系统——增加更多的车道,这样它就可以承载更多的车辆。我喜欢的一些人知道如何扩大啤酒生产规模——他们通过增加酿造容器的数量和扩大容器的体积、增加执行和管理酿造啤酒的人员数量,以及增加用来盛新鲜美味啤酒的桶的数量来实现产能扩大。任何物理系统——交通系统、机场,以及建筑物中的电梯——以及增加容量的途径都是显而易见的。

与物理系统不同,软件系统在某种程度上是无定形的。它们并非是通过外部观察就能了解其内部行为的东西,你无法指向它、看到它、触摸它和感觉它。一个软件系统就是一个数字工件(artifact)。从本质上讲,构成可执行代码和数据的1和0二进制流对于任何人来说都难以区分。那么,就软件系统而言,可扩展性意味着什么?

避免陷入定义之战,长话短说,可扩展性定义了软件系统在某个运营维度上处理增长的业务量的能力。不同运营维度的例子有:

●系统可以同时处理的来自用户或外部设备(例如传感器)的请求的数量。

●系统可以有效处理和管理的数据量。

●对系统存储的数据进行预测分析而获得的价值。

●随着请求量的增长,系统保持稳定、一致的响应时间的能力。

例如,假设一家大型连锁超市正在迅速开设新店并增加每家商店的自助结账终端的数量。这需要超市核心软件系统能够执行以下功能:

●在不缩短响应时间的情况下,处理增加的来自项目扫描的请求量。即时响应项目扫描对于维持客户满意度是必要的。

●处理和存储因销售额增加而产生的更多数据量。这些数据被用于库存管理、会计、计划和许多其他超市职能。

●从每个商店、不同地区和不同国家获取“实时”(例如每小时)销售数据摘要,并与历史趋势数据进行比较。这些趋势数据让某些地区的异常事件(意外的天气条件、活动中的大量人群等)一目了然,并帮助受影响的商店迅速做出反应。

●随着商店和客户数量的增长,改进库存订购预测子系统,以便能够正确预测销售量(以及因此需要重新订购库存商品的需求量)。

上述不同维度的要求实际上是系统可扩展性的需求。如果在一年多的时间里,这家连锁超市开了100家新店,销售额增长了400倍(有些新店很大!),那么要保证超市能够高效运营,便需要扩展软件系统以提供足够的处理能力。如果系统无法扩展并影响到客户的购物体验,那么当客户不满意时,我们会损失销售额。我们可能会因为库存滞留而增加成本。我们也可能会错失根据当地情况举办促销活动来增加销售额的机会。上述所有现象都是降低客户满意度和利润的因素,没有一个对企业有利。

因此,成功扩展系统对于我们虚构的超市的业务增长至关重要,实际上,它也关系到许多现代互联网应用程序的生存问题。但对于大多数商业和政府系统而言,在开发和部署的早期阶段,可扩展性并不是主要的质量要求。相反,增强可用性和实用性的新功能成为我们开发周期的驱动力。只要在正常负载下系统性能满足要求,我们就会不断添加面向用户的功能来提高系统的业务价值。事实上,在有明确的需求之前引入一些复杂的分布式技术(我将在本书中介绍的技术)可能对项目有害,额外的复杂性会导致开发过于保守。

尽管如此,在系统的发展过程中,提高系统性能和可扩展性成为当务之急甚至影响系统存亡的现象比比皆是。有吸引力的功能和实用性高的特性带来了成功,但也带来了更多需要处理的请求和更多需要管理的数据。这往往存在一个临界点,在轻负载下有意义的设计和决策突然变成了技术债务 [1] 。以下来自系统外部的事件通常会引爆临界点:2020年3月或4月的媒体上有许多关于政府失业和冠状病毒大流行导致超市在线订购网站无法承受激增的需求而崩溃的报道。

通过增加资源来增加系统在某个维度上的容量称为 纵向扩展 (scaling up)或 横向扩展 (scaling out)——稍后我将探讨它们之间的区别。此外,与物理系统不同,能够 按比例缩小 (scale down)系统容量以降低成本同样重要。

一个非常典型的例子是Netflix,它需要处理来自不同区域的可预测的昼夜请求。概括地说,在任何地理区域,晚上9点观看Netflix的人比早上5点要多。这使得Netflix可以在访问量较低时减少资源,从而节省了在亚马逊云中运行节点的成本,同时也带来了社会价值,如降低数据中心能源功耗等。比较一下Netflix和高速公路:高速公路晚上很少有车,但我们不会撤回车道(修理除外)。为数不多的司机可以在全部道路上随心所欲地行驶。在软件系统中,我们可以在几秒钟内扩展和收缩系统的处理能力,以满足瞬时访问量。与物理系统相比,我们部署的策略大有不同。

关于软件系统的可扩展性还有很多需要考虑的地方,让我们先探讨2021年左右的一些当代软件系统的规模,然后再回到这些问题。

1.2 21世纪早期系统规模案例

在这场技术游戏中展望未来总是充满风险。2008年我写过:

“虽然PB [2] 数据集和千兆数据流是当今数据密集型应用程序的边缘,但毫无疑问10年后,我们会怀念这种规模下的软件系统问题,并担心正在逼近的百亿亿级应用程序所带来的困难。” [3]

毫无悬念,这是真的,但只是百亿亿级吗?这个数量级的应用程序在当今世界几乎是司空见惯的事。谷歌在2014年报告了几个EB [4] 的Gmail( https://oreil.ly/vQ7M3 ),到目前为止,是否所有Google服务都管理1个YB [5] 或更多的数据?我不知道。我甚至不敢想象YB是什么概念!虽然谷歌没有公开其存储,但我不会打赌它未来不公开。同样,亚马逊到底为其客户在各种AWS数据仓储中存储了多少数据呢?例如,对于所有支持的客户端应用程序,DynamoDB每秒总共处理多少个请求?这些事情想得太久,你的脑袋会爆炸的。

各大互联网公司的技术博客有时是洞察当代运营规模的重要信息来源。除此之外,还有一些分析互联网流量的网站,它们的数据可以很直观地说明流量。请记住,以下网站提供的案例在一年甚至几年内都是新奇有趣的:

●Facebook的技术博客描述Scribe( https://oreil.ly/omAo8 )解决方案每小时收集、聚合和交付PB级日志数据,具有低延迟和高吞吐量。Facebook的计算基础设施由数百万台机器组成,每台机器捕获与系统和应用程序健康相关的重要事件,并生成日志文件。处理这些日志文件(例如来自Web服务器的日志)可以让开发团队深入了解应用程序的行为和性能,并支持故障查找。Scribe是一种自定义缓存队列解决方案,它可以以每秒几TB的速率从服务器传输日志,并将它们传送到下游分析和数据仓库系统中。我的朋友们,这是海量的数据!

●你可以在Internet Live Stats( https://oreil.ly/9Acav )查看众多服务的实时互联网流量。挖掘一下,你会发现一些惊人的统计数据,例如:谷歌每天处理大约35亿次搜索请求;Instagram用户每天上传大约6500万张照片;世界上大约有17亿个网站。这是一个有趣的网站,包含丰富的信息。值得注意的是,数据不是真实的,而是根据多个数据源的统计分析进行估计的。

●2016年,谷歌发表了一篇描述其代码库特征的论文( https://oreil.ly/hyMgl )。论文报道了众多令人吃惊的事实,其中之一是:“该存储库包含86 TB的数据,包括900万个唯一的源文件,其中有约20亿行代码。”请记住,这发生在2016年 [6]

尽管如此,互联网站点提供的关于服务规模的真实且具体的数据仍然是商业机密。幸运的是,我们可以通过某家科技公司的年度使用报告深入了解其处理的请求和数据量的规模。不过要小心,因为它来自Pornhub 。你可以在这里( https://oreil.ly/hOxsP )浏览极其详细的使用统计数据,数据从2019年开始统计。这是对大规模系统能力的迷人一瞥。

1.3 软件系统发展简史

我相信很多读者都无法想象在互联网搜索、YouTube和社交媒体出现之前人类文明生活是如何存在的。事实上,第一次上传视频到YouTube( https://oreil.ly/a8i1b )发生在2005年。是的,连我都难以置信。让我们简要回顾一下我们是如何达到当今系统规模的。以下是一些值得注意的历史里程碑:

1980~1989

一个由分时主机和小型机主宰的时代。个人计算机出现于20世纪80年代初期,但很少联网。到20世纪80年代末,软件研发实验室、大学及(越来越多的)企业都拥有了电子邮件和访问原始互联网资源的权限。

1990~1995

网络变得更加普遍,为基于HTTP/HTML的万维网(WWW)技术的出现创造了成熟的环境。万维网在20世纪80年代诞生于欧洲核子研究中心(CERN),由Tim Berners-Lee( https://oreil.ly/IULsL )首创。到1995年,网站的数量还很少,但未来的种子已经种下,如1994年雅虎(Yahoo!)创立以及1995年亚马逊和eBay诞生。

1996~2000

网站数量从10000左右增长到1000万( https://oreil.ly/ikPrg ),这是一个真正的爆发式增长期。网络带宽和访问量也迅速增长。针对如今我们熟知或正在使用的高度可扩展系统,亚马逊、eBay、谷歌和雅虎等公司已经开创了许多设计原则和先进技术的早期版本。正如下面“规模如何影响业务系统”中所阐述的,日常企业都争先恐后地利用电子商务提供的新机会,使得系统可扩展性的重要性日益彰显。

2001~2006

在此期间,网站数量从约1000万个增长到8000万个,新的服务和商业模式出现。2005年,YouTube推出。2006年,Facebook向公众开放。同年,在2004年低调起步的亚马逊网络服务(AWS)重新推出了S3和E C2服务。

2007至今

我们现在生活在一个大约拥有20亿个网站的世界中,其中大约20%处于活跃状态。大约有40亿互联网用户( https://oreil.ly/LEU3c )。由AWS、谷歌云平台(GCP)和Microsoft Azure等公有云运营商运营的巨大数据中心,以及无数私有数据中心,都分散在地球的各个角落,例如,Twitter的运营基础设施( https://oreil.ly/lUXMu )。云托管数百万个应用程序,工程师使用先进的云管理门户网站来配置和操作他们的计算和数据存储系统。强大的云服务使我们只需点击几下鼠标就可以构建、部署和扩展我们的系统。所有公司要做的就是在月底支付云提供商账单。

这就是本书所要描述的世界。在这个世界中,我们的应用程序需要利用关键原则来构建可扩展系统并使用高度可扩展的基础设施平台。请记住,在现代应用程序中,你执行的大部分代码并非由你的组织编写。代码是容器、数据库、消息传递系统和其他组件的一部分,通过调用API和构建指令,构成你的应用程序。这使得相关组件的选择和使用变得至少与设计和开发业务逻辑一样重要。它们是不易更改的架构决策。

规模如何影响业务系统

20世纪90年代,互联网接入用户的激增为企业带来了新的在线赚钱机会。这掀起了一番浪潮,企业通过Web浏览器向用户公开业务功能(销售、服务等)。这预示着我们对构建系统的思考方式发生了深刻的变化。

以零售银行为例。在提供在线服务之前,可以准确地预测银行业务系统将经历的访问量。我们知道有多少人在银行工作并使用内部系统,有多少终端和PC机已连接到银行的网络,必须支持多少台ATM,以及与其他金融机构联系的数量和性质。有了这些知识,我们就可以构建最多支持3000个并发用户的系统,并且知道不超过这个数字便是安全的。增长也相对缓慢,并且大部分时间(即非工作时间)访问量将远低于峰值。这使我们的软件设计决策和硬件配置变得更加容易。

现在,假设零售银行决定让所有客户都可以访问网上银行,而这家银行有500万客户。现在最大访问量是多少?在工作日期间访问量如何分散?高峰期是什么时候?如果我们通过限时促销来尝试拉拢新客户会怎样?突然间,我们相对简单和受限的业务系统环境受到冲击,同时将面临互联网用户群带来的更高的平均访问量和峰值访问量,以及不可预测性。

1.4 可扩展性系统设计基本原则

扩展系统的基本目标是在应用程序的某些特定维度上增加容量。一个常见的维度是增加系统在给定时间段内可以处理的请求数量。这称为系统的吞吐量。让我们用一个类比来探索我们在扩展系统和增加吞吐量时可以利用的两个基本原则:复制和优化。

1932年,世界标志性工程奇迹之一——悉尼海港大桥( https://oreil.ly/u7bOH )启用。现在,一个相当安全的假设是,2021年的交通流量略高于1932年。如果你有机会在过去30年的高峰时间开车过桥,那么你就会知道每天的交通流量大大超出它的容量。所以,我们如何提高物理基础设施(例如网桥)的吞吐量呢?

这个问题在20世纪80年代的悉尼变得非常突出,当时人们意识到必须增加海港过境的容量。解决方案是增加了名声略小的悉尼海港隧道( https://oreil.ly/1VWm7 ),它在海港下方基本遵循相同的路线。它提供了四个额外的车道,因此增加了大约三分之一的港口过境能力。在不太远的奥克兰 ,海港大桥( https://oreil.ly/E7yJz )也存在容量问题。它建于1959年,只有四条车道。本质上,奥克兰采用了与悉尼相同的解决方案,即增加容量。但是,奥克兰没有建造隧道,而是通过拓宽桥的两边,巧妙地将桥上的车道数量翻倍,并用有趣的名字“Nippon clip-ons” 来命名该桥。

这些例子说明了我们在软件系统中增加容量的第一个策略。我们基本上复制了软件中处理请求的资源,以提供更多的请求处理能力,从而提高吞吐量,如图1-1所示。复制的资源类似于桥梁上的车道,为到达的请求流量提供了一个几乎独立的处理路径。

幸运的是,在基于云的软件系统中,只需点击鼠标即可实现复制,我们可以有效地复制用来处理请求的资源,高达数千倍。在这方面,我们比桥梁建设者容易得多。尽管如此,我们仍需要保证“好钢用在刀刃上”,确保复制的资源会切实缓解实际的瓶颈。向未被淹没的路径添加容量将增加不必要的成本,也不会提供可扩展性优势。

图1-1:通过复制资源来增加容量

第二种可扩展性策略也可以通过桥的示例来说明。在悉尼,一些细心人士意识到,早上有更多的车辆从北向南过桥,而在下午则看到了相反的模式。因此想出了一个聪明的解决方案——在早上将更多的车道分配给高需求的方向,并在下午的某个时间进行切换。这在不分配任何新资源的情况下有效地增加了桥梁的容量——我们 优化了 (optimize)已有的可用资源。

我们可以在软件中遵循同样的方法来扩展系统。如果我们通过以下方法可以优化请求的处理,那么可以在不增加资源的情况下提高系统能力:使用更有效的算法;在数据库中添加额外的索引以加快查询速度;用更快的编程语言重写服务器。典型的例子是Facebook创建的用于PHP的HipHop(已停止维护; https://oreil.ly/d2JFX ),HipHop将PHP代码编译为C++,将Facebook的网页生成速度提高了6倍。

贯穿本书,我将重新审视这两个设计原则,即复制和优化。由于我们正在构建的是分布式系统,你将看到采用这两个设计原则会产生许多复杂的影响。分布式系统具有很多特性,使得构建可扩展系统变得 有意思 ,在这里,“有意思”既有积极的含义,也有消极的含义。

1.5 可扩展性与成本

让我们举一个简单的假设例子来观察可扩展性和成本之间的关系。假设我们有一个基于Web的系统(例如Web服务器和数据库),它可以处理100个并发请求,平均响应时间为1 s。我们有一个业务需求要求扩展这个系统,即在相同的响应时间下处理1000个并发请求。在不做任何改动的情况下,对系统进行简单的负载测试,性能如图1-2(左)所示。随着请求量的增加,我们看到平均响应时间稳步增长,在预期请求量下增长到10 s。显然,这个部署配置不能满足我们当前的要求,系统无法扩展。

图1-2:扩展应用程序;左侧表示不可扩展的性能,右侧表示可扩展的性能

为达到所需的性能,需要在工程方面做一些努力。图1-2(右)显示了修改后的系统性能。它现在能在特定的响应时间内处理1000个并发请求。因此,我们成功地扩展了系统。放松时间到了!

然而,一个重要问题迫在眉睫。需要多少努力和资源才能实现这一性能?也许只是在更强大的(虚拟)机器上运行Web服务器。在云上执行这类操作,即重新配置系统最多需要30 min。稍微复杂一点的是重新配置系统来运行多个Web服务器实例,以增加容量。上述两种方法只是对应用程序配置进行简单、低成本的更改,无须更改代码。这是极好的结果。

事实上,扩展系统并非如此简单。原因是多种多样的,可能是:

1.每秒1000个请求时,数据库的响应速度变慢,数据库服务器需要升级到新机器。

2.Web服务器动态生成大量内容,增加了相应请求量下的响应时间。一种可能的解决方案是更改代码,更有效地生成内容,减少每个请求的处理时间。

3.当许多请求尝试同时访问和更新相同的记录时,会在数据库中创建性能热点。这需要重新设计数据库schema并重新加载数据库,以及更改数据访问层的代码。

4.选择的Web服务器框架强调的是易于开发而不是易于扩展。它的执行模型意味着在该框架内根本无法通过扩展代码来满足高请求量的要求,需要使用其他框架甚至另一种编程语言来完全重写代码。

虽然还有无数潜在的其他原因,但我希望上面这些原因能说明当我们从选项1向选项4移动时,可能需要越来越多的努力。

现在让我们假设选项1,升级数据库服务器,需要15 h的工程努力和每月1000美元的额外云成本来获得更强大的服务器。这还不算太昂贵。让我们假设选项4,重写Web应用程序层,由于要用新的编程语言重构(例如,从Ruby向Java迁移),还需要10000 h的开发时间。选项2和选项3的成本介于选项1和选项4之间。10000 h的开发成本非常可观。更糟糕的是,在开发过程中,由于无法满足客户的访问需求,该应用程序可能会失去市场份额并因此失去资金。这些情况可能会导致系统和业务失败。

这个简单的场景说明了资源和成本如何与系统可扩展性密不可分。如果系统一开始的设计没有考虑可扩展性,那么为了满足新的访问需求,增加系统容量所需的下游成本和资源可能是巨大的。对于某些应用程序,需要承担修改业务系统来满足最终业务需求的成本,例如HealthCare.gov( https://oreil.ly/P7nyc ),付出了超过20亿美元的代价。对于其他系统,无法以低成本快速扩展,高昂代价可能就是它的“丧钟”,例如俄勒冈州的医疗保健交易所( https://oreil.ly/fTDcc ),扩展的成本为3.03亿美元。

我们永远不可能看到有人会尝试将郊区住宅的容量扩大成50层的办公楼。房子建筑的结构、材料和地基无法支撑这样的需求,如果不将它完全拆除和重建,这是一个遥不可及的目标。同样,我们不应该期望一个没有采用可扩展架构、机制和技术的软件系统快速发展以满足更大的容量需求。规模化的基础需要从一开始就建立好,而且要认识到组件将随着时间的推移而发展。采用促进可扩展性的设计和开发原则,我们可以更快速、更低成本地扩展系统以满足快速增长的需求。我将在本书的第二部分解释这些原则。

在成本线性增长的同时支持指数级扩展的软件系统被称为超大规模系统,我将其定义如下:“超大可扩展系统在计算和存储能力方面表现为指数级增长,同时在所需资源成本方面表现为线性增长,成本包括在构建、运维、支持和发展系统过程中所需的软件和硬件资源。”你可以在 https://oreil.ly/WwHqX 这篇文章中阅读有关超大规模系统的更多信息。

1.6 可扩展性与架构的权衡

可扩展性是非功能性需求,它只是众多质量属性之一——“非功能性需求”是软件架构领域的通用语。软件架构的长远复杂性问题之一是如何在质量属性之间做权衡。通常来说,有利于一种质量属性的设计可能会对其他质量属性产生负面或正面的影响。例如,我们可能希望在服务中发生某些事件时写入日志消息,以便取证和调试代码。但是,我们需要注意捕获了多少事件,因为写入日志记录会引入开销,对性能和成本产生负面影响。

经验丰富的软件架构师非常注意分寸,他们精心设计系统来满足高优先级的质量属性,同时尽量减少对其他质量属性的负面影响。

系统可扩展性也不例外。当我们将焦点放在系统的扩展能力上时,我们必须仔细考虑自己的设计如何影响其他非常理想的属性,例如性能、可用性、安全性以及经常被忽视的可管理性。我将在接下来的内容中简要讨论其中一些质量属性内在的权衡。

1.6.1 性能

有一种简单的方法可以体现系统性能和可扩展性之间的差异。当我们以性能为目标时,我们会尝试达到个别请求的一些期望指标,可能是平均响应时间小于2 s,或者是最坏情况下的性能目标,例如第99百分位响应时间小于3 s。

提高性能对可扩展性来说通常是一件好事。如果我们提高单个请求的性能,那么它会占用系统更少的容量,这有助于提高可扩展性,因为我们可以将用未使用的容量来处理更多请求。

然而,事情并不总是那么简单。我们可以通过多种方式缩短响应时间。我们可能会仔细优化代码,例如,删除不必要的对象复制,使用更快的JSON序列化库,甚至用更快的编程语言重写代码。这些方法能在不增加资源使用的情况下优化性能。

另一种方法可能是将经常访问的状态保留在内存中,避免每个请求都写入数据库,从而优化单个请求。消除数据库访问几乎总能加快速度。但是,如果我们的系统内存中长时间维护着大量状态,我们可能要(在访问量较多的系统中必须)仔细管理系统可以处理的请求数量。这可能会降低可扩展性,因为我们针对单个请求的优化方法比原始解决方案使用的资源(在本例中为内存)更多,从而降低了系统容量。

在本书中,我们将再次看到性能和可扩展性之间的这种紧张关系。事实上,有时让个别请求稍微慢一些是明智的,这样我们就可以利用额外的系统容量。我在下一章讨论负载均衡时,将提供一个很好的例子。

1.6.2 可用性

可用性和可扩展性通常是高度兼容的“合作伙伴”。当我们复制资源来扩展自己的系统时,我们创建了多个服务实例,可用于处理来自任何用户的请求。如果一个实例出现故障,则其他实例仍然可用。系统容量减少的原因只是应用程序出现故障或资源不可用。类似的想法也适用于复制网络链接、网络路由器、磁盘以及计算系统中的几乎所有资源。

涉及状态时,可扩展性和可用性会变得复杂。例如数据库,如果单个数据库服务器过载,我们可以复制它并向任一实例发送请求。这也提高了可用性,因为我们可以容忍一个实例的故障。如果数据库是只读的,则此方案非常有效。但是,一旦更新了一个实例,我们就必须弄清楚如何以及何时更新另一个实例。这是副本在一致性问题上影响很大的地方。

事实上,每当为了可扩展性和可用性而复制状态时,我们都必须处理一致性问题。在本书的第三部分讨论分布式数据库时,这是一个关键话题。

1.6.3 安全性

安全性是一个复杂的、技术性很强的话题,值得用一本书来探讨。没有人愿意使用不安全的系统,系统被黑客入侵和泄露用户数据都会导致CTO辞职,在极端情况下,公司甚至会倒闭。

安全系统的基本要素是身份验证、授权和数据完整性。我们需要确保数据不会在网络传输的过程中被截获,并且任何无权访问数据的人都无法访问静态数据(持久存储)。基本上,当我的信用卡号在系统之间进行传输或存储在公司的数据库中时,我不希望任何人看到它。

因此,安全性是所有面向互联网的系统的必要质量属性。构建安全系统的成本是不可避免的,让我们简要地研究一下这些成本是如何影响性能和可扩展性的。

在网络层,系统通常会采用运行在TCP/IP之上的TLS协议[传输层安全协议( https://oreil.ly/pG2eg ),参见第3章]。TLS使用非对称加密( https://oreil.ly/FqPSm )提供加密、身份验证和数据完整性能力。在建立安全连接过程中,由于双方都需要生成和交换密钥,所以会产生性能成本。TLS连接建立还包括交换证书来验证服务器(以及可选的客户端)的身份,以及选择一种算法来检查数据在传输过程中是否被篡改。建立连接后,传输中的数据将使用对称加密技术进行加密,由于现代CPU具有专用的加密硬件,因此这部分的性能损失可以忽略不计。建立连接通常需要在客户端和服务器之间进行两次消息交换,因此速度较慢。尽可能重用连接可以最大限度地减少性能开销。

保护静态数据有多种选择。SQL Server和Oracle等流行数据库引擎具有透明数据加密(TDE)等功能,可提供高效的文件级加密。金融等受监管行业越来越需要细粒度的加密机制,直至字段级别。云提供商也提供各种功能,确保数据存储在云数据存储服务中是安全的。静态安全数据的开销是实现安全性必须承担的成本——研究表明开销在5%~10%的范围内。

另一个关于安全的观点是CIA triad( https://oreil.ly/building-secure ),它代表 保密性 (Confidentiality)、 完整性 (Integrity)和 可用性 (Availability)。前两个和我上面描述的差不多。可用性是指系统即使在对手的攻击下也仍然保持可靠运行的能力。来自对手的攻击可能是试图利用系统设计弱点使系统瘫痪。另一种攻击则是经典的分布式拒绝服务(DDoS),在这种攻击中,对手获得了对众多系统和设备的控制权,并协同大量请求,使得受害系统不可用。

一般来说,安全性和可扩展性是对立的力量。安全必然会导致性能下降。系统包含的安全层越多,性能和可扩展性的负担就越大。这最终会影响成本底线——需要更强大和更昂贵的资源来实现系统的性能和可扩展性要求。

1.6.4 可管理性

随着我们构建的系统在交互中变得更加分散和复杂,对它们的管理和运营也变得越发重要。我们需要确保每个组件都按预期运行,同时性能继续达到预期。

我们用于构建系统的平台和技术提供了大量标准的专有监控工具,可用于实现上述目标。监控仪表板可用于检查每个系统组件的持续运行状况和行为。这些仪表板使用高度可定制和开放的工具构建(例如Grafana, https://oreil.ly/PNaBs ),可以显示系统指标,在系统出现需要操作员注意的各种阈值或事件时发送警报。用于描述这种复杂监控功能的术语是 可观测性 https://oreil.ly/xcuLd )。

工程师可以使用各种API来捕获系统的自定义指标,例如Java的MBean( https://oreil.ly/vtTUT )、AWS CloudWatch( https://oreil.ly/cvviZ )和Python的AppMetrics( https://oreil.ly/oa9MT )——一个典型的例子是捕获请求响应时间。使用这些API收集的指标,可以定制监控仪表板来显示实时图表和图形,从而深入了解系统的行为。指标对于洞察系统是非常宝贵的,它有助于确保持续运营,以及凸显系统中可能需要优化或复制的部分。

扩展系统总是意味着添加新的系统组件——硬件和软件。随着组件数量的增加,我们需要监控和管理更多活动部分。这绝不轻松,它增加了系统操作的复杂性和监控成本,这需要开发更多的监控代码来调整可观测平台,确保其监控了新的系统组件。

在扩展时,控制成本和可管理性的复杂性的唯一方法是自动化。这就是DevOps闪亮登场的时刻了。DevOps( https://oreil.ly/effective-devops )是一套集成软件开发与系统操作的实践和工具。DevOps自动执行系统的持续测试、部署、管理、升级和监控,缩短了新功能的开发生命周期。它是所有成功的可扩展系统的组成部分。

1.7 总结和延伸阅读

快速且低成本地扩展应用程序的能力应该是当代面向互联网的应用程序的软件架构的决定性质量。我们有两种实现可扩展性的基本方法,即增加系统容量(通常通过复制)和优化系统组件的性能。

像其他软件架构的质量属性一样,可扩展性不能孤立地实现。它不可避免地涉及复杂的权衡,需要根据应用程序的要求进行调整。在本书接下来的内容中,我将讨论基本质量属性之间的权衡。从下一章开始,我将描述实现可扩展架构的具体方法。

[1] Neil Ernst et al., Technical Debt in Practice:How to Find It and Fix It (MIT Press,2021).

[2] PB(PetaByte),拍字节,1 PB=1024 TB==2 50 字节。

[3] Ian Gorton et al., “Data-Intensive Computing in the 21st Century,” Computer 41,no. 4(April 2008):30-32.

[4] EB(ExaByte),艾字节,1 EB=1024 PB==2 60 字节。

[5] YB(YottaByte),尧字节,1 YB=1024 ZB==2 80 字节。

[6] Rachel Potvin and Josh Levenberg,“Why Google Stores Billions of Lines of Code in a Single Repository,” Communications of the ACM 59,7(July 2016):78-87. 376IrWLfHVbGGeVlfvaW1IbINe8cjwmQ2tUk697xsrTGZjtcczKDUJm4XaEam5RB

点击中间区域
呼出菜单
上一章
目录
下一章
×