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自动执行系统的持续测试、部署、管理、升级和监控,缩短了新功能的开发生命周期。它是所有成功的可扩展系统的组成部分。