mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
use pangu to update ch1's text spacing, update PR list
This commit is contained in:
parent
56e22d22e9
commit
d33ca8e671
@ -149,6 +149,8 @@
|
||||
|
||||
| ISSUE & Pull Requests | USER | Title |
|
||||
| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| [177](https://github.com/Vonng/ddia/pull/177) | [@haifeiWu](https://github.com/haifeiWu) | ch2: 语义网相关翻译更正 |
|
||||
| [176](https://github.com/Vonng/ddia/pull/176) | [@cwr31](https://github.com/cwr31) | ch7: 不变式相关翻译更正 |
|
||||
| [174](https://github.com/Vonng/ddia/pull/174) | [@BeBraveBeCurious](https://github.com/BeBraveBeCurious) | README & preface: 更正不正确的中文用词和标点符号 |
|
||||
| [173](https://github.com/Vonng/ddia/pull/173) | [@ZvanYang](https://github.com/ZvanYang) | ch12: 修正不完整的翻译 |
|
||||
| [171](https://github.com/Vonng/ddia/pull/171) | [@ZvanYang](https://github.com/ZvanYang) | ch12: 修正重复的译文 |
|
||||
|
168
ch1.md
168
ch1.md
@ -4,13 +4,13 @@
|
||||
|
||||
> 互联网做得太棒了,以至于大多数人将它看作像太平洋这样的自然资源,而不是什么人工产物。上一次出现这种大规模且无差错的技术, 你还记得是什么时候吗?
|
||||
>
|
||||
> —— [阿兰·凯](http://www.drdobbs.com/architecture-and-design/interview-with-alan-kay/240003442) 在接受Dobb博士杂志采访时说(2012年)
|
||||
> —— [艾伦・凯](http://www.drdobbs.com/architecture-and-design/interview-with-alan-kay/240003442) 在接受 Dobb 博士杂志采访时说(2012 年)
|
||||
|
||||
-----------------------
|
||||
|
||||
[TOC]
|
||||
|
||||
现今很多应用程序都是 **数据密集型(data-intensive)** 的,而非 **计算密集型(compute-intensive)** 的。因此CPU很少成为这类应用的瓶颈,更大的问题通常来自数据量、数据复杂性、以及数据的变更速度。
|
||||
现今很多应用程序都是 **数据密集型(data-intensive)** 的,而非 **计算密集型(compute-intensive)** 的。因此 CPU 很少成为这类应用的瓶颈,更大的问题通常来自数据量、数据复杂性、以及数据的变更速度。
|
||||
|
||||
数据密集型应用通常由标准组件构建而成,标准组件提供了很多通用的功能;例如,许多应用程序都需要:
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
## 关于数据系统的思考
|
||||
|
||||
我们通常认为,数据库、消息队列、缓存等工具分属于几个差异显著的类别。虽然数据库和消息队列表面上有一些相似性——它们都会存储一段时间的数据——但它们有迥然不同的访问模式,这意味着迥异的性能特征和实现手段。
|
||||
我们通常认为,数据库、消息队列、缓存等工具分属于几个差异显著的类别。虽然数据库和消息队列表面上有一些相似性 —— 它们都会存储一段时间的数据 —— 但它们有迥然不同的访问模式,这意味着迥异的性能特征和实现手段。
|
||||
|
||||
那我们为什么要把这些东西放在 **数据系统(data system)** 的总称之下混为一谈呢?
|
||||
|
||||
@ -39,15 +39,15 @@
|
||||
|
||||
其次,越来越多的应用程序有着各种严格而广泛的要求,单个工具不足以满足所有的数据处理和存储需求。取而代之的是,总体工作被拆分成一系列能被单个工具高效完成的任务,并通过应用代码将它们缝合起来。
|
||||
|
||||
例如,如果将缓存(应用管理的缓存层,Memcached或同类产品)和全文搜索(全文搜索服务器,例如Elasticsearch或Solr)功能从主数据库剥离出来,那么使缓存/索引与主数据库保持同步通常是应用代码的责任。[图1-1](img/fig1-1.png) 给出了这种架构可能的样子(细节将在后面的章节中详细介绍)。
|
||||
例如,如果将缓存(应用管理的缓存层,Memcached 或同类产品)和全文搜索(全文搜索服务器,例如 Elasticsearch 或 Solr)功能从主数据库剥离出来,那么使缓存 / 索引与主数据库保持同步通常是应用代码的责任。[图 1-1](img/fig1-1.png) 给出了这种架构可能的样子(细节将在后面的章节中详细介绍)。
|
||||
|
||||
![](img/fig1-1.png)
|
||||
|
||||
**图1-1 一个可能的组合使用多个组件的数据系统架构**
|
||||
**图 1-1 一个可能的组合使用多个组件的数据系统架构**
|
||||
|
||||
当你将多个工具组合在一起提供服务时,服务的接口或**应用程序编程接口(API, Application Programming Interface)** 通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的、专用的数据系统。这个新的复合数据系统可能会提供特定的保证,例如:缓存在写入时会作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。
|
||||
当你将多个工具组合在一起提供服务时,服务的接口或 **应用程序编程接口(API, Application Programming Interface)** 通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的、专用的数据系统。这个新的复合数据系统可能会提供特定的保证,例如:缓存在写入时会作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。
|
||||
|
||||
设计数据系统或服务时可能会遇到很多棘手的问题,例如:当系统出问题时,如何确保数据的正确性和完整性?当部分系统退化降级时,如何为客户提供始终如一的良好性能?当负载增加时,如何扩容应对?什么样的API才是好的API?
|
||||
设计数据系统或服务时可能会遇到很多棘手的问题,例如:当系统出问题时,如何确保数据的正确性和完整性?当部分系统退化降级时,如何为客户提供始终如一的良好性能?当负载增加时,如何扩容应对?什么样的 API 才是好的 API?
|
||||
|
||||
影响数据系统设计的因素很多,包括参与人员的技能和经验、历史遗留问题、系统路径依赖、交付时限、公司的风险容忍度、监管约束等,这些因素都需要具体问题具体分析。
|
||||
|
||||
@ -55,15 +55,15 @@
|
||||
|
||||
* 可靠性(Reliability)
|
||||
|
||||
系统在**困境**(adversity,比如硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。请参阅“[可靠性](#可靠性)”。
|
||||
系统在 **困境**(adversity,比如硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。请参阅 “[可靠性](#可靠性)”。
|
||||
|
||||
* 可伸缩性(Scalability)
|
||||
|
||||
有合理的办法应对系统的增长(数据量、流量、复杂性)。请参阅“[可伸缩性](#可伸缩性)”。
|
||||
有合理的办法应对系统的增长(数据量、流量、复杂性)。请参阅 “[可伸缩性](#可伸缩性)”。
|
||||
|
||||
* 可维护性(Maintainability)
|
||||
|
||||
许多不同的人(工程师、运维)在不同的生命周期,都能高效地在系统上工作(使系统保持现有行为,并适应新的应用场景)。请参阅“[可维护性](#可维护性)”。
|
||||
许多不同的人(工程师、运维)在不同的生命周期,都能高效地在系统上工作(使系统保持现有行为,并适应新的应用场景)。请参阅 “[可维护性](#可维护性)”。
|
||||
|
||||
人们经常追求这些词汇,却没有清楚理解它们到底意味着什么。为了工程的严谨性,本章的剩余部分将探讨可靠性、可伸缩性、可维护性的含义。为实现这些目标而使用的各种技术,架构和算法将在后续的章节中研究。
|
||||
|
||||
@ -77,65 +77,65 @@
|
||||
* 在预期的负载和数据量下,性能满足要求。
|
||||
* 系统能防止未经授权的访问和滥用。
|
||||
|
||||
如果所有这些在一起意味着“正确工作”,那么可以把可靠性粗略理解为“即使出现问题,也能继续正确工作”。
|
||||
如果所有这些在一起意味着 “正确工作”,那么可以把可靠性粗略理解为 “即使出现问题,也能继续正确工作”。
|
||||
|
||||
造成错误的原因叫做**故障(fault)**,能预料并应对故障的系统特性可称为**容错(fault-tolerant)** 或**韧性(resilient)**。“**容错**”一词可能会产生误导,因为它暗示着系统可以容忍所有可能的错误,但在实际中这是不可能的。比方说,如果整个地球(及其上的所有服务器)都被黑洞吞噬了,想要容忍这种错误,需要把网络托管到太空中——这种预算能不能批准就祝你好运了。所以在讨论容错时,只有谈论特定类型的错误才有意义。
|
||||
造成错误的原因叫做 **故障(fault)**,能预料并应对故障的系统特性可称为 **容错(fault-tolerant)** 或 **韧性(resilient)**。“**容错**” 一词可能会产生误导,因为它暗示着系统可以容忍所有可能的错误,但在实际中这是不可能的。比方说,如果整个地球(及其上的所有服务器)都被黑洞吞噬了,想要容忍这种错误,需要把网络托管到太空中 —— 这种预算能不能批准就祝你好运了。所以在讨论容错时,只有谈论特定类型的错误才有意义。
|
||||
|
||||
注意**故障(fault)** 不同于**失效(failure)**【2】。**故障**通常定义为系统的一部分状态偏离其标准,而**失效**则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制以防因**故障**而导致**失效**。本书中我们将介绍几种用不可靠的部件构建可靠系统的技术。
|
||||
注意 **故障(fault)** 不同于 **失效(failure)**【2】。**故障** 通常定义为系统的一部分状态偏离其标准,而 **失效** 则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制以防因 **故障** 而导致 **失效**。本书中我们将介绍几种用不可靠的部件构建可靠系统的技术。
|
||||
|
||||
反直觉的是,在这类容错系统中,通过故意触发来**提高**故障率是有意义的,例如:在没有警告的情况下随机地杀死单个进程。许多高危漏洞实际上是由糟糕的错误处理导致的【3】,因此我们可以通过故意引发故障来确保容错机制不断运行并接受考验,从而提高故障自然发生时系统能正确处理的信心。Netflix公司的*Chaos Monkey*【4】就是这种方法的一个例子。
|
||||
反直觉的是,在这类容错系统中,通过故意触发来 **提高** 故障率是有意义的,例如:在没有警告的情况下随机地杀死单个进程。许多高危漏洞实际上是由糟糕的错误处理导致的【3】,因此我们可以通过故意引发故障来确保容错机制不断运行并接受考验,从而提高故障自然发生时系统能正确处理的信心。Netflix 公司的 *Chaos Monkey*【4】就是这种方法的一个例子。
|
||||
|
||||
尽管比起**阻止错误(prevent error)**,我们通常更倾向于**容忍错误**。但也有**预防胜于治疗**的情况(比如不存在治疗方法时)。安全问题就属于这种情况。例如,如果攻击者破坏了系统,并获取了敏感数据,这种事是撤销不了的。但本书主要讨论的是可以恢复的故障种类,正如下面几节所述。
|
||||
尽管比起 **阻止错误(prevent error)**,我们通常更倾向于 **容忍错误**。但也有 **预防胜于治疗** 的情况(比如不存在治疗方法时)。安全问题就属于这种情况。例如,如果攻击者破坏了系统,并获取了敏感数据,这种事是撤销不了的。但本书主要讨论的是可以恢复的故障种类,正如下面几节所述。
|
||||
|
||||
### 硬件故障
|
||||
|
||||
当想到系统失效的原因时,**硬件故障(hardware faults)** 总会第一个进入脑海。硬盘崩溃、内存出错、机房断电、有人拔错网线……任何与大型数据中心打过交道的人都会告诉你:一旦你拥有很多机器,这些事情**总**会发生!
|
||||
当想到系统失效的原因时,**硬件故障(hardware faults)** 总会第一个进入脑海。硬盘崩溃、内存出错、机房断电、有人拔错网线…… 任何与大型数据中心打过交道的人都会告诉你:一旦你拥有很多机器,这些事情 **总** 会发生!
|
||||
|
||||
据报道称,硬盘的 **平均无故障时间(MTTF mean time to failure)** 约为10到50年【5】【6】。因此从数学期望上讲,在拥有10000个磁盘的存储集群上,平均每天会有1个磁盘出故障。
|
||||
据报道称,硬盘的 **平均无故障时间(MTTF, mean time to failure)** 约为 10 到 50 年【5】【6】。因此从数学期望上讲,在拥有 10000 个磁盘的存储集群上,平均每天会有 1 个磁盘出故障。
|
||||
|
||||
为了减少系统的故障率,第一反应通常都是增加单个硬件的冗余度,例如:磁盘可以组建RAID,服务器可能有双路电源和热插拔CPU,数据中心可能有电池和柴油发电机作为后备电源,某个组件挂掉时冗余组件可以立刻接管。这种方法虽然不能完全防止由硬件问题导致的系统失效,但它简单易懂,通常也足以让机器不间断运行很多年。
|
||||
为了减少系统的故障率,第一反应通常都是增加单个硬件的冗余度,例如:磁盘可以组建 RAID,服务器可能有双路电源和热插拔 CPU,数据中心可能有电池和柴油发电机作为后备电源,某个组件挂掉时冗余组件可以立刻接管。这种方法虽然不能完全防止由硬件问题导致的系统失效,但它简单易懂,通常也足以让机器不间断运行很多年。
|
||||
|
||||
直到最近,硬件冗余对于大多数应用来说已经足够了,它使单台机器完全失效变得相当罕见。只要你能快速地把备份恢复到新机器上,故障停机时间对大多数应用而言都算不上灾难性的。只有少量高可用性至关重要的应用才会要求有多套硬件冗余。
|
||||
|
||||
但是随着数据量和应用计算需求的增加,越来越多的应用开始大量使用机器,这会相应地增加硬件故障率。此外,在类似亚马逊AWS(Amazon Web Services)的一些云服务平台上,虚拟机实例不可用却没有任何警告也是很常见的【7】,因为云平台的设计就是优先考虑**灵活性(flexibility)** 和**弹性(elasticity)**[^i],而不是单机可靠性。
|
||||
但是随着数据量和应用计算需求的增加,越来越多的应用开始大量使用机器,这会相应地增加硬件故障率。此外,在类似亚马逊 AWS(Amazon Web Services)的一些云服务平台上,虚拟机实例不可用却没有任何警告也是很常见的【7】,因为云平台的设计就是优先考虑 **灵活性(flexibility)** 和 **弹性(elasticity)**[^i],而不是单机可靠性。
|
||||
|
||||
如果在硬件冗余的基础上进一步引入软件容错机制,那么系统在容忍整个(单台)机器故障的道路上就更进一步了。这样的系统也有运维上的便利,例如:如果需要重启机器(例如应用操作系统安全补丁),单服务器系统就需要计划停机。而允许机器失效的系统则可以一次修复一个节点,无需整个系统停机。
|
||||
|
||||
[^i]: 在[应对负载的方法](#应对负载的方法)一节定义
|
||||
[^i]: 在 [应对负载的方法](#应对负载的方法) 一节定义
|
||||
|
||||
### 软件错误
|
||||
|
||||
我们通常认为硬件故障是随机的、相互独立的:一台机器的磁盘失效并不意味着另一台机器的磁盘也会失效。大量硬件组件不可能同时发生故障,除非它们存在比较弱的相关性(同样的原因导致关联性错误,例如服务器机架的温度)。
|
||||
|
||||
另一类错误是内部的**系统性错误(systematic error)**【8】。这类错误难以预料,而且因为是跨节点相关的,所以比起不相关的硬件故障往往可能造成更多的**系统失效**【5】。例子包括:
|
||||
另一类错误是内部的 **系统性错误(systematic error)**【8】。这类错误难以预料,而且因为是跨节点相关的,所以比起不相关的硬件故障往往可能造成更多的 **系统失效**【5】。例子包括:
|
||||
|
||||
* 接受特定的错误输入,便导致所有应用服务器实例崩溃的BUG。例如2012年6月30日的闰秒,由于Linux内核中的一个错误【9】,许多应用同时挂掉了。
|
||||
* 失控进程会用尽一些共享资源,包括CPU时间、内存、磁盘空间或网络带宽。
|
||||
* 接受特定的错误输入,便导致所有应用服务器实例崩溃的 BUG。例如 2012 年 6 月 30 日的闰秒,由于 Linux 内核中的一个错误【9】,许多应用同时挂掉了。
|
||||
* 失控进程会用尽一些共享资源,包括 CPU 时间、内存、磁盘空间或网络带宽。
|
||||
* 系统依赖的服务变慢,没有响应,或者开始返回错误的响应。
|
||||
* 级联故障,一个组件中的小故障触发另一个组件中的故障,进而触发更多的故障【10】。
|
||||
|
||||
导致这类软件故障的BUG通常会潜伏很长时间,直到被异常情况触发为止。这种情况意味着软件对其环境做出了某种假设——虽然这种假设通常来说是正确的,但由于某种原因最后不再成立了【11】。
|
||||
导致这类软件故障的 BUG 通常会潜伏很长时间,直到被异常情况触发为止。这种情况意味着软件对其环境做出了某种假设 —— 虽然这种假设通常来说是正确的,但由于某种原因最后不再成立了【11】。
|
||||
|
||||
虽然软件中的系统性故障没有速效药,但我们还是有很多小办法,例如:仔细考虑系统中的假设和交互;彻底的测试;进程隔离;允许进程崩溃并重启;测量、监控并分析生产环境中的系统行为。如果系统能够提供一些保证(例如在一个消息队列中,进入与发出的消息数量相等),那么系统就可以在运行时不断自检,并在出现**差异(discrepancy)** 时报警【12】。
|
||||
虽然软件中的系统性故障没有速效药,但我们还是有很多小办法,例如:仔细考虑系统中的假设和交互;彻底的测试;进程隔离;允许进程崩溃并重启;测量、监控并分析生产环境中的系统行为。如果系统能够提供一些保证(例如在一个消息队列中,进入与发出的消息数量相等),那么系统就可以在运行时不断自检,并在出现 **差异(discrepancy)** 时报警【12】。
|
||||
|
||||
### 人为错误
|
||||
|
||||
设计并构建了软件系统的工程师是人类,维持系统运行的运维也是人类。即使他们怀有最大的善意,人类也是不可靠的。举个例子,一项关于大型互联网服务的研究发现,运维配置错误是导致服务中断的首要原因,而硬件故障(服务器或网络)仅导致了10-25%的服务中断【13】。
|
||||
设计并构建了软件系统的工程师是人类,维持系统运行的运维也是人类。即使他们怀有最大的善意,人类也是不可靠的。举个例子,一项关于大型互联网服务的研究发现,运维配置错误是导致服务中断的首要原因,而硬件故障(服务器或网络)仅导致了 10-25% 的服务中断【13】。
|
||||
|
||||
尽管人类不可靠,但怎么做才能让系统变得可靠?最好的系统会组合使用以下几种办法:
|
||||
|
||||
* 以最小化犯错机会的方式设计系统。例如,精心设计的抽象、API和管理后台使做对事情更容易,搞砸事情更困难。但如果接口限制太多,人们就会忽略它们的好处而想办法绕开。很难正确把握这种微妙的平衡。
|
||||
* 将人们最容易犯错的地方与可能导致失效的地方**解耦(decouple)**。特别是提供一个功能齐全的非生产环境**沙箱(sandbox)**,使人们可以在不影响真实用户的情况下,使用真实数据安全地探索和实验。
|
||||
* 在各个层次进行彻底的测试【3】,从单元测试、全系统集成测试到手动测试。自动化测试易于理解,已经被广泛使用,特别适合用来覆盖正常情况中少见的**边缘场景(corner case)**。
|
||||
* 以最小化犯错机会的方式设计系统。例如,精心设计的抽象、API 和管理后台使做对事情更容易,搞砸事情更困难。但如果接口限制太多,人们就会忽略它们的好处而想办法绕开。很难正确把握这种微妙的平衡。
|
||||
* 将人们最容易犯错的地方与可能导致失效的地方 **解耦(decouple)**。特别是提供一个功能齐全的非生产环境 **沙箱(sandbox)**,使人们可以在不影响真实用户的情况下,使用真实数据安全地探索和实验。
|
||||
* 在各个层次进行彻底的测试【3】,从单元测试、全系统集成测试到手动测试。自动化测试易于理解,已经被广泛使用,特别适合用来覆盖正常情况中少见的 **边缘场景(corner case)**。
|
||||
* 允许从人为错误中简单快速地恢复,以最大限度地减少失效情况带来的影响。 例如,快速回滚配置变更,分批发布新代码(以便任何意外错误只影响一小部分用户),并提供数据重算工具(以备旧的计算出错)。
|
||||
* 配置详细和明确的监控,比如性能指标和错误率。 在其他工程学科中这指的是**遥测(telemetry)**(一旦火箭离开了地面,遥测技术对于跟踪发生的事情和理解失败是至关重要的)。监控可以向我们发出预警信号,并允许我们检查是否有任何地方违反了假设和约束。当出现问题时,指标数据对于问题诊断是非常宝贵的。
|
||||
* 良好的管理实践与充分的培训——一个复杂而重要的方面,但超出了本书的范围。
|
||||
* 配置详细和明确的监控,比如性能指标和错误率。 在其他工程学科中这指的是 **遥测(telemetry)**(一旦火箭离开了地面,遥测技术对于跟踪发生的事情和理解失败是至关重要的)。监控可以向我们发出预警信号,并允许我们检查是否有任何地方违反了假设和约束。当出现问题时,指标数据对于问题诊断是非常宝贵的。
|
||||
* 良好的管理实践与充分的培训 —— 一个复杂而重要的方面,但超出了本书的范围。
|
||||
|
||||
### 可靠性有多重要?
|
||||
|
||||
可靠性不仅仅是针对核电站和空中交通管制软件而言,我们也期望更多平凡的应用能可靠地运行。商务应用中的错误会导致生产力损失(也许数据报告不完整还会有法律风险),而电商网站的中断则可能会导致收入和声誉的巨大损失。
|
||||
|
||||
即使在“非关键”应用中,我们也对用户负有责任。试想一位家长把所有的照片和孩子的视频储存在你的照片应用里【15】。如果数据库突然损坏,他们会感觉如何?他们可能会知道如何从备份恢复吗?
|
||||
即使在 “非关键” 应用中,我们也对用户负有责任。试想一位家长把所有的照片和孩子的视频储存在你的照片应用里【15】。如果数据库突然损坏,他们会感觉如何?他们可能会知道如何从备份恢复吗?
|
||||
|
||||
在某些情况下,我们可能会选择牺牲可靠性来降低开发成本(例如为未经证实的市场开发产品原型)或运营成本(例如利润率极低的服务),但我们偷工减料时,应该清楚意识到自己在做什么。
|
||||
|
||||
@ -144,29 +144,29 @@
|
||||
|
||||
系统今天能可靠运行,并不意味未来也能可靠运行。服务 **降级(degradation)** 的一个常见原因是负载增加,例如:系统负载已经从一万个并发用户增长到十万个并发用户,或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多。
|
||||
|
||||
**可伸缩性(Scalability)** 是用来描述系统应对负载增长能力的术语。但是请注意,这不是贴在系统上的一维标签:说“X可伸缩”或“Y不可伸缩”是没有任何意义的。相反,讨论可伸缩性意味着考虑诸如“如果系统以特定方式增长,有什么选项可以应对增长?”和“如何增加计算资源来处理额外的负载?”等问题。
|
||||
**可伸缩性(Scalability)** 是用来描述系统应对负载增长能力的术语。但是请注意,这不是贴在系统上的一维标签:说 “X 可伸缩” 或 “Y 不可伸缩” 是没有任何意义的。相反,讨论可伸缩性意味着考虑诸如 “如果系统以特定方式增长,有什么选项可以应对增长?” 和 “如何增加计算资源来处理额外的负载?” 等问题。
|
||||
|
||||
### 描述负载
|
||||
|
||||
在讨论增长问题(如果负载加倍会发生什么?)前,首先要能简要描述系统的当前负载。负载可以用一些称为 **负载参数(load parameters)** 的数字来描述。参数的最佳选择取决于系统架构,它可能是每秒向Web服务器发出的请求、数据库中的读写比率、聊天室中同时活跃的用户数量、缓存命中率或其他东西。除此之外,也许平均情况对你很重要,也许你的瓶颈是少数极端场景。
|
||||
在讨论增长问题(如果负载加倍会发生什么?)前,首先要能简要描述系统的当前负载。负载可以用一些称为 **负载参数(load parameters)** 的数字来描述。参数的最佳选择取决于系统架构,它可能是每秒向 Web 服务器发出的请求、数据库中的读写比率、聊天室中同时活跃的用户数量、缓存命中率或其他东西。除此之外,也许平均情况对你很重要,也许你的瓶颈是少数极端场景。
|
||||
|
||||
为了使这个概念更加具体,我们以推特在2012年11月发布的数据【16】为例。推特的两个主要业务是:
|
||||
为了使这个概念更加具体,我们以推特在 2012 年 11 月发布的数据【16】为例。推特的两个主要业务是:
|
||||
|
||||
* 发布推文
|
||||
|
||||
用户可以向其粉丝发布新消息(平均 4.6k请求/秒,峰值超过 12k请求/秒)。
|
||||
用户可以向其粉丝发布新消息(平均 4.6k 请求 / 秒,峰值超过 12k 请求 / 秒)。
|
||||
|
||||
* 主页时间线
|
||||
|
||||
用户可以查阅他们关注的人发布的推文(300k请求/秒)。
|
||||
用户可以查阅他们关注的人发布的推文(300k 请求 / 秒)。
|
||||
|
||||
处理每秒12,000次写入(发推文的速率峰值)还是很简单的。然而推特的伸缩性挑战并不是主要来自推特量,而是来自**扇出(fan-out)**[^ii]——每个用户关注了很多人,也被很多人关注。
|
||||
处理每秒 12,000 次写入(发推文的速率峰值)还是很简单的。然而推特的伸缩性挑战并不是主要来自推特量,而是来自 **扇出(fan-out)**[^ii]—— 每个用户关注了很多人,也被很多人关注。
|
||||
|
||||
[^ii]: 扇出:从电子工程学中借用的术语,它描述了输入连接到另一个门输出的逻辑门数量。 输出需要提供足够的电流来驱动所有连接的输入。 在事务处理系统中,我们使用它来描述为了服务一个传入请求而需要执行其他服务的请求数量。
|
||||
|
||||
大体上讲,这一对操作有两种实现方式。
|
||||
|
||||
1. 发布推文时,只需将新推文插入全局推文集合即可。当一个用户请求自己的主页时间线时,首先查找他关注的所有人,查询这些被关注用户发布的推文并按时间顺序合并。在如[图1-2](img/fig1-2.png)所示的关系型数据库中,可以编写这样的查询:
|
||||
1. 发布推文时,只需将新推文插入全局推文集合即可。当一个用户请求自己的主页时间线时,首先查找他关注的所有人,查询这些被关注用户发布的推文并按时间顺序合并。在如 [图 1-2](img/fig1-2.png) 所示的关系型数据库中,可以编写这样的查询:
|
||||
|
||||
```sql
|
||||
SELECT tweets.*, users.*
|
||||
@ -177,21 +177,21 @@
|
||||
```
|
||||
![](img/fig1-2.png)
|
||||
|
||||
**图1-2 推特主页时间线的关系型模式简单实现**
|
||||
**图 1-2 推特主页时间线的关系型模式简单实现**
|
||||
|
||||
2. 为每个用户的主页时间线维护一个缓存,就像每个用户的推文收件箱([图1-3](img/fig1-3.png))。 当一个用户发布推文时,查找所有关注该用户的人,并将新的推文插入到每个主页时间线缓存中。 因此读取主页时间线的请求开销很小,因为结果已经提前计算好了。
|
||||
2. 为每个用户的主页时间线维护一个缓存,就像每个用户的推文收件箱([图 1-3](img/fig1-3.png))。 当一个用户发布推文时,查找所有关注该用户的人,并将新的推文插入到每个主页时间线缓存中。 因此读取主页时间线的请求开销很小,因为结果已经提前计算好了。
|
||||
|
||||
![](img/fig1-3.png)
|
||||
|
||||
**图1-3 用于分发推特至关注者的数据流水线,2012年11月的负载参数【16】**
|
||||
**图 1-3 用于分发推特至关注者的数据流水线,2012 年 11 月的负载参数【16】**
|
||||
|
||||
推特的第一个版本使用了方法1,但系统很难跟上主页时间线查询的负载。所以公司转向了方法2,方法2的效果更好,因为发推频率比查询主页时间线的频率几乎低了两个数量级,所以在这种情况下,最好在写入时做更多的工作,而在读取时做更少的工作。
|
||||
推特的第一个版本使用了方法 1,但系统很难跟上主页时间线查询的负载。所以公司转向了方法 2,方法 2 的效果更好,因为发推频率比查询主页时间线的频率几乎低了两个数量级,所以在这种情况下,最好在写入时做更多的工作,而在读取时做更少的工作。
|
||||
|
||||
然而方法2的缺点是,发推现在需要大量的额外工作。平均来说,一条推文会发往约75个关注者,所以每秒4.6k的发推写入,变成了对主页时间线缓存每秒345k的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实,一些用户有超过3000万的粉丝,这意味着一条推文就可能会导致主页时间线缓存的3000万次写入!及时完成这种操作是一个巨大的挑战 —— 推特尝试在5秒内向粉丝发送推文。
|
||||
然而方法 2 的缺点是,发推现在需要大量的额外工作。平均来说,一条推文会发往约 75 个关注者,所以每秒 4.6k 的发推写入,变成了对主页时间线缓存每秒 345k 的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实,一些用户有超过 3000 万的粉丝,这意味着一条推文就可能会导致主页时间线缓存的 3000 万次写入!及时完成这种操作是一个巨大的挑战 —— 推特尝试在 5 秒内向粉丝发送推文。
|
||||
|
||||
在推特的例子中,每个用户粉丝数的分布(可能按这些用户的发推频率来加权)是探讨可伸缩性的一个关键负载参数,因为它决定了扇出负载。你的应用程序可能具有非常不同的特征,但可以采用相似的原则来考虑它的负载。
|
||||
|
||||
推特轶事的最终转折:现在已经稳健地实现了方法2,推特逐步转向了两种方法的混合。大多数用户发的推文会被扇出写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户(即名流)会被排除在外。当用户读取主页时间线时,分别地获取出该用户所关注的每位名流的推文,再与用户的主页时间线缓存合并,如方法1所示。这种混合方法能始终如一地提供良好性能。在[第十二章](ch12.md)中我们将重新讨论这个例子,这在覆盖更多技术层面之后。
|
||||
推特轶事的最终转折:现在已经稳健地实现了方法 2,推特逐步转向了两种方法的混合。大多数用户发的推文会被扇出写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户(即名流)会被排除在外。当用户读取主页时间线时,分别地获取出该用户所关注的每位名流的推文,再与用户的主页时间线缓存合并,如方法 1 所示。这种混合方法能始终如一地提供良好性能。在 [第十二章](ch12.md) 中我们将重新讨论这个例子,这在覆盖更多技术层面之后。
|
||||
|
||||
### 描述性能
|
||||
|
||||
@ -202,71 +202,71 @@
|
||||
|
||||
这两个问题都需要性能数据,所以让我们简单地看一下如何描述系统性能。
|
||||
|
||||
对于Hadoop这样的批处理系统,通常关心的是**吞吐量(throughput)**,即每秒可以处理的记录数量,或者在特定规模数据集上运行作业的总时间[^iii]。对于在线系统,通常更重要的是服务的**响应时间(response time)**,即客户端发送请求到接收响应之间的时间。
|
||||
对于 Hadoop 这样的批处理系统,通常关心的是 **吞吐量(throughput)**,即每秒可以处理的记录数量,或者在特定规模数据集上运行作业的总时间 [^iii]。对于在线系统,通常更重要的是服务的 **响应时间(response time)**,即客户端发送请求到接收响应之间的时间。
|
||||
|
||||
[^iii]: 理想情况下,批量作业的运行时间是数据集的大小除以吞吐量。 在实践中由于数据倾斜(数据不是均匀分布在每个工作进程中),需要等待最慢的任务完成,所以运行时间往往更长。
|
||||
|
||||
> #### 延迟和响应时间
|
||||
>
|
||||
> **延迟(latency)** 和 **响应时间(response time)** 经常用作同义词,但实际上它们并不一样。响应时间是客户所看到的,除了实际处理请求的时间( **服务时间(service time)** )之外,还包括网络延迟和排队延迟。延迟是某个请求等待处理的**持续时长**,在此期间它处于 **休眠(latent)** 状态,并等待服务【17】。
|
||||
> **延迟(latency)** 和 **响应时间(response time)** 经常用作同义词,但实际上它们并不一样。响应时间是客户所看到的,除了实际处理请求的时间( **服务时间(service time)** )之外,还包括网络延迟和排队延迟。延迟是某个请求等待处理的 **持续时长**,在此期间它处于 **休眠(latent)** 状态,并等待服务【17】。
|
||||
|
||||
即使不断重复发送同样的请求,每次得到的响应时间也都会略有不同。现实世界的系统会处理各式各样的请求,响应时间可能会有很大差异。因此我们需要将响应时间视为一个可以测量的数值**分布(distribution)**,而不是单个数值。
|
||||
即使不断重复发送同样的请求,每次得到的响应时间也都会略有不同。现实世界的系统会处理各式各样的请求,响应时间可能会有很大差异。因此我们需要将响应时间视为一个可以测量的数值 **分布(distribution)**,而不是单个数值。
|
||||
|
||||
在[图1-4](img/fig1-4.png)中,每个灰条代表一次对服务的请求,其高度表示请求花费了多长时间。大多数请求是相当快的,但偶尔会出现需要更长的时间的异常值。这也许是因为缓慢的请求实质上开销更大,例如它们可能会处理更多的数据。但即使(你认为)所有请求都花费相同时间的情况下,随机的附加延迟也会导致结果变化,例如:上下文切换到后台进程,网络数据包丢失与TCP重传,垃圾收集暂停,强制从磁盘读取的页面错误,服务器机架中的震动【18】,还有很多其他原因。
|
||||
在 [图 1-4](img/fig1-4.png) 中,每个灰条代表一次对服务的请求,其高度表示请求花费了多长时间。大多数请求是相当快的,但偶尔会出现需要更长的时间的异常值。这也许是因为缓慢的请求实质上开销更大,例如它们可能会处理更多的数据。但即使(你认为)所有请求都花费相同时间的情况下,随机的附加延迟也会导致结果变化,例如:上下文切换到后台进程,网络数据包丢失与 TCP 重传,垃圾收集暂停,强制从磁盘读取的页面错误,服务器机架中的震动【18】,还有很多其他原因。
|
||||
|
||||
![](img/fig1-4.png)
|
||||
|
||||
**图1-4 展示了一个服务100次请求响应时间的均值与百分位数**
|
||||
**图 1-4 展示了一个服务 100 次请求响应时间的均值与百分位数**
|
||||
|
||||
通常报表都会展示服务的平均响应时间。 (严格来讲“平均”一词并不指代任何特定公式,但实际上它通常被理解为**算术平均值(arithmetic mean)**:给定 n 个值,加起来除以 n )。然而如果你想知道“**典型(typical)**”响应时间,那么平均值并不是一个非常好的指标,因为它不能告诉你有多少用户实际上经历了这个延迟。
|
||||
通常报表都会展示服务的平均响应时间。 (严格来讲 “平均” 一词并不指代任何特定公式,但实际上它通常被理解为 **算术平均值(arithmetic mean)**:给定 n 个值,加起来除以 n )。然而如果你想知道 “**典型(typical)**” 响应时间,那么平均值并不是一个非常好的指标,因为它不能告诉你有多少用户实际上经历了这个延迟。
|
||||
|
||||
通常使用**百分位点(percentiles)** 会更好。如果将响应时间列表按最快到最慢排序,那么**中位数(median)** 就在正中间:举个例子,如果你的响应时间中位数是200毫秒,这意味着一半请求的返回时间少于200毫秒,另一半比这个要长。
|
||||
通常使用 **百分位点(percentiles)** 会更好。如果将响应时间列表按最快到最慢排序,那么 **中位数(median)** 就在正中间:举个例子,如果你的响应时间中位数是 200 毫秒,这意味着一半请求的返回时间少于 200 毫秒,另一半比这个要长。
|
||||
|
||||
如果想知道典型场景下用户需要等待多长时间,那么中位数是一个好的度量标准:一半用户请求的响应时间少于响应时间的中位数,另一半服务时间比中位数长。中位数也被称为第50百分位点,有时缩写为p50。注意中位数是关于单个请求的;如果用户同时发出几个请求(在一个会话过程中,或者由于一个页面中包含了多个资源),则至少一个请求比中位数慢的概率远大于50%。
|
||||
如果想知道典型场景下用户需要等待多长时间,那么中位数是一个好的度量标准:一半用户请求的响应时间少于响应时间的中位数,另一半服务时间比中位数长。中位数也被称为第 50 百分位点,有时缩写为 p50。注意中位数是关于单个请求的;如果用户同时发出几个请求(在一个会话过程中,或者由于一个页面中包含了多个资源),则至少一个请求比中位数慢的概率远大于 50%。
|
||||
|
||||
为了弄清异常值有多糟糕,可以看看更高的百分位点,例如第95、99和99.9百分位点(缩写为p95,p99和p999)。它们意味着95%、99%或99.9%的请求响应时间要比该阈值快,例如:如果第95百分位点响应时间是1.5秒,则意味着100个请求中的95个响应时间快于1.5秒,而100个请求中的5个响应时间超过1.5秒。如[图1-4](img/fig1-4.png)所示。
|
||||
为了弄清异常值有多糟糕,可以看看更高的百分位点,例如第 95、99 和 99.9 百分位点(缩写为 p95,p99 和 p999)。它们意味着 95%、99% 或 99.9% 的请求响应时间要比该阈值快,例如:如果第 95 百分位点响应时间是 1.5 秒,则意味着 100 个请求中的 95 个响应时间快于 1.5 秒,而 100 个请求中的 5 个响应时间超过 1.5 秒。如 [图 1-4](img/fig1-4.png) 所示。
|
||||
|
||||
响应时间的高百分位点(也称为**尾部延迟**,即**tail latencies**)非常重要,因为它们直接影响用户的服务体验。例如亚马逊在描述内部服务的响应时间要求时以99.9百分位点为准,即使它只影响一千个请求中的一个。这是因为请求响应最慢的客户往往也是数据最多的客户,也可以说是最有价值的客户 —— 因为他们掏钱了【19】。保证网站响应迅速对于保持客户的满意度非常重要,亚马逊观察到:响应时间增加100毫秒,销售量就减少1%【20】;而另一些报告说:慢 1 秒钟会让客户满意度指标减少16%【21,22】。
|
||||
响应时间的高百分位点(也称为 **尾部延迟**,即 **tail latencies**)非常重要,因为它们直接影响用户的服务体验。例如亚马逊在描述内部服务的响应时间要求时以 99.9 百分位点为准,即使它只影响一千个请求中的一个。这是因为请求响应最慢的客户往往也是数据最多的客户,也可以说是最有价值的客户 —— 因为他们掏钱了【19】。保证网站响应迅速对于保持客户的满意度非常重要,亚马逊观察到:响应时间增加 100 毫秒,销售量就减少 1%【20】;而另一些报告说:慢 1 秒钟会让客户满意度指标减少 16%【21,22】。
|
||||
|
||||
另一方面,优化第99.99百分位点(一万个请求中最慢的一个)被认为太昂贵了,不能为亚马逊的目标带来足够好处。减小高百分位点处的响应时间相当困难,因为它很容易受到随机事件的影响,这超出了控制范围,而且效益也很小。
|
||||
另一方面,优化第 99.99 百分位点(一万个请求中最慢的一个)被认为太昂贵了,不能为亚马逊的目标带来足够好处。减小高百分位点处的响应时间相当困难,因为它很容易受到随机事件的影响,这超出了控制范围,而且效益也很小。
|
||||
|
||||
百分位点通常用于**服务级别目标(SLO, service level objectives)** 和**服务级别协议(SLA, service level agreements)**,即定义服务预期性能和可用性的合同。 SLA可能会声明,如果服务响应时间的中位数小于200毫秒,且99.9百分位点低于1秒,则认为服务工作正常(如果响应时间更长,就认为服务不达标)。这些指标为客户设定了期望值,并允许客户在SLA未达标的情况下要求退款。
|
||||
百分位点通常用于 **服务级别目标(SLO, service level objectives)** 和 **服务级别协议(SLA, service level agreements)**,即定义服务预期性能和可用性的合同。 SLA 可能会声明,如果服务响应时间的中位数小于 200 毫秒,且 99.9 百分位点低于 1 秒,则认为服务工作正常(如果响应时间更长,就认为服务不达标)。这些指标为客户设定了期望值,并允许客户在 SLA 未达标的情况下要求退款。
|
||||
|
||||
**排队延迟(queueing delay)** 通常占了高百分位点处响应时间的很大一部分。由于服务器只能并行处理少量的事务(如受其CPU核数的限制),所以只要有少量缓慢的请求就能阻碍后续请求的处理,这种效应有时被称为 **头部阻塞(head-of-line blocking)** 。即使后续请求在服务器上处理的非常迅速,由于需要等待先前请求完成,客户端最终看到的是缓慢的总体响应时间。因为存在这种效应,测量客户端的响应时间非常重要。
|
||||
**排队延迟(queueing delay)** 通常占了高百分位点处响应时间的很大一部分。由于服务器只能并行处理少量的事务(如受其 CPU 核数的限制),所以只要有少量缓慢的请求就能阻碍后续请求的处理,这种效应有时被称为 **头部阻塞(head-of-line blocking)** 。即使后续请求在服务器上处理的非常迅速,由于需要等待先前请求完成,客户端最终看到的是缓慢的总体响应时间。因为存在这种效应,测量客户端的响应时间非常重要。
|
||||
|
||||
为测试系统的可伸缩性而人为产生负载时,产生负载的客户端要独立于响应时间不断发送请求。如果客户端在发送下一个请求之前等待先前的请求完成,这种行为会产生人为排队的效果,使得测试时的队列比现实情况更短,使测量结果产生偏差【23】。
|
||||
|
||||
> #### 实践中的百分位点
|
||||
>
|
||||
> 在多重调用的后端服务里,高百分位数变得特别重要。即使并行调用,最终用户请求仍然需要等待最慢的并行调用完成。如[图1-5](img/fig1-5.png)所示,只需要一个缓慢的调用就可以使整个最终用户请求变慢。即使只有一小部分后端调用速度较慢,如果最终用户请求需要多个后端调用,则获得较慢调用的机会也会增加,因此较高比例的最终用户请求速度会变慢(效果称为尾部延迟放大【24】)。
|
||||
> 在多重调用的后端服务里,高百分位数变得特别重要。即使并行调用,最终用户请求仍然需要等待最慢的并行调用完成。如 [图 1-5](img/fig1-5.png) 所示,只需要一个缓慢的调用就可以使整个最终用户请求变慢。即使只有一小部分后端调用速度较慢,如果最终用户请求需要多个后端调用,则获得较慢调用的机会也会增加,因此较高比例的最终用户请求速度会变慢(效果称为尾部延迟放大【24】)。
|
||||
>
|
||||
> 如果你想将响应时间百分点添加到你的服务的监视仪表板,则需要持续有效地计算它们。例如,你可能希望在最近10分钟内保持请求响应时间的滚动窗口。每一分钟,你都会计算出该窗口中的中值和各种百分数,并将这些度量值绘制在图上。
|
||||
> 如果你想将响应时间百分点添加到你的服务的监视仪表板,则需要持续有效地计算它们。例如,你可能希望在最近 10 分钟内保持请求响应时间的滚动窗口。每一分钟,你都会计算出该窗口中的中值和各种百分数,并将这些度量值绘制在图上。
|
||||
>
|
||||
> 简单的实现是在时间窗口内保存所有请求的响应时间列表,并且每分钟对列表进行排序。如果对你来说效率太低,那么有一些算法能够以最小的CPU和内存成本(如前向衰减【25】、t-digest【26】或HdrHistogram 【27】)来计算百分位数的近似值。请注意,平均百分比(例如,减少时间分辨率或合并来自多台机器的数据)在数学上没有意义 - 聚合响应时间数据的正确方法是添加直方图【28】。
|
||||
> 简单的实现是在时间窗口内保存所有请求的响应时间列表,并且每分钟对列表进行排序。如果对你来说效率太低,那么有一些算法能够以最小的 CPU 和内存成本(如前向衰减【25】、t-digest【26】或 HdrHistogram 【27】)来计算百分位数的近似值。请注意,平均百分比(例如,减少时间分辨率或合并来自多台机器的数据)在数学上没有意义 - 聚合响应时间数据的正确方法是添加直方图【28】。
|
||||
|
||||
![](img/fig1-5.png)
|
||||
|
||||
**图1-5 当一个请求需要多个后端请求时,单个后端慢请求就会拖慢整个终端用户的请求**
|
||||
**图 1-5 当一个请求需要多个后端请求时,单个后端慢请求就会拖慢整个终端用户的请求**
|
||||
|
||||
### 应对负载的方法
|
||||
|
||||
现在我们已经讨论了用于描述负载的参数和用于衡量性能的指标。可以开始认真讨论可伸缩性了:当负载参数增加时,如何保持良好的性能?
|
||||
|
||||
适应某个级别负载的架构不太可能应付10倍于此的负载。如果你正在开发一个快速增长的服务,那么每次负载发生数量级的增长时,你可能都需要重新考虑架构——或者更频繁。
|
||||
适应某个级别负载的架构不太可能应付 10 倍于此的负载。如果你正在开发一个快速增长的服务,那么每次负载发生数量级的增长时,你可能都需要重新考虑架构 —— 或者更频繁。
|
||||
|
||||
人们经常讨论**纵向伸缩**(scaling up,也称为垂直伸缩,即vertical scaling,转向更强大的机器)和**横向伸缩**(scaling out,也称为水平伸缩,即horizontal scaling,将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“**无共享(shared-nothing)**”架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向伸缩。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。
|
||||
人们经常讨论 **纵向伸缩**(scaling up,也称为垂直伸缩,即 vertical scaling,转向更强大的机器)和 **横向伸缩**(scaling out,也称为水平伸缩,即 horizontal scaling,将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为 “**无共享(shared-nothing)**” 架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向伸缩。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。
|
||||
|
||||
有些系统是 **弹性(elastic)** 的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动伸缩(人工分析容量并决定向系统添加更多的机器)。如果负载**极难预测(highly unpredictable)**,则弹性系统可能很有用,但手动伸缩系统更简单,并且意外操作可能会更少(请参阅“[分区再平衡](ch6.md#分区再平衡)”)。
|
||||
有些系统是 **弹性(elastic)** 的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动伸缩(人工分析容量并决定向系统添加更多的机器)。如果负载 **极难预测(highly unpredictable)**,则弹性系统可能很有用,但手动伸缩系统更简单,并且意外操作可能会更少(请参阅 “[分区再平衡](ch6.md# 分区再平衡)”)。
|
||||
|
||||
跨多台机器部署 **无状态服务(stateless services)** 非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向伸缩),直到伸缩成本或可用性需求迫使其改为分布式。
|
||||
|
||||
随着分布式系统的工具和抽象越来越好,至少对于某些类型的应用而言,这种常识可能会改变。可以预见分布式数据系统将成为未来的默认设置,即使对不处理大量数据或流量的场景也如此。本书的其余部分将介绍多种分布式数据系统,不仅讨论它们在可伸缩性方面的表现,还包括易用性和可维护性。
|
||||
|
||||
大规模的系统架构通常是应用特定的—— 没有一招鲜吃遍天的通用可伸缩架构(不正式的叫法:**万金油(magic scaling sauce)** )。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。
|
||||
大规模的系统架构通常是应用特定的 —— 没有一招鲜吃遍天的通用可伸缩架构(不正式的叫法:**万金油(magic scaling sauce)** )。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。
|
||||
|
||||
举个例子,用于处理每秒十万个请求(每个大小为1 kB)的系统与用于处理每分钟3个请求(每个大小为2GB)的系统看上去会非常不一样,尽管两个系统有同样的数据吞吐量。
|
||||
举个例子,用于处理每秒十万个请求(每个大小为 1 kB)的系统与用于处理每分钟 3 个请求(每个大小为 2GB)的系统看上去会非常不一样,尽管两个系统有同样的数据吞吐量。
|
||||
|
||||
一个良好适配应用的可伸缩架构,是围绕着**假设(assumption)** 建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为伸缩所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可伸缩至未来的假想负载要重要的多。
|
||||
一个良好适配应用的可伸缩架构,是围绕着 **假设(assumption)** 建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为伸缩所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可伸缩至未来的假想负载要重要的多。
|
||||
|
||||
尽管这些架构是应用程序特定的,但可伸缩的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。
|
||||
|
||||
@ -275,7 +275,7 @@
|
||||
|
||||
众所周知,软件的大部分开销并不在最初的开发阶段,而是在持续的维护阶段,包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债、添加新的功能等等。
|
||||
|
||||
不幸的是,许多从事软件系统行业的人不喜欢维护所谓的**遗留(legacy)** 系统,——也许因为涉及修复其他人的错误、和过时的平台打交道,或者系统被迫使用于一些份外工作。每一个遗留系统都以自己的方式让人不爽,所以很难给出一个通用的建议来和它们打交道。
|
||||
不幸的是,许多从事软件系统行业的人不喜欢维护所谓的 **遗留(legacy)** 系统,—— 也许因为涉及修复其他人的错误、和过时的平台打交道,或者系统被迫使用于一些份外工作。每一个遗留系统都以自己的方式让人不爽,所以很难给出一个通用的建议来和它们打交道。
|
||||
|
||||
但是我们可以,也应该以这样一种方式来设计软件:在设计之初就尽量考虑尽可能减少维护期间的痛苦,从而避免自己的软件系统变成遗留系统。为此,我们将特别关注软件系统的三个设计原则:
|
||||
|
||||
@ -285,11 +285,11 @@
|
||||
|
||||
* 简单性(Simplicity)
|
||||
|
||||
从系统中消除尽可能多的**复杂度(complexity)**,使新工程师也能轻松理解系统(注意这和用户接口的简单性不一样)。
|
||||
从系统中消除尽可能多的 **复杂度(complexity)**,使新工程师也能轻松理解系统(注意这和用户接口的简单性不一样)。
|
||||
|
||||
* 可演化性(evolvability)
|
||||
|
||||
使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为**可伸缩性(extensibility)**、**可修改性(modifiability)** 或**可塑性(plasticity)**。
|
||||
使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为 **可伸缩性(extensibility)**、**可修改性(modifiability)** 或 **可塑性(plasticity)**。
|
||||
|
||||
和之前提到的可靠性、可伸缩性一样,实现这些目标也没有简单的解决方案。不过我们会试着想象具有可操作性,简单性和可演化性的系统会是什么样子。
|
||||
|
||||
@ -304,7 +304,7 @@
|
||||
* 及时更新软件和平台,比如安全补丁。
|
||||
* 了解系统间的相互作用,以便在异常变更造成损失前进行规避。
|
||||
* 预测未来的问题,并在问题出现之前加以解决(例如,容量规划)。
|
||||
* 建立部署,配置、管理方面的良好实践,编写相应工具。
|
||||
* 建立部署、配置、管理方面的良好实践,编写相应工具。
|
||||
* 执行复杂的维护任务,例如将应用程序从一个平台迁移到另一个平台。
|
||||
* 当配置变更时,维持系统的安全性。
|
||||
* 定义工作流程,使运维操作可预测,并保持生产环境稳定。
|
||||
@ -312,10 +312,10 @@
|
||||
|
||||
良好的可操作性意味着更轻松的日常工作,进而运维团队能专注于高价值的事情。数据系统可以通过各种方式使日常任务更轻松:
|
||||
|
||||
* 通过良好的监控,提供对系统内部状态和运行时行为的**可见性(visibility)**。
|
||||
* 通过良好的监控,提供对系统内部状态和运行时行为的 **可见性(visibility)**。
|
||||
* 为自动化提供良好支持,将系统与标准化工具相集成。
|
||||
* 避免依赖单台机器(在整个系统继续不间断运行的情况下允许机器停机维护)。
|
||||
* 提供良好的文档和易于理解的操作模型(“如果做X,会发生Y”)。
|
||||
* 提供良好的文档和易于理解的操作模型(“如果做 X,会发生 Y”)。
|
||||
* 提供良好的默认行为,但需要时也允许管理员自由覆盖默认值。
|
||||
* 有条件时进行自我修复,但需要时也允许管理员手动控制系统状态。
|
||||
* 行为可预测,最大限度减少意外。
|
||||
@ -325,15 +325,15 @@
|
||||
|
||||
小型软件项目可以使用简单讨喜的、富表现力的代码,但随着项目越来越大,代码往往变得非常复杂,难以理解。这种复杂度拖慢了所有系统相关人员,进一步增加了维护成本。一个陷入复杂泥潭的软件项目有时被描述为 **烂泥潭(a big ball of mud)** 【30】。
|
||||
|
||||
**复杂度(complexity)** 有各种可能的症状,例如:状态空间激增、模块间紧密耦合、纠结的依赖关系、不一致的命名和术语、解决性能问题的Hack、需要绕开的特例等等,现在已经有很多关于这个话题的讨论【31,32,33】。
|
||||
**复杂度(complexity)** 有各种可能的症状,例如:状态空间激增、模块间紧密耦合、纠结的依赖关系、不一致的命名和术语、解决性能问题的 Hack、需要绕开的特例等等,现在已经有很多关于这个话题的讨论【31,32,33】。
|
||||
|
||||
因为复杂度导致维护困难时,预算和时间安排通常会超支。在复杂的软件中进行变更,引入错误的风险也更大:当开发人员难以理解系统时,隐藏的假设、无意的后果和意外的交互就更容易被忽略。相反,降低复杂度能极大地提高软件的可维护性,因此简单性应该是构建系统的一个关键目标。
|
||||
|
||||
简化系统并不一定意味着减少功能;它也可以意味着消除**额外的(accidental)** 的复杂度。 Moseley和Marks【32】把 **额外复杂度** 定义为:由具体实现中涌现,而非(从用户视角看,系统所解决的)问题本身固有的复杂度。
|
||||
简化系统并不一定意味着减少功能;它也可以意味着消除 **额外的(accidental)** 的复杂度。 Moseley 和 Marks【32】把 **额外复杂度** 定义为:由具体实现中涌现,而非(从用户视角看,系统所解决的)问题本身固有的复杂度。
|
||||
|
||||
用于消除**额外复杂度** 的最好工具之一是**抽象(abstraction)**。一个好的抽象可以将大量实现细节隐藏在一个干净,简单易懂的外观下面。一个好的抽象也可以广泛用于各类不同应用。比起重复造很多轮子,重用抽象不仅更有效率,而且有助于开发高质量的软件。抽象组件的质量改进将使所有使用它的应用受益。
|
||||
用于消除 **额外复杂度** 的最好工具之一是 **抽象(abstraction)**。一个好的抽象可以将大量实现细节隐藏在一个干净,简单易懂的外观下面。一个好的抽象也可以广泛用于各类不同应用。比起重复造很多轮子,重用抽象不仅更有效率,而且有助于开发高质量的软件。抽象组件的质量改进将使所有使用它的应用受益。
|
||||
|
||||
例如,高级编程语言是一种抽象,隐藏了机器码、CPU寄存器和系统调用。 SQL也是一种抽象,隐藏了复杂的磁盘/内存数据结构、来自其他客户端的并发请求、崩溃后的不一致性。当然在用高级语言编程时,我们仍然用到了机器码;只不过没有**直接(directly)** 使用罢了,正是因为编程语言的抽象,我们才不必去考虑这些实现细节。
|
||||
例如,高级编程语言是一种抽象,隐藏了机器码、CPU 寄存器和系统调用。 SQL 也是一种抽象,隐藏了复杂的磁盘 / 内存数据结构、来自其他客户端的并发请求、崩溃后的不一致性。当然在用高级语言编程时,我们仍然用到了机器码;只不过没有 **直接(directly)** 使用罢了,正是因为编程语言的抽象,我们才不必去考虑这些实现细节。
|
||||
|
||||
抽象可以帮助我们将系统的复杂度控制在可管理的水平,不过,找到好的抽象是非常困难的。在分布式系统领域虽然有许多好的算法,但我们并不清楚它们应该打包成什么样抽象。
|
||||
|
||||
@ -345,19 +345,19 @@
|
||||
|
||||
在组织流程方面, **敏捷(agile)** 工作模式为适应变化提供了一个框架。敏捷社区还开发了对在频繁变化的环境中开发软件很有帮助的技术工具和模式,如 **测试驱动开发(TDD, test-driven development)** 和 **重构(refactoring)** 。
|
||||
|
||||
这些敏捷技术的大部分讨论都集中在相当小的规模(同一个应用中的几个代码文件)。本书将探索在更大数据系统层面上提高敏捷性的方法,可能由几个不同的应用或服务组成。例如,为了将装配主页时间线的方法从方法1变为方法2,你会如何“重构”推特的架构 ?
|
||||
这些敏捷技术的大部分讨论都集中在相当小的规模(同一个应用中的几个代码文件)。本书将探索在更大数据系统层面上提高敏捷性的方法,可能由几个不同的应用或服务组成。例如,为了将装配主页时间线的方法从方法 1 变为方法 2,你会如何 “重构” 推特的架构 ?
|
||||
|
||||
修改数据系统并使其适应不断变化需求的容易程度,是与**简单性**和**抽象性**密切相关的:简单易懂的系统通常比复杂系统更容易修改。但由于这是一个非常重要的概念,我们将用一个不同的词来指代数据系统层面的敏捷性: **可演化性(evolvability)** 【34】。
|
||||
修改数据系统并使其适应不断变化需求的容易程度,是与 **简单性** 和 **抽象性** 密切相关的:简单易懂的系统通常比复杂系统更容易修改。但由于这是一个非常重要的概念,我们将用一个不同的词来指代数据系统层面的敏捷性: **可演化性(evolvability)** 【34】。
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
本章探讨了一些关于数据密集型应用的基本思考方式。这些原则将指导我们阅读本书的其余部分,那里将会深入技术细节。
|
||||
|
||||
一个应用必须满足各种需求才称得上有用。有一些**功能需求**(functional requirements,即它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些**非功能性需求**(nonfunctional,即通用属性,例如安全性、可靠性、合规性、可伸缩性、兼容性和可维护性)。在本章详细讨论了可靠性,可伸缩性和可维护性。
|
||||
一个应用必须满足各种需求才称得上有用。有一些 **功能需求**(functional requirements,即它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些 **非功能性需求**(nonfunctional,即通用属性,例如安全性、可靠性、合规性、可伸缩性、兼容性和可维护性)。在本章详细讨论了可靠性,可伸缩性和可维护性。
|
||||
|
||||
|
||||
**可靠性(Reliability)** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的)、软件(通常是系统性的Bug,很难处理)和人类(不可避免地时不时出错)。 **容错技术** 可以对终端用户隐藏某些类型的故障。
|
||||
**可靠性(Reliability)** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的)、软件(通常是系统性的 Bug,很难处理)和人类(不可避免地时不时出错)。 **容错技术** 可以对终端用户隐藏某些类型的故障。
|
||||
|
||||
**可伸缩性(Scalability)** 意味着即使在负载增加的情况下也有保持性能的策略。为了讨论可伸缩性,我们首先需要定量描述负载和性能的方法。我们简要了解了推特主页时间线的例子,介绍描述负载的方法,并将响应时间百分位点作为衡量性能的一种方式。在可伸缩的系统中可以添加 **处理容量(processing capacity)** 以在高负载下保持可靠。
|
||||
|
||||
@ -365,7 +365,7 @@
|
||||
|
||||
不幸的是,使应用可靠、可伸缩或可维护并不容易。但是某些模式和技术会不断重新出现在不同的应用中。在接下来的几章中,我们将看到一些数据系统的例子,并分析它们如何实现这些目标。
|
||||
|
||||
在本书后面的[第三部分](part-iii.md)中,我们将看到一种模式:几个组件协同工作以构成一个完整的系统(如[图1-1](img/fig1-1.png)中的例子)
|
||||
在本书后面的 [第三部分](part-iii.md) 中,我们将看到一种模式:几个组件协同工作以构成一个完整的系统(如 [图 1-1](img/fig1-1.png) 中的例子)
|
||||
|
||||
|
||||
## 参考文献
|
||||
@ -410,4 +410,4 @@
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
| ----------------------------------- | ------------------------------- | ------------------------------------ |
|
||||
| [第一部分:数据系统基础](part-i.md) | [设计数据密集型应用](README.md) | [第二章:数据模型与查询语言](ch2.md) |
|
||||
| [第一部分:数据系统基础](part-i.md) | [设计数据密集型应用](README.md) | [第二章:数据模型与查询语言](ch2.md) |
|
@ -149,6 +149,8 @@
|
||||
|
||||
| ISSUE & Pull Requests | USER | Title |
|
||||
| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| [177](https://github.com/Vonng/ddia/pull/177) | [@haifeiWu](https://github.com/haifeiWu) | ch2: 語義網相關翻譯更正 |
|
||||
| [176](https://github.com/Vonng/ddia/pull/176) | [@cwr31](https://github.com/cwr31) | ch7: 不變式相關翻譯更正 |
|
||||
| [174](https://github.com/Vonng/ddia/pull/174) | [@BeBraveBeCurious](https://github.com/BeBraveBeCurious) | README & preface: 更正不正確的中文用詞和標點符號 |
|
||||
| [173](https://github.com/Vonng/ddia/pull/173) | [@ZvanYang](https://github.com/ZvanYang) | ch12: 修正不完整的翻譯 |
|
||||
| [171](https://github.com/Vonng/ddia/pull/171) | [@ZvanYang](https://github.com/ZvanYang) | ch12: 修正重複的譯文 |
|
||||
|
166
zh-tw/ch1.md
166
zh-tw/ch1.md
@ -4,13 +4,13 @@
|
||||
|
||||
> 網際網路做得太棒了,以至於大多數人將它看作像太平洋這樣的自然資源,而不是什麼人工產物。上一次出現這種大規模且無差錯的技術, 你還記得是什麼時候嗎?
|
||||
>
|
||||
> —— [阿蘭·凱](http://www.drdobbs.com/architecture-and-design/interview-with-alan-kay/240003442) 在接受Dobb博士雜誌採訪時說(2012年)
|
||||
> —— [艾倫・凱](http://www.drdobbs.com/architecture-and-design/interview-with-alan-kay/240003442) 在接受 Dobb 博士雜誌採訪時說(2012 年)
|
||||
|
||||
-----------------------
|
||||
|
||||
[TOC]
|
||||
|
||||
現今很多應用程式都是 **資料密集型(data-intensive)** 的,而非 **計算密集型(compute-intensive)** 的。因此CPU很少成為這類應用的瓶頸,更大的問題通常來自資料量、資料複雜性、以及資料的變更速度。
|
||||
現今很多應用程式都是 **資料密集型(data-intensive)** 的,而非 **計算密集型(compute-intensive)** 的。因此 CPU 很少成為這類應用的瓶頸,更大的問題通常來自資料量、資料複雜性、以及資料的變更速度。
|
||||
|
||||
資料密集型應用通常由標準組件構建而成,標準組件提供了很多通用的功能;例如,許多應用程式都需要:
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
## 關於資料系統的思考
|
||||
|
||||
我們通常認為,資料庫、訊息佇列、快取等工具分屬於幾個差異顯著的類別。雖然資料庫和訊息隊列表面上有一些相似性——它們都會儲存一段時間的資料——但它們有迥然不同的訪問模式,這意味著迥異的效能特徵和實現手段。
|
||||
我們通常認為,資料庫、訊息佇列、快取等工具分屬於幾個差異顯著的類別。雖然資料庫和訊息隊列表面上有一些相似性 —— 它們都會儲存一段時間的資料 —— 但它們有迥然不同的訪問模式,這意味著迥異的效能特徵和實現手段。
|
||||
|
||||
那我們為什麼要把這些東西放在 **資料系統(data system)** 的總稱之下混為一談呢?
|
||||
|
||||
@ -39,15 +39,15 @@
|
||||
|
||||
其次,越來越多的應用程式有著各種嚴格而廣泛的要求,單個工具不足以滿足所有的資料處理和儲存需求。取而代之的是,總體工作被拆分成一系列能被單個工具高效完成的任務,並透過應用程式碼將它們縫合起來。
|
||||
|
||||
例如,如果將快取(應用管理的快取層,Memcached或同類產品)和全文搜尋(全文搜尋伺服器,例如Elasticsearch或Solr)功能從主資料庫剝離出來,那麼使快取/索引與主資料庫保持同步通常是應用程式碼的責任。[圖1-1](../img/fig1-1.png) 給出了這種架構可能的樣子(細節將在後面的章節中詳細介紹)。
|
||||
例如,如果將快取(應用管理的快取層,Memcached 或同類產品)和全文搜尋(全文搜尋伺服器,例如 Elasticsearch 或 Solr)功能從主資料庫剝離出來,那麼使快取 / 索引與主資料庫保持同步通常是應用程式碼的責任。[圖 1-1](../img/fig1-1.png) 給出了這種架構可能的樣子(細節將在後面的章節中詳細介紹)。
|
||||
|
||||
![](../img/fig1-1.png)
|
||||
|
||||
**圖1-1 一個可能的組合使用多個元件的資料系統架構**
|
||||
**圖 1-1 一個可能的組合使用多個元件的資料系統架構**
|
||||
|
||||
當你將多個工具組合在一起提供服務時,服務的介面或**應用程式程式設計介面(API, Application Programming Interface)** 通常向客戶端隱藏這些實現細節。現在,你基本上已經使用較小的通用元件建立了一個全新的、專用的資料系統。這個新的複合資料系統可能會提供特定的保證,例如:快取在寫入時會作廢或更新,以便外部客戶端獲取一致的結果。現在你不僅是應用程式開發人員,還是資料系統設計人員了。
|
||||
當你將多個工具組合在一起提供服務時,服務的介面或 **應用程式程式設計介面(API, Application Programming Interface)** 通常向客戶端隱藏這些實現細節。現在,你基本上已經使用較小的通用元件建立了一個全新的、專用的資料系統。這個新的複合資料系統可能會提供特定的保證,例如:快取在寫入時會作廢或更新,以便外部客戶端獲取一致的結果。現在你不僅是應用程式開發人員,還是資料系統設計人員了。
|
||||
|
||||
設計資料系統或服務時可能會遇到很多棘手的問題,例如:當系統出問題時,如何確保資料的正確性和完整性?當部分系統退化降級時,如何為客戶提供始終如一的良好效能?當負載增加時,如何擴容應對?什麼樣的API才是好的API?
|
||||
設計資料系統或服務時可能會遇到很多棘手的問題,例如:當系統出問題時,如何確保資料的正確性和完整性?當部分系統退化降級時,如何為客戶提供始終如一的良好效能?當負載增加時,如何擴容應對?什麼樣的 API 才是好的 API?
|
||||
|
||||
影響資料系統設計的因素很多,包括參與人員的技能和經驗、歷史遺留問題、系統路徑依賴、交付時限、公司的風險容忍度、監管約束等,這些因素都需要具體問題具體分析。
|
||||
|
||||
@ -55,15 +55,15 @@
|
||||
|
||||
* 可靠性(Reliability)
|
||||
|
||||
系統在**困境**(adversity,比如硬體故障、軟體故障、人為錯誤)中仍可正常工作(正確完成功能,並能達到期望的效能水準)。請參閱“[可靠性](#可靠性)”。
|
||||
系統在 **困境**(adversity,比如硬體故障、軟體故障、人為錯誤)中仍可正常工作(正確完成功能,並能達到期望的效能水準)。請參閱 “[可靠性](#可靠性)”。
|
||||
|
||||
* 可伸縮性(Scalability)
|
||||
|
||||
有合理的辦法應對系統的增長(資料量、流量、複雜性)。請參閱“[可伸縮性](#可伸縮性)”。
|
||||
有合理的辦法應對系統的增長(資料量、流量、複雜性)。請參閱 “[可伸縮性](#可伸縮性)”。
|
||||
|
||||
* 可維護性(Maintainability)
|
||||
|
||||
許多不同的人(工程師、運維)在不同的生命週期,都能高效地在系統上工作(使系統保持現有行為,並適應新的應用場景)。請參閱“[可維護性](#可維護性)”。
|
||||
許多不同的人(工程師、運維)在不同的生命週期,都能高效地在系統上工作(使系統保持現有行為,並適應新的應用場景)。請參閱 “[可維護性](#可維護性)”。
|
||||
|
||||
人們經常追求這些詞彙,卻沒有清楚理解它們到底意味著什麼。為了工程的嚴謹性,本章的剩餘部分將探討可靠性、可伸縮性、可維護性的含義。為實現這些目標而使用的各種技術,架構和演算法將在後續的章節中研究。
|
||||
|
||||
@ -77,65 +77,65 @@
|
||||
* 在預期的負載和資料量下,效能滿足要求。
|
||||
* 系統能防止未經授權的訪問和濫用。
|
||||
|
||||
如果所有這些在一起意味著“正確工作”,那麼可以把可靠性粗略理解為“即使出現問題,也能繼續正確工作”。
|
||||
如果所有這些在一起意味著 “正確工作”,那麼可以把可靠性粗略理解為 “即使出現問題,也能繼續正確工作”。
|
||||
|
||||
造成錯誤的原因叫做**故障(fault)**,能預料並應對故障的系統特性可稱為**容錯(fault-tolerant)** 或**韌性(resilient)**。“**容錯**”一詞可能會產生誤導,因為它暗示著系統可以容忍所有可能的錯誤,但在實際中這是不可能的。比方說,如果整個地球(及其上的所有伺服器)都被黑洞吞噬了,想要容忍這種錯誤,需要把網路託管到太空中——這種預算能不能批准就祝你好運了。所以在討論容錯時,只有談論特定型別的錯誤才有意義。
|
||||
造成錯誤的原因叫做 **故障(fault)**,能預料並應對故障的系統特性可稱為 **容錯(fault-tolerant)** 或 **韌性(resilient)**。“**容錯**” 一詞可能會產生誤導,因為它暗示著系統可以容忍所有可能的錯誤,但在實際中這是不可能的。比方說,如果整個地球(及其上的所有伺服器)都被黑洞吞噬了,想要容忍這種錯誤,需要把網路託管到太空中 —— 這種預算能不能批准就祝你好運了。所以在討論容錯時,只有談論特定型別的錯誤才有意義。
|
||||
|
||||
注意**故障(fault)** 不同於**失效(failure)**【2】。**故障**通常定義為系統的一部分狀態偏離其標準,而**失效**則是系統作為一個整體停止向用戶提供服務。故障的概率不可能降到零,因此最好設計容錯機制以防因**故障**而導致**失效**。本書中我們將介紹幾種用不可靠的部件構建可靠系統的技術。
|
||||
注意 **故障(fault)** 不同於 **失效(failure)**【2】。**故障** 通常定義為系統的一部分狀態偏離其標準,而 **失效** 則是系統作為一個整體停止向用戶提供服務。故障的概率不可能降到零,因此最好設計容錯機制以防因 **故障** 而導致 **失效**。本書中我們將介紹幾種用不可靠的部件構建可靠系統的技術。
|
||||
|
||||
反直覺的是,在這類容錯系統中,透過故意觸發來**提高**故障率是有意義的,例如:在沒有警告的情況下隨機地殺死單個程序。許多高危漏洞實際上是由糟糕的錯誤處理導致的【3】,因此我們可以透過故意引發故障來確保容錯機制不斷執行並接受考驗,從而提高故障自然發生時系統能正確處理的信心。Netflix公司的*Chaos Monkey*【4】就是這種方法的一個例子。
|
||||
反直覺的是,在這類容錯系統中,透過故意觸發來 **提高** 故障率是有意義的,例如:在沒有警告的情況下隨機地殺死單個程序。許多高危漏洞實際上是由糟糕的錯誤處理導致的【3】,因此我們可以透過故意引發故障來確保容錯機制不斷執行並接受考驗,從而提高故障自然發生時系統能正確處理的信心。Netflix 公司的 *Chaos Monkey*【4】就是這種方法的一個例子。
|
||||
|
||||
儘管比起**阻止錯誤(prevent error)**,我們通常更傾向於**容忍錯誤**。但也有**預防勝於治療**的情況(比如不存在治療方法時)。安全問題就屬於這種情況。例如,如果攻擊者破壞了系統,並獲取了敏感資料,這種事是撤銷不了的。但本書主要討論的是可以恢復的故障種類,正如下面幾節所述。
|
||||
儘管比起 **阻止錯誤(prevent error)**,我們通常更傾向於 **容忍錯誤**。但也有 **預防勝於治療** 的情況(比如不存在治療方法時)。安全問題就屬於這種情況。例如,如果攻擊者破壞了系統,並獲取了敏感資料,這種事是撤銷不了的。但本書主要討論的是可以恢復的故障種類,正如下面幾節所述。
|
||||
|
||||
### 硬體故障
|
||||
|
||||
當想到系統失效的原因時,**硬體故障(hardware faults)** 總會第一個進入腦海。硬碟崩潰、記憶體出錯、機房斷電、有人拔錯網線……任何與大型資料中心打過交道的人都會告訴你:一旦你擁有很多機器,這些事情**總**會發生!
|
||||
當想到系統失效的原因時,**硬體故障(hardware faults)** 總會第一個進入腦海。硬碟崩潰、記憶體出錯、機房斷電、有人拔錯網線…… 任何與大型資料中心打過交道的人都會告訴你:一旦你擁有很多機器,這些事情 **總** 會發生!
|
||||
|
||||
據報道稱,硬碟的 **平均無故障時間(MTTF mean time to failure)** 約為10到50年【5】【6】。因此從數學期望上講,在擁有10000個磁碟的儲存叢集上,平均每天會有1個磁碟出故障。
|
||||
據報道稱,硬碟的 **平均無故障時間(MTTF, mean time to failure)** 約為 10 到 50 年【5】【6】。因此從數學期望上講,在擁有 10000 個磁碟的儲存叢集上,平均每天會有 1 個磁碟出故障。
|
||||
|
||||
為了減少系統的故障率,第一反應通常都是增加單個硬體的冗餘度,例如:磁碟可以組建RAID,伺服器可能有雙路電源和熱插拔CPU,資料中心可能有電池和柴油發電機作為後備電源,某個元件掛掉時冗餘元件可以立刻接管。這種方法雖然不能完全防止由硬體問題導致的系統失效,但它簡單易懂,通常也足以讓機器不間斷執行很多年。
|
||||
為了減少系統的故障率,第一反應通常都是增加單個硬體的冗餘度,例如:磁碟可以組建 RAID,伺服器可能有雙路電源和熱插拔 CPU,資料中心可能有電池和柴油發電機作為後備電源,某個元件掛掉時冗餘元件可以立刻接管。這種方法雖然不能完全防止由硬體問題導致的系統失效,但它簡單易懂,通常也足以讓機器不間斷執行很多年。
|
||||
|
||||
直到最近,硬體冗餘對於大多數應用來說已經足夠了,它使單臺機器完全失效變得相當罕見。只要你能快速地把備份恢復到新機器上,故障停機時間對大多數應用而言都算不上災難性的。只有少量高可用性至關重要的應用才會要求有多套硬體冗餘。
|
||||
|
||||
但是隨著資料量和應用計算需求的增加,越來越多的應用開始大量使用機器,這會相應地增加硬體故障率。此外,在類似亞馬遜AWS(Amazon Web Services)的一些雲服務平臺上,虛擬機器例項不可用卻沒有任何警告也是很常見的【7】,因為雲平臺的設計就是優先考慮**靈活性(flexibility)** 和**彈性(elasticity)**[^i],而不是單機可靠性。
|
||||
但是隨著資料量和應用計算需求的增加,越來越多的應用開始大量使用機器,這會相應地增加硬體故障率。此外,在類似亞馬遜 AWS(Amazon Web Services)的一些雲服務平臺上,虛擬機器例項不可用卻沒有任何警告也是很常見的【7】,因為雲平臺的設計就是優先考慮 **靈活性(flexibility)** 和 **彈性(elasticity)**[^i],而不是單機可靠性。
|
||||
|
||||
如果在硬體冗餘的基礎上進一步引入軟體容錯機制,那麼系統在容忍整個(單臺)機器故障的道路上就更進一步了。這樣的系統也有運維上的便利,例如:如果需要重啟機器(例如應用作業系統安全補丁),單伺服器系統就需要計劃停機。而允許機器失效的系統則可以一次修復一個節點,無需整個系統停機。
|
||||
|
||||
[^i]: 在[應對負載的方法](#應對負載的方法)一節定義
|
||||
[^i]: 在 [應對負載的方法](#應對負載的方法) 一節定義
|
||||
|
||||
### 軟體錯誤
|
||||
|
||||
我們通常認為硬體故障是隨機的、相互獨立的:一臺機器的磁碟失效並不意味著另一臺機器的磁碟也會失效。大量硬體元件不可能同時發生故障,除非它們存在比較弱的相關性(同樣的原因導致關聯性錯誤,例如伺服器機架的溫度)。
|
||||
|
||||
另一類錯誤是內部的**系統性錯誤(systematic error)**【8】。這類錯誤難以預料,而且因為是跨節點相關的,所以比起不相關的硬體故障往往可能造成更多的**系統失效**【5】。例子包括:
|
||||
另一類錯誤是內部的 **系統性錯誤(systematic error)**【8】。這類錯誤難以預料,而且因為是跨節點相關的,所以比起不相關的硬體故障往往可能造成更多的 **系統失效**【5】。例子包括:
|
||||
|
||||
* 接受特定的錯誤輸入,便導致所有應用伺服器例項崩潰的BUG。例如2012年6月30日的閏秒,由於Linux核心中的一個錯誤【9】,許多應用同時掛掉了。
|
||||
* 失控程序會用盡一些共享資源,包括CPU時間、記憶體、磁碟空間或網路頻寬。
|
||||
* 接受特定的錯誤輸入,便導致所有應用伺服器例項崩潰的 BUG。例如 2012 年 6 月 30 日的閏秒,由於 Linux 核心中的一個錯誤【9】,許多應用同時掛掉了。
|
||||
* 失控程序會用盡一些共享資源,包括 CPU 時間、記憶體、磁碟空間或網路頻寬。
|
||||
* 系統依賴的服務變慢,沒有響應,或者開始返回錯誤的響應。
|
||||
* 級聯故障,一個元件中的小故障觸發另一個元件中的故障,進而觸發更多的故障【10】。
|
||||
|
||||
導致這類軟體故障的BUG通常會潛伏很長時間,直到被異常情況觸發為止。這種情況意味著軟體對其環境做出了某種假設——雖然這種假設通常來說是正確的,但由於某種原因最後不再成立了【11】。
|
||||
導致這類軟體故障的 BUG 通常會潛伏很長時間,直到被異常情況觸發為止。這種情況意味著軟體對其環境做出了某種假設 —— 雖然這種假設通常來說是正確的,但由於某種原因最後不再成立了【11】。
|
||||
|
||||
雖然軟體中的系統性故障沒有速效藥,但我們還是有很多小辦法,例如:仔細考慮系統中的假設和互動;徹底的測試;程序隔離;允許程序崩潰並重啟;測量、監控並分析生產環境中的系統行為。如果系統能夠提供一些保證(例如在一個訊息佇列中,進入與發出的訊息數量相等),那麼系統就可以在執行時不斷自檢,並在出現**差異(discrepancy)** 時報警【12】。
|
||||
雖然軟體中的系統性故障沒有速效藥,但我們還是有很多小辦法,例如:仔細考慮系統中的假設和互動;徹底的測試;程序隔離;允許程序崩潰並重啟;測量、監控並分析生產環境中的系統行為。如果系統能夠提供一些保證(例如在一個訊息佇列中,進入與發出的訊息數量相等),那麼系統就可以在執行時不斷自檢,並在出現 **差異(discrepancy)** 時報警【12】。
|
||||
|
||||
### 人為錯誤
|
||||
|
||||
設計並構建了軟體系統的工程師是人類,維持系統執行的運維也是人類。即使他們懷有最大的善意,人類也是不可靠的。舉個例子,一項關於大型網際網路服務的研究發現,運維配置錯誤是導致服務中斷的首要原因,而硬體故障(伺服器或網路)僅導致了10-25%的服務中斷【13】。
|
||||
設計並構建了軟體系統的工程師是人類,維持系統執行的運維也是人類。即使他們懷有最大的善意,人類也是不可靠的。舉個例子,一項關於大型網際網路服務的研究發現,運維配置錯誤是導致服務中斷的首要原因,而硬體故障(伺服器或網路)僅導致了 10-25% 的服務中斷【13】。
|
||||
|
||||
儘管人類不可靠,但怎麼做才能讓系統變得可靠?最好的系統會組合使用以下幾種辦法:
|
||||
|
||||
* 以最小化犯錯機會的方式設計系統。例如,精心設計的抽象、API和管理後臺使做對事情更容易,搞砸事情更困難。但如果介面限制太多,人們就會忽略它們的好處而想辦法繞開。很難正確把握這種微妙的平衡。
|
||||
* 將人們最容易犯錯的地方與可能導致失效的地方**解耦(decouple)**。特別是提供一個功能齊全的非生產環境**沙箱(sandbox)**,使人們可以在不影響真實使用者的情況下,使用真實資料安全地探索和實驗。
|
||||
* 在各個層次進行徹底的測試【3】,從單元測試、全系統整合測試到手動測試。自動化測試易於理解,已經被廣泛使用,特別適合用來覆蓋正常情況中少見的**邊緣場景(corner case)**。
|
||||
* 以最小化犯錯機會的方式設計系統。例如,精心設計的抽象、API 和管理後臺使做對事情更容易,搞砸事情更困難。但如果介面限制太多,人們就會忽略它們的好處而想辦法繞開。很難正確把握這種微妙的平衡。
|
||||
* 將人們最容易犯錯的地方與可能導致失效的地方 **解耦(decouple)**。特別是提供一個功能齊全的非生產環境 **沙箱(sandbox)**,使人們可以在不影響真實使用者的情況下,使用真實資料安全地探索和實驗。
|
||||
* 在各個層次進行徹底的測試【3】,從單元測試、全系統整合測試到手動測試。自動化測試易於理解,已經被廣泛使用,特別適合用來覆蓋正常情況中少見的 **邊緣場景(corner case)**。
|
||||
* 允許從人為錯誤中簡單快速地恢復,以最大限度地減少失效情況帶來的影響。 例如,快速回滾配置變更,分批發布新程式碼(以便任何意外錯誤隻影響一小部分使用者),並提供資料重算工具(以備舊的計算出錯)。
|
||||
* 配置詳細和明確的監控,比如效能指標和錯誤率。 在其他工程學科中這指的是**遙測(telemetry)**(一旦火箭離開了地面,遙測技術對於跟蹤發生的事情和理解失敗是至關重要的)。監控可以向我們發出預警訊號,並允許我們檢查是否有任何地方違反了假設和約束。當出現問題時,指標資料對於問題診斷是非常寶貴的。
|
||||
* 良好的管理實踐與充分的培訓——一個複雜而重要的方面,但超出了本書的範圍。
|
||||
* 配置詳細和明確的監控,比如效能指標和錯誤率。 在其他工程學科中這指的是 **遙測(telemetry)**(一旦火箭離開了地面,遙測技術對於跟蹤發生的事情和理解失敗是至關重要的)。監控可以向我們發出預警訊號,並允許我們檢查是否有任何地方違反了假設和約束。當出現問題時,指標資料對於問題診斷是非常寶貴的。
|
||||
* 良好的管理實踐與充分的培訓 —— 一個複雜而重要的方面,但超出了本書的範圍。
|
||||
|
||||
### 可靠性有多重要?
|
||||
|
||||
可靠性不僅僅是針對核電站和空中交通管制軟體而言,我們也期望更多平凡的應用能可靠地執行。商務應用中的錯誤會導致生產力損失(也許資料報告不完整還會有法律風險),而電商網站的中斷則可能會導致收入和聲譽的巨大損失。
|
||||
|
||||
即使在“非關鍵”應用中,我們也對使用者負有責任。試想一位家長把所有的照片和孩子的影片儲存在你的照片應用裡【15】。如果資料庫突然損壞,他們會感覺如何?他們可能會知道如何從備份恢復嗎?
|
||||
即使在 “非關鍵” 應用中,我們也對使用者負有責任。試想一位家長把所有的照片和孩子的影片儲存在你的照片應用裡【15】。如果資料庫突然損壞,他們會感覺如何?他們可能會知道如何從備份恢復嗎?
|
||||
|
||||
在某些情況下,我們可能會選擇犧牲可靠性來降低開發成本(例如為未經證實的市場開發產品原型)或運營成本(例如利潤率極低的服務),但我們偷工減料時,應該清楚意識到自己在做什麼。
|
||||
|
||||
@ -144,29 +144,29 @@
|
||||
|
||||
系統今天能可靠執行,並不意味未來也能可靠執行。服務 **降級(degradation)** 的一個常見原因是負載增加,例如:系統負載已經從一萬個併發使用者增長到十萬個併發使用者,或者從一百萬增長到一千萬。也許現在處理的資料量級要比過去大得多。
|
||||
|
||||
**可伸縮性(Scalability)** 是用來描述系統應對負載增長能力的術語。但是請注意,這不是貼在系統上的一維標籤:說“X可伸縮”或“Y不可伸縮”是沒有任何意義的。相反,討論可伸縮性意味著考慮諸如“如果系統以特定方式增長,有什麼選項可以應對增長?”和“如何增加計算資源來處理額外的負載?”等問題。
|
||||
**可伸縮性(Scalability)** 是用來描述系統應對負載增長能力的術語。但是請注意,這不是貼在系統上的一維標籤:說 “X 可伸縮” 或 “Y 不可伸縮” 是沒有任何意義的。相反,討論可伸縮性意味著考慮諸如 “如果系統以特定方式增長,有什麼選項可以應對增長?” 和 “如何增加計算資源來處理額外的負載?” 等問題。
|
||||
|
||||
### 描述負載
|
||||
|
||||
在討論增長問題(如果負載加倍會發生什麼?)前,首先要能簡要描述系統的當前負載。負載可以用一些稱為 **負載引數(load parameters)** 的數字來描述。引數的最佳選擇取決於系統架構,它可能是每秒向Web伺服器發出的請求、資料庫中的讀寫比率、聊天室中同時活躍的使用者數量、快取命中率或其他東西。除此之外,也許平均情況對你很重要,也許你的瓶頸是少數極端場景。
|
||||
在討論增長問題(如果負載加倍會發生什麼?)前,首先要能簡要描述系統的當前負載。負載可以用一些稱為 **負載引數(load parameters)** 的數字來描述。引數的最佳選擇取決於系統架構,它可能是每秒向 Web 伺服器發出的請求、資料庫中的讀寫比率、聊天室中同時活躍的使用者數量、快取命中率或其他東西。除此之外,也許平均情況對你很重要,也許你的瓶頸是少數極端場景。
|
||||
|
||||
為了使這個概念更加具體,我們以推特在2012年11月釋出的資料【16】為例。推特的兩個主要業務是:
|
||||
為了使這個概念更加具體,我們以推特在 2012 年 11 月釋出的資料【16】為例。推特的兩個主要業務是:
|
||||
|
||||
* 釋出推文
|
||||
|
||||
使用者可以向其粉絲釋出新訊息(平均 4.6k請求/秒,峰值超過 12k請求/秒)。
|
||||
使用者可以向其粉絲釋出新訊息(平均 4.6k 請求 / 秒,峰值超過 12k 請求 / 秒)。
|
||||
|
||||
* 主頁時間線
|
||||
|
||||
使用者可以查閱他們關注的人釋出的推文(300k請求/秒)。
|
||||
使用者可以查閱他們關注的人釋出的推文(300k 請求 / 秒)。
|
||||
|
||||
處理每秒12,000次寫入(發推文的速率峰值)還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量,而是來自**扇出(fan-out)**[^ii]——每個使用者關注了很多人,也被很多人關注。
|
||||
處理每秒 12,000 次寫入(發推文的速率峰值)還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量,而是來自 **扇出(fan-out)**[^ii]—— 每個使用者關注了很多人,也被很多人關注。
|
||||
|
||||
[^ii]: 扇出:從電子工程學中借用的術語,它描述了輸入連線到另一個門輸出的邏輯閘數量。 輸出需要提供足夠的電流來驅動所有連線的輸入。 在事務處理系統中,我們使用它來描述為了服務一個傳入請求而需要執行其他服務的請求數量。
|
||||
|
||||
大體上講,這一對操作有兩種實現方式。
|
||||
|
||||
1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如[圖1-2](../img/fig1-2.png)所示的關係型資料庫中,可以編寫這樣的查詢:
|
||||
1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如 [圖 1-2](../img/fig1-2.png) 所示的關係型資料庫中,可以編寫這樣的查詢:
|
||||
|
||||
```sql
|
||||
SELECT tweets.*, users.*
|
||||
@ -177,21 +177,21 @@
|
||||
```
|
||||
![](../img/fig1-2.png)
|
||||
|
||||
**圖1-2 推特主頁時間線的關係型模式簡單實現**
|
||||
**圖 1-2 推特主頁時間線的關係型模式簡單實現**
|
||||
|
||||
2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖1-3](../img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。
|
||||
2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖 1-3](../img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。
|
||||
|
||||
![](../img/fig1-3.png)
|
||||
|
||||
**圖1-3 用於分發推特至關注者的資料流水線,2012年11月的負載引數【16】**
|
||||
**圖 1-3 用於分發推特至關注者的資料流水線,2012 年 11 月的負載引數【16】**
|
||||
|
||||
推特的第一個版本使用了方法1,但系統很難跟上主頁時間線查詢的負載。所以公司轉向了方法2,方法2的效果更好,因為發推頻率比查詢主頁時間線的頻率幾乎低了兩個數量級,所以在這種情況下,最好在寫入時做更多的工作,而在讀取時做更少的工作。
|
||||
推特的第一個版本使用了方法 1,但系統很難跟上主頁時間線查詢的負載。所以公司轉向了方法 2,方法 2 的效果更好,因為發推頻率比查詢主頁時間線的頻率幾乎低了兩個數量級,所以在這種情況下,最好在寫入時做更多的工作,而在讀取時做更少的工作。
|
||||
|
||||
然而方法2的缺點是,發推現在需要大量的額外工作。平均來說,一條推文會發往約75個關注者,所以每秒4.6k的發推寫入,變成了對主頁時間線快取每秒345k的寫入。但這個平均值隱藏了使用者粉絲數差異巨大這一現實,一些使用者有超過3000萬的粉絲,這意味著一條推文就可能會導致主頁時間線快取的3000萬次寫入!及時完成這種操作是一個巨大的挑戰 —— 推特嘗試在5秒內向粉絲傳送推文。
|
||||
然而方法 2 的缺點是,發推現在需要大量的額外工作。平均來說,一條推文會發往約 75 個關注者,所以每秒 4.6k 的發推寫入,變成了對主頁時間線快取每秒 345k 的寫入。但這個平均值隱藏了使用者粉絲數差異巨大這一現實,一些使用者有超過 3000 萬的粉絲,這意味著一條推文就可能會導致主頁時間線快取的 3000 萬次寫入!及時完成這種操作是一個巨大的挑戰 —— 推特嘗試在 5 秒內向粉絲傳送推文。
|
||||
|
||||
在推特的例子中,每個使用者粉絲數的分佈(可能按這些使用者的發推頻率來加權)是探討可伸縮性的一個關鍵負載引數,因為它決定了扇出負載。你的應用程式可能具有非常不同的特徵,但可以採用相似的原則來考慮它的負載。
|
||||
|
||||
推特軼事的最終轉折:現在已經穩健地實現了方法2,推特逐步轉向了兩種方法的混合。大多數使用者發的推文會被扇出寫入其粉絲主頁時間線快取中。但是少數擁有海量粉絲的使用者(即名流)會被排除在外。當用戶讀取主頁時間線時,分別地獲取出該使用者所關注的每位名流的推文,再與使用者的主頁時間線快取合併,如方法1所示。這種混合方法能始終如一地提供良好效能。在[第十二章](ch12.md)中我們將重新討論這個例子,這在覆蓋更多技術層面之後。
|
||||
推特軼事的最終轉折:現在已經穩健地實現了方法 2,推特逐步轉向了兩種方法的混合。大多數使用者發的推文會被扇出寫入其粉絲主頁時間線快取中。但是少數擁有海量粉絲的使用者(即名流)會被排除在外。當用戶讀取主頁時間線時,分別地獲取出該使用者所關注的每位名流的推文,再與使用者的主頁時間線快取合併,如方法 1 所示。這種混合方法能始終如一地提供良好效能。在 [第十二章](ch12.md) 中我們將重新討論這個例子,這在覆蓋更多技術層面之後。
|
||||
|
||||
### 描述效能
|
||||
|
||||
@ -202,71 +202,71 @@
|
||||
|
||||
這兩個問題都需要效能資料,所以讓我們簡單地看一下如何描述系統性能。
|
||||
|
||||
對於Hadoop這樣的批處理系統,通常關心的是**吞吐量(throughput)**,即每秒可以處理的記錄數量,或者在特定規模資料集上執行作業的總時間[^iii]。對於線上系統,通常更重要的是服務的**響應時間(response time)**,即客戶端傳送請求到接收響應之間的時間。
|
||||
對於 Hadoop 這樣的批處理系統,通常關心的是 **吞吐量(throughput)**,即每秒可以處理的記錄數量,或者在特定規模資料集上執行作業的總時間 [^iii]。對於線上系統,通常更重要的是服務的 **響應時間(response time)**,即客戶端傳送請求到接收響應之間的時間。
|
||||
|
||||
[^iii]: 理想情況下,批次作業的執行時間是資料集的大小除以吞吐量。 在實踐中由於資料傾斜(資料不是均勻分佈在每個工作程序中),需要等待最慢的任務完成,所以執行時間往往更長。
|
||||
|
||||
> #### 延遲和響應時間
|
||||
>
|
||||
> **延遲(latency)** 和 **響應時間(response time)** 經常用作同義詞,但實際上它們並不一樣。響應時間是客戶所看到的,除了實際處理請求的時間( **服務時間(service time)** )之外,還包括網路延遲和排隊延遲。延遲是某個請求等待處理的**持續時長**,在此期間它處於 **休眠(latent)** 狀態,並等待服務【17】。
|
||||
> **延遲(latency)** 和 **響應時間(response time)** 經常用作同義詞,但實際上它們並不一樣。響應時間是客戶所看到的,除了實際處理請求的時間( **服務時間(service time)** )之外,還包括網路延遲和排隊延遲。延遲是某個請求等待處理的 **持續時長**,在此期間它處於 **休眠(latent)** 狀態,並等待服務【17】。
|
||||
|
||||
即使不斷重複傳送同樣的請求,每次得到的響應時間也都會略有不同。現實世界的系統會處理各式各樣的請求,響應時間可能會有很大差異。因此我們需要將響應時間視為一個可以測量的數值**分佈(distribution)**,而不是單個數值。
|
||||
即使不斷重複傳送同樣的請求,每次得到的響應時間也都會略有不同。現實世界的系統會處理各式各樣的請求,響應時間可能會有很大差異。因此我們需要將響應時間視為一個可以測量的數值 **分佈(distribution)**,而不是單個數值。
|
||||
|
||||
在[圖1-4](../img/fig1-4.png)中,每個灰條代表一次對服務的請求,其高度表示請求花費了多長時間。大多數請求是相當快的,但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大,例如它們可能會處理更多的資料。但即使(你認為)所有請求都花費相同時間的情況下,隨機的附加延遲也會導致結果變化,例如:上下文切換到後臺程序,網路資料包丟失與TCP重傳,垃圾收集暫停,強制從磁碟讀取的頁面錯誤,伺服器機架中的震動【18】,還有很多其他原因。
|
||||
在 [圖 1-4](../img/fig1-4.png) 中,每個灰條代表一次對服務的請求,其高度表示請求花費了多長時間。大多數請求是相當快的,但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大,例如它們可能會處理更多的資料。但即使(你認為)所有請求都花費相同時間的情況下,隨機的附加延遲也會導致結果變化,例如:上下文切換到後臺程序,網路資料包丟失與 TCP 重傳,垃圾收集暫停,強制從磁碟讀取的頁面錯誤,伺服器機架中的震動【18】,還有很多其他原因。
|
||||
|
||||
![](../img/fig1-4.png)
|
||||
|
||||
**圖1-4 展示了一個服務100次請求響應時間的均值與百分位數**
|
||||
**圖 1-4 展示了一個服務 100 次請求響應時間的均值與百分位數**
|
||||
|
||||
通常報表都會展示服務的平均響應時間。 (嚴格來講“平均”一詞並不指代任何特定公式,但實際上它通常被理解為**算術平均值(arithmetic mean)**:給定 n 個值,加起來除以 n )。然而如果你想知道“**典型(typical)**”響應時間,那麼平均值並不是一個非常好的指標,因為它不能告訴你有多少使用者實際上經歷了這個延遲。
|
||||
通常報表都會展示服務的平均響應時間。 (嚴格來講 “平均” 一詞並不指代任何特定公式,但實際上它通常被理解為 **算術平均值(arithmetic mean)**:給定 n 個值,加起來除以 n )。然而如果你想知道 “**典型(typical)**” 響應時間,那麼平均值並不是一個非常好的指標,因為它不能告訴你有多少使用者實際上經歷了這個延遲。
|
||||
|
||||
通常使用**百分位點(percentiles)** 會更好。如果將響應時間列表按最快到最慢排序,那麼**中位數(median)** 就在正中間:舉個例子,如果你的響應時間中位數是200毫秒,這意味著一半請求的返回時間少於200毫秒,另一半比這個要長。
|
||||
通常使用 **百分位點(percentiles)** 會更好。如果將響應時間列表按最快到最慢排序,那麼 **中位數(median)** 就在正中間:舉個例子,如果你的響應時間中位數是 200 毫秒,這意味著一半請求的返回時間少於 200 毫秒,另一半比這個要長。
|
||||
|
||||
如果想知道典型場景下使用者需要等待多長時間,那麼中位數是一個好的度量標準:一半使用者請求的響應時間少於響應時間的中位數,另一半服務時間比中位數長。中位數也被稱為第50百分位點,有時縮寫為p50。注意中位數是關於單個請求的;如果使用者同時發出幾個請求(在一個會話過程中,或者由於一個頁面中包含了多個資源),則至少一個請求比中位數慢的概率遠大於50%。
|
||||
如果想知道典型場景下使用者需要等待多長時間,那麼中位數是一個好的度量標準:一半使用者請求的響應時間少於響應時間的中位數,另一半服務時間比中位數長。中位數也被稱為第 50 百分位點,有時縮寫為 p50。注意中位數是關於單個請求的;如果使用者同時發出幾個請求(在一個會話過程中,或者由於一個頁面中包含了多個資源),則至少一個請求比中位數慢的概率遠大於 50%。
|
||||
|
||||
為了弄清異常值有多糟糕,可以看看更高的百分位點,例如第95、99和99.9百分位點(縮寫為p95,p99和p999)。它們意味著95%、99%或99.9%的請求響應時間要比該閾值快,例如:如果第95百分位點響應時間是1.5秒,則意味著100個請求中的95個響應時間快於1.5秒,而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。
|
||||
為了弄清異常值有多糟糕,可以看看更高的百分位點,例如第 95、99 和 99.9 百分位點(縮寫為 p95,p99 和 p999)。它們意味著 95%、99% 或 99.9% 的請求響應時間要比該閾值快,例如:如果第 95 百分位點響應時間是 1.5 秒,則意味著 100 個請求中的 95 個響應時間快於 1.5 秒,而 100 個請求中的 5 個響應時間超過 1.5 秒。如 [圖 1-4](../img/fig1-4.png) 所示。
|
||||
|
||||
響應時間的高百分位點(也稱為**尾部延遲**,即**tail latencies**)非常重要,因為它們直接影響使用者的服務體驗。例如亞馬遜在描述內部服務的響應時間要求時以99.9百分位點為準,即使它隻影響一千個請求中的一個。這是因為請求響應最慢的客戶往往也是資料最多的客戶,也可以說是最有價值的客戶 —— 因為他們掏錢了【19】。保證網站響應迅速對於保持客戶的滿意度非常重要,亞馬遜觀察到:響應時間增加100毫秒,銷售量就減少1%【20】;而另一些報告說:慢 1 秒鐘會讓客戶滿意度指標減少16%【21,22】。
|
||||
響應時間的高百分位點(也稱為 **尾部延遲**,即 **tail latencies**)非常重要,因為它們直接影響使用者的服務體驗。例如亞馬遜在描述內部服務的響應時間要求時以 99.9 百分位點為準,即使它隻影響一千個請求中的一個。這是因為請求響應最慢的客戶往往也是資料最多的客戶,也可以說是最有價值的客戶 —— 因為他們掏錢了【19】。保證網站響應迅速對於保持客戶的滿意度非常重要,亞馬遜觀察到:響應時間增加 100 毫秒,銷售量就減少 1%【20】;而另一些報告說:慢 1 秒鐘會讓客戶滿意度指標減少 16%【21,22】。
|
||||
|
||||
另一方面,最佳化第99.99百分位點(一萬個請求中最慢的一個)被認為太昂貴了,不能為亞馬遜的目標帶來足夠好處。減小高百分位點處的響應時間相當困難,因為它很容易受到隨機事件的影響,這超出了控制範圍,而且效益也很小。
|
||||
另一方面,最佳化第 99.99 百分位點(一萬個請求中最慢的一個)被認為太昂貴了,不能為亞馬遜的目標帶來足夠好處。減小高百分位點處的響應時間相當困難,因為它很容易受到隨機事件的影響,這超出了控制範圍,而且效益也很小。
|
||||
|
||||
百分位點通常用於**服務級別目標(SLO, service level objectives)** 和**服務級別協議(SLA, service level agreements)**,即定義服務預期效能和可用性的合同。 SLA可能會宣告,如果服務響應時間的中位數小於200毫秒,且99.9百分位點低於1秒,則認為服務工作正常(如果響應時間更長,就認為服務不達標)。這些指標為客戶設定了期望值,並允許客戶在SLA未達標的情況下要求退款。
|
||||
百分位點通常用於 **服務級別目標(SLO, service level objectives)** 和 **服務級別協議(SLA, service level agreements)**,即定義服務預期效能和可用性的合同。 SLA 可能會宣告,如果服務響應時間的中位數小於 200 毫秒,且 99.9 百分位點低於 1 秒,則認為服務工作正常(如果響應時間更長,就認為服務不達標)。這些指標為客戶設定了期望值,並允許客戶在 SLA 未達標的情況下要求退款。
|
||||
|
||||
**排隊延遲(queueing delay)** 通常佔了高百分位點處響應時間的很大一部分。由於伺服器只能並行處理少量的事務(如受其CPU核數的限制),所以只要有少量緩慢的請求就能阻礙後續請求的處理,這種效應有時被稱為 **頭部阻塞(head-of-line blocking)** 。即使後續請求在伺服器上處理的非常迅速,由於需要等待先前請求完成,客戶端最終看到的是緩慢的總體響應時間。因為存在這種效應,測量客戶端的響應時間非常重要。
|
||||
**排隊延遲(queueing delay)** 通常佔了高百分位點處響應時間的很大一部分。由於伺服器只能並行處理少量的事務(如受其 CPU 核數的限制),所以只要有少量緩慢的請求就能阻礙後續請求的處理,這種效應有時被稱為 **頭部阻塞(head-of-line blocking)** 。即使後續請求在伺服器上處理的非常迅速,由於需要等待先前請求完成,客戶端最終看到的是緩慢的總體響應時間。因為存在這種效應,測量客戶端的響應時間非常重要。
|
||||
|
||||
為測試系統的可伸縮性而人為產生負載時,產生負載的客戶端要獨立於響應時間不斷傳送請求。如果客戶端在傳送下一個請求之前等待先前的請求完成,這種行為會產生人為排隊的效果,使得測試時的佇列比現實情況更短,使測量結果產生偏差【23】。
|
||||
|
||||
> #### 實踐中的百分位點
|
||||
>
|
||||
> 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如[圖1-5](../img/fig1-5.png)所示,只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢,如果終端使用者請求需要多個後端呼叫,則獲得較慢呼叫的機會也會增加,因此較高比例的終端使用者請求速度會變慢(效果稱為尾部延遲放大【24】)。
|
||||
> 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如 [圖 1-5](../img/fig1-5.png) 所示,只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢,如果終端使用者請求需要多個後端呼叫,則獲得較慢呼叫的機會也會增加,因此較高比例的終端使用者請求速度會變慢(效果稱為尾部延遲放大【24】)。
|
||||
>
|
||||
> 如果你想將響應時間百分點新增到你的服務的監視儀表板,則需要持續有效地計算它們。例如,你可能希望在最近10分鐘內保持請求響應時間的滾動視窗。每一分鐘,你都會計算出該視窗中的中值和各種百分數,並將這些度量值繪製在圖上。
|
||||
> 如果你想將響應時間百分點新增到你的服務的監視儀表板,則需要持續有效地計算它們。例如,你可能希望在最近 10 分鐘內保持請求響應時間的滾動視窗。每一分鐘,你都會計算出該視窗中的中值和各種百分數,並將這些度量值繪製在圖上。
|
||||
>
|
||||
> 簡單的實現是在時間視窗內儲存所有請求的響應時間列表,並且每分鐘對列表進行排序。如果對你來說效率太低,那麼有一些演算法能夠以最小的CPU和記憶體成本(如前向衰減【25】、t-digest【26】或HdrHistogram 【27】)來計算百分位數的近似值。請注意,平均百分比(例如,減少時間解析度或合併來自多臺機器的資料)在數學上沒有意義 - 聚合響應時間資料的正確方法是新增直方圖【28】。
|
||||
> 簡單的實現是在時間視窗內儲存所有請求的響應時間列表,並且每分鐘對列表進行排序。如果對你來說效率太低,那麼有一些演算法能夠以最小的 CPU 和記憶體成本(如前向衰減【25】、t-digest【26】或 HdrHistogram 【27】)來計算百分位數的近似值。請注意,平均百分比(例如,減少時間解析度或合併來自多臺機器的資料)在數學上沒有意義 - 聚合響應時間資料的正確方法是新增直方圖【28】。
|
||||
|
||||
![](../img/fig1-5.png)
|
||||
|
||||
**圖1-5 當一個請求需要多個後端請求時,單個後端慢請求就會拖慢整個終端使用者的請求**
|
||||
**圖 1-5 當一個請求需要多個後端請求時,單個後端慢請求就會拖慢整個終端使用者的請求**
|
||||
|
||||
### 應對負載的方法
|
||||
|
||||
現在我們已經討論了用於描述負載的引數和用於衡量效能的指標。可以開始認真討論可伸縮性了:當負載引數增加時,如何保持良好的效能?
|
||||
|
||||
適應某個級別負載的架構不太可能應付10倍於此的負載。如果你正在開發一個快速增長的服務,那麼每次負載發生數量級的增長時,你可能都需要重新考慮架構——或者更頻繁。
|
||||
適應某個級別負載的架構不太可能應付 10 倍於此的負載。如果你正在開發一個快速增長的服務,那麼每次負載發生數量級的增長時,你可能都需要重新考慮架構 —— 或者更頻繁。
|
||||
|
||||
人們經常討論**縱向伸縮**(scaling up,也稱為垂直伸縮,即vertical scaling,轉向更強大的機器)和**橫向伸縮**(scaling out,也稱為水平伸縮,即horizontal scaling,將負載分佈到多臺小機器上)之間的對立。跨多臺機器分配負載也稱為“**無共享(shared-nothing)**”架構。可以在單臺機器上執行的系統通常更簡單,但高階機器可能非常貴,所以非常密集的負載通常無法避免地需要橫向伸縮。現實世界中的優秀架構需要將這兩種方法務實地結合,因為使用幾臺足夠強大的機器可能比使用大量的小型虛擬機器更簡單也更便宜。
|
||||
人們經常討論 **縱向伸縮**(scaling up,也稱為垂直伸縮,即 vertical scaling,轉向更強大的機器)和 **橫向伸縮**(scaling out,也稱為水平伸縮,即 horizontal scaling,將負載分佈到多臺小機器上)之間的對立。跨多臺機器分配負載也稱為 “**無共享(shared-nothing)**” 架構。可以在單臺機器上執行的系統通常更簡單,但高階機器可能非常貴,所以非常密集的負載通常無法避免地需要橫向伸縮。現實世界中的優秀架構需要將這兩種方法務實地結合,因為使用幾臺足夠強大的機器可能比使用大量的小型虛擬機器更簡單也更便宜。
|
||||
|
||||
有些系統是 **彈性(elastic)** 的,這意味著可以在檢測到負載增加時自動增加計算資源,而其他系統則是手動伸縮(人工分析容量並決定向系統新增更多的機器)。如果負載**極難預測(highly unpredictable)**,則彈性系統可能很有用,但手動伸縮系統更簡單,並且意外操作可能會更少(請參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”)。
|
||||
有些系統是 **彈性(elastic)** 的,這意味著可以在檢測到負載增加時自動增加計算資源,而其他系統則是手動伸縮(人工分析容量並決定向系統新增更多的機器)。如果負載 **極難預測(highly unpredictable)**,則彈性系統可能很有用,但手動伸縮系統更簡單,並且意外操作可能會更少(請參閱 “[分割槽再平衡](ch6.md# 分割槽再平衡)”)。
|
||||
|
||||
跨多臺機器部署 **無狀態服務(stateless services)** 非常簡單,但將帶狀態的資料系統從單節點變為分散式配置則可能引入許多額外複雜度。出於這個原因,常識告訴我們應該將資料庫放在單個節點上(縱向伸縮),直到伸縮成本或可用性需求迫使其改為分散式。
|
||||
|
||||
隨著分散式系統的工具和抽象越來越好,至少對於某些型別的應用而言,這種常識可能會改變。可以預見分散式資料系統將成為未來的預設設定,即使對不處理大量資料或流量的場景也如此。本書的其餘部分將介紹多種分散式資料系統,不僅討論它們在可伸縮性方面的表現,還包括易用性和可維護性。
|
||||
|
||||
大規模的系統架構通常是應用特定的—— 沒有一招鮮吃遍天的通用可伸縮架構(不正式的叫法:**萬金油(magic scaling sauce)** )。應用的問題可能是讀取量、寫入量、要儲存的資料量、資料的複雜度、響應時間要求、訪問模式或者所有問題的大雜燴。
|
||||
大規模的系統架構通常是應用特定的 —— 沒有一招鮮吃遍天的通用可伸縮架構(不正式的叫法:**萬金油(magic scaling sauce)** )。應用的問題可能是讀取量、寫入量、要儲存的資料量、資料的複雜度、響應時間要求、訪問模式或者所有問題的大雜燴。
|
||||
|
||||
舉個例子,用於處理每秒十萬個請求(每個大小為1 kB)的系統與用於處理每分鐘3個請求(每個大小為2GB)的系統看上去會非常不一樣,儘管兩個系統有同樣的資料吞吐量。
|
||||
舉個例子,用於處理每秒十萬個請求(每個大小為 1 kB)的系統與用於處理每分鐘 3 個請求(每個大小為 2GB)的系統看上去會非常不一樣,儘管兩個系統有同樣的資料吞吐量。
|
||||
|
||||
一個良好適配應用的可伸縮架構,是圍繞著**假設(assumption)** 建立的:哪些操作是常見的?哪些操作是罕見的?這就是所謂負載引數。如果假設最終是錯誤的,那麼為伸縮所做的工程投入就白費了,最糟糕的是適得其反。在早期創業公司或非正式產品中,通常支援產品快速迭代的能力,要比可伸縮至未來的假想負載要重要的多。
|
||||
一個良好適配應用的可伸縮架構,是圍繞著 **假設(assumption)** 建立的:哪些操作是常見的?哪些操作是罕見的?這就是所謂負載引數。如果假設最終是錯誤的,那麼為伸縮所做的工程投入就白費了,最糟糕的是適得其反。在早期創業公司或非正式產品中,通常支援產品快速迭代的能力,要比可伸縮至未來的假想負載要重要的多。
|
||||
|
||||
儘管這些架構是應用程式特定的,但可伸縮的架構通常也是從通用的積木塊搭建而成的,並以常見的模式排列。在本書中,我們將討論這些構件和模式。
|
||||
|
||||
@ -275,7 +275,7 @@
|
||||
|
||||
眾所周知,軟體的大部分開銷並不在最初的開發階段,而是在持續的維護階段,包括修復漏洞、保持系統正常執行、調查失效、適配新的平臺、為新的場景進行修改、償還技術債、新增新的功能等等。
|
||||
|
||||
不幸的是,許多從事軟體系統行業的人不喜歡維護所謂的**遺留(legacy)** 系統,——也許因為涉及修復其他人的錯誤、和過時的平臺打交道,或者系統被迫使用於一些份外工作。每一個遺留系統都以自己的方式讓人不爽,所以很難給出一個通用的建議來和它們打交道。
|
||||
不幸的是,許多從事軟體系統行業的人不喜歡維護所謂的 **遺留(legacy)** 系統,—— 也許因為涉及修復其他人的錯誤、和過時的平臺打交道,或者系統被迫使用於一些份外工作。每一個遺留系統都以自己的方式讓人不爽,所以很難給出一個通用的建議來和它們打交道。
|
||||
|
||||
但是我們可以,也應該以這樣一種方式來設計軟體:在設計之初就儘量考慮儘可能減少維護期間的痛苦,從而避免自己的軟體系統變成遺留系統。為此,我們將特別關注軟體系統的三個設計原則:
|
||||
|
||||
@ -285,11 +285,11 @@
|
||||
|
||||
* 簡單性(Simplicity)
|
||||
|
||||
從系統中消除儘可能多的**複雜度(complexity)**,使新工程師也能輕鬆理解系統(注意這和使用者介面的簡單性不一樣)。
|
||||
從系統中消除儘可能多的 **複雜度(complexity)**,使新工程師也能輕鬆理解系統(注意這和使用者介面的簡單性不一樣)。
|
||||
|
||||
* 可演化性(evolvability)
|
||||
|
||||
使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為**可伸縮性(extensibility)**、**可修改性(modifiability)** 或**可塑性(plasticity)**。
|
||||
使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為 **可伸縮性(extensibility)**、**可修改性(modifiability)** 或 **可塑性(plasticity)**。
|
||||
|
||||
和之前提到的可靠性、可伸縮性一樣,實現這些目標也沒有簡單的解決方案。不過我們會試著想象具有可操作性,簡單性和可演化性的系統會是什麼樣子。
|
||||
|
||||
@ -304,7 +304,7 @@
|
||||
* 及時更新軟體和平臺,比如安全補丁。
|
||||
* 瞭解系統間的相互作用,以便在異常變更造成損失前進行規避。
|
||||
* 預測未來的問題,並在問題出現之前加以解決(例如,容量規劃)。
|
||||
* 建立部署,配置、管理方面的良好實踐,編寫相應工具。
|
||||
* 建立部署、配置、管理方面的良好實踐,編寫相應工具。
|
||||
* 執行復雜的維護任務,例如將應用程式從一個平臺遷移到另一個平臺。
|
||||
* 當配置變更時,維持系統的安全性。
|
||||
* 定義工作流程,使運維操作可預測,並保持生產環境穩定。
|
||||
@ -312,10 +312,10 @@
|
||||
|
||||
良好的可操作性意味著更輕鬆的日常工作,進而運維團隊能專注於高價值的事情。資料系統可以透過各種方式使日常任務更輕鬆:
|
||||
|
||||
* 透過良好的監控,提供對系統內部狀態和執行時行為的**可見性(visibility)**。
|
||||
* 透過良好的監控,提供對系統內部狀態和執行時行為的 **可見性(visibility)**。
|
||||
* 為自動化提供良好支援,將系統與標準化工具相整合。
|
||||
* 避免依賴單臺機器(在整個系統繼續不間斷執行的情況下允許機器停機維護)。
|
||||
* 提供良好的文件和易於理解的操作模型(“如果做X,會發生Y”)。
|
||||
* 提供良好的文件和易於理解的操作模型(“如果做 X,會發生 Y”)。
|
||||
* 提供良好的預設行為,但需要時也允許管理員自由覆蓋預設值。
|
||||
* 有條件時進行自我修復,但需要時也允許管理員手動控制系統狀態。
|
||||
* 行為可預測,最大限度減少意外。
|
||||
@ -325,15 +325,15 @@
|
||||
|
||||
小型軟體專案可以使用簡單討喜的、富表現力的程式碼,但隨著專案越來越大,程式碼往往變得非常複雜,難以理解。這種複雜度拖慢了所有系統相關人員,進一步增加了維護成本。一個陷入複雜泥潭的軟體專案有時被描述為 **爛泥潭(a big ball of mud)** 【30】。
|
||||
|
||||
**複雜度(complexity)** 有各種可能的症狀,例如:狀態空間激增、模組間緊密耦合、糾結的依賴關係、不一致的命名和術語、解決效能問題的Hack、需要繞開的特例等等,現在已經有很多關於這個話題的討論【31,32,33】。
|
||||
**複雜度(complexity)** 有各種可能的症狀,例如:狀態空間激增、模組間緊密耦合、糾結的依賴關係、不一致的命名和術語、解決效能問題的 Hack、需要繞開的特例等等,現在已經有很多關於這個話題的討論【31,32,33】。
|
||||
|
||||
因為複雜度導致維護困難時,預算和時間安排通常會超支。在複雜的軟體中進行變更,引入錯誤的風險也更大:當開發人員難以理解系統時,隱藏的假設、無意的後果和意外的互動就更容易被忽略。相反,降低複雜度能極大地提高軟體的可維護性,因此簡單性應該是構建系統的一個關鍵目標。
|
||||
|
||||
簡化系統並不一定意味著減少功能;它也可以意味著消除**額外的(accidental)** 的複雜度。 Moseley和Marks【32】把 **額外複雜度** 定義為:由具體實現中湧現,而非(從使用者視角看,系統所解決的)問題本身固有的複雜度。
|
||||
簡化系統並不一定意味著減少功能;它也可以意味著消除 **額外的(accidental)** 的複雜度。 Moseley 和 Marks【32】把 **額外複雜度** 定義為:由具體實現中湧現,而非(從使用者視角看,系統所解決的)問題本身固有的複雜度。
|
||||
|
||||
用於消除**額外複雜度** 的最好工具之一是**抽象(abstraction)**。一個好的抽象可以將大量實現細節隱藏在一個乾淨,簡單易懂的外觀下面。一個好的抽象也可以廣泛用於各類不同應用。比起重複造很多輪子,重用抽象不僅更有效率,而且有助於開發高質量的軟體。抽象元件的質量改進將使所有使用它的應用受益。
|
||||
用於消除 **額外複雜度** 的最好工具之一是 **抽象(abstraction)**。一個好的抽象可以將大量實現細節隱藏在一個乾淨,簡單易懂的外觀下面。一個好的抽象也可以廣泛用於各類不同應用。比起重複造很多輪子,重用抽象不僅更有效率,而且有助於開發高質量的軟體。抽象元件的質量改進將使所有使用它的應用受益。
|
||||
|
||||
例如,高階程式語言是一種抽象,隱藏了機器碼、CPU暫存器和系統呼叫。 SQL也是一種抽象,隱藏了複雜的磁碟/記憶體資料結構、來自其他客戶端的併發請求、崩潰後的不一致性。當然在用高階語言程式設計時,我們仍然用到了機器碼;只不過沒有**直接(directly)** 使用罷了,正是因為程式語言的抽象,我們才不必去考慮這些實現細節。
|
||||
例如,高階程式語言是一種抽象,隱藏了機器碼、CPU 暫存器和系統呼叫。 SQL 也是一種抽象,隱藏了複雜的磁碟 / 記憶體資料結構、來自其他客戶端的併發請求、崩潰後的不一致性。當然在用高階語言程式設計時,我們仍然用到了機器碼;只不過沒有 **直接(directly)** 使用罷了,正是因為程式語言的抽象,我們才不必去考慮這些實現細節。
|
||||
|
||||
抽象可以幫助我們將系統的複雜度控制在可管理的水平,不過,找到好的抽象是非常困難的。在分散式系統領域雖然有許多好的演算法,但我們並不清楚它們應該打包成什麼樣抽象。
|
||||
|
||||
@ -345,19 +345,19 @@
|
||||
|
||||
在組織流程方面, **敏捷(agile)** 工作模式為適應變化提供了一個框架。敏捷社群還開發了對在頻繁變化的環境中開發軟體很有幫助的技術工具和模式,如 **測試驅動開發(TDD, test-driven development)** 和 **重構(refactoring)** 。
|
||||
|
||||
這些敏捷技術的大部分討論都集中在相當小的規模(同一個應用中的幾個程式碼檔案)。本書將探索在更大資料系統層面上提高敏捷性的方法,可能由幾個不同的應用或服務組成。例如,為了將裝配主頁時間線的方法從方法1變為方法2,你會如何“重構”推特的架構 ?
|
||||
這些敏捷技術的大部分討論都集中在相當小的規模(同一個應用中的幾個程式碼檔案)。本書將探索在更大資料系統層面上提高敏捷性的方法,可能由幾個不同的應用或服務組成。例如,為了將裝配主頁時間線的方法從方法 1 變為方法 2,你會如何 “重構” 推特的架構 ?
|
||||
|
||||
修改資料系統並使其適應不斷變化需求的容易程度,是與**簡單性**和**抽象性**密切相關的:簡單易懂的系統通常比複雜系統更容易修改。但由於這是一個非常重要的概念,我們將用一個不同的詞來指代資料系統層面的敏捷性: **可演化性(evolvability)** 【34】。
|
||||
修改資料系統並使其適應不斷變化需求的容易程度,是與 **簡單性** 和 **抽象性** 密切相關的:簡單易懂的系統通常比複雜系統更容易修改。但由於這是一個非常重要的概念,我們將用一個不同的詞來指代資料系統層面的敏捷性: **可演化性(evolvability)** 【34】。
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
本章探討了一些關於資料密集型應用的基本思考方式。這些原則將指導我們閱讀本書的其餘部分,那裡將會深入技術細節。
|
||||
|
||||
一個應用必須滿足各種需求才稱得上有用。有一些**功能需求**(functional requirements,即它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求**(nonfunctional,即通用屬性,例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。
|
||||
一個應用必須滿足各種需求才稱得上有用。有一些 **功能需求**(functional requirements,即它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些 **非功能性需求**(nonfunctional,即通用屬性,例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。
|
||||
|
||||
|
||||
**可靠性(Reliability)** 意味著即使發生故障,系統也能正常工作。故障可能發生在硬體(通常是隨機的和不相關的)、軟體(通常是系統性的Bug,很難處理)和人類(不可避免地時不時出錯)。 **容錯技術** 可以對終端使用者隱藏某些型別的故障。
|
||||
**可靠性(Reliability)** 意味著即使發生故障,系統也能正常工作。故障可能發生在硬體(通常是隨機的和不相關的)、軟體(通常是系統性的 Bug,很難處理)和人類(不可避免地時不時出錯)。 **容錯技術** 可以對終端使用者隱藏某些型別的故障。
|
||||
|
||||
**可伸縮性(Scalability)** 意味著即使在負載增加的情況下也有保持效能的策略。為了討論可伸縮性,我們首先需要定量描述負載和效能的方法。我們簡要了解了推特主頁時間線的例子,介紹描述負載的方法,並將響應時間百分位點作為衡量效能的一種方式。在可伸縮的系統中可以新增 **處理容量(processing capacity)** 以在高負載下保持可靠。
|
||||
|
||||
@ -365,7 +365,7 @@
|
||||
|
||||
不幸的是,使應用可靠、可伸縮或可維護並不容易。但是某些模式和技術會不斷重新出現在不同的應用中。在接下來的幾章中,我們將看到一些資料系統的例子,並分析它們如何實現這些目標。
|
||||
|
||||
在本書後面的[第三部分](part-iii.md)中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如[圖1-1](../img/fig1-1.png)中的例子)
|
||||
在本書後面的 [第三部分](part-iii.md) 中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如 [圖 1-1](../img/fig1-1.png) 中的例子)
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
Loading…
Reference in New Issue
Block a user