mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
fix #89 可伸缩=>可扩展
This commit is contained in:
parent
2dc147efef
commit
b1d99985af
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
### [第一部分:数据系统的基石](part-i.md)
|
### [第一部分:数据系统的基石](part-i.md)
|
||||||
|
|
||||||
* [第一章:可靠性、可扩展性、可维护性](ch1.md)
|
* [第一章:可靠性、可伸缩性、可维护性](ch1.md)
|
||||||
* [第二章:数据模型与查询语言](ch2.md)
|
* [第二章:数据模型与查询语言](ch2.md)
|
||||||
* [第三章:存储与检索](ch3.md)
|
* [第三章:存储与检索](ch3.md)
|
||||||
* [第四章:编码与演化](ch4.md)
|
* [第四章:编码与演化](ch4.md)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* [简介](README.md)
|
* [简介](README.md)
|
||||||
* [序言](preface.md)
|
* [序言](preface.md)
|
||||||
* [第一部分:数据系统的基石](part-i.md)
|
* [第一部分:数据系统的基石](part-i.md)
|
||||||
* [第一章:可靠性、可扩展性、可维护性](ch1.md)
|
* [第一章:可靠性、可伸缩性、可维护性](ch1.md)
|
||||||
* [第二章:数据模型与查询语言](ch2.md)
|
* [第二章:数据模型与查询语言](ch2.md)
|
||||||
* [第三章:存储与检索](ch3.md)
|
* [第三章:存储与检索](ch3.md)
|
||||||
* [第四章:编码与演化](ch4.md)
|
* [第四章:编码与演化](ch4.md)
|
||||||
|
46
ch1.md
46
ch1.md
@ -1,4 +1,4 @@
|
|||||||
# 第一章:可靠性,可扩展性,可维护性
|
# 第一章:可靠性,可伸缩性,可维护性
|
||||||
|
|
||||||
![](img/ch1.png)
|
![](img/ch1.png)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
本书将是一趟关于数据系统原理、实践与应用的旅程,并讲述了设计数据密集型应用的方法。我们将探索不同工具之间的共性与特性,以及各自的实现原理。
|
本书将是一趟关于数据系统原理、实践与应用的旅程,并讲述了设计数据密集型应用的方法。我们将探索不同工具之间的共性与特性,以及各自的实现原理。
|
||||||
|
|
||||||
本章将从我们所要实现的基础目标开始:可靠、可扩展、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。
|
本章将从我们所要实现的基础目标开始:可靠、可伸缩、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -58,9 +58,9 @@
|
|||||||
|
|
||||||
系统在**困境(adversity)**(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。
|
系统在**困境(adversity)**(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。
|
||||||
|
|
||||||
***可扩展性(Scalability)***
|
***可伸缩性(Scalability)***
|
||||||
|
|
||||||
有合理的办法应对系统的增长(数据量、流量、复杂性)(参阅“[可扩展性](#可扩展性)”)
|
有合理的办法应对系统的增长(数据量、流量、复杂性)(参阅“[可伸缩性](#可伸缩性)”)
|
||||||
|
|
||||||
***可维护性(Maintainability)***
|
***可维护性(Maintainability)***
|
||||||
|
|
||||||
@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
人们经常追求这些词汇,却没有清楚理解它们到底意味着什么。为了工程的严谨性,本章的剩余部分将探讨可靠性、可扩展性、可维护性的含义。为实现这些目标而使用的各种技术,架构和算法将在后续的章节中研究。
|
人们经常追求这些词汇,却没有清楚理解它们到底意味着什么。为了工程的严谨性,本章的剩余部分将探讨可靠性、可伸缩性、可维护性的含义。为实现这些目标而使用的各种技术,架构和算法将在后续的章节中研究。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -146,11 +146,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 可扩展性
|
## 可伸缩性
|
||||||
|
|
||||||
系统今天能可靠运行,并不意味未来也能可靠运行。服务 **降级(degradation)** 的一个常见原因是负载增加,例如:系统负载已经从一万个并发用户增长到十万个并发用户,或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多。
|
系统今天能可靠运行,并不意味未来也能可靠运行。服务 **降级(degradation)** 的一个常见原因是负载增加,例如:系统负载已经从一万个并发用户增长到十万个并发用户,或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多。
|
||||||
|
|
||||||
**可扩展性(Scalability)** 是用来描述系统应对负载增长能力的术语。但是请注意,这不是贴在系统上的一维标签:说“X可扩展”或“Y不可扩展”是没有任何意义的。相反,讨论可扩展性意味着考虑诸如“如果系统以特定方式增长,有什么选项可以应对增长?”和“如何增加计算资源来处理额外的负载?”等问题。
|
**可伸缩性(Scalability)** 是用来描述系统应对负载增长能力的术语。但是请注意,这不是贴在系统上的一维标签:说“X可伸缩”或“Y不可伸缩”是没有任何意义的。相反,讨论可伸缩性意味着考虑诸如“如果系统以特定方式增长,有什么选项可以应对增长?”和“如何增加计算资源来处理额外的负载?”等问题。
|
||||||
|
|
||||||
### 描述负载
|
### 描述负载
|
||||||
|
|
||||||
@ -168,7 +168,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
处理每秒12,000次写入(发推文的速率峰值)还是很简单的。然而推特的扩展性挑战并不是主要来自推特量,而是来自**扇出(fan-out)**——每个用户关注了很多人,也被很多人关注。
|
处理每秒12,000次写入(发推文的速率峰值)还是很简单的。然而推特的伸缩性挑战并不是主要来自推特量,而是来自**扇出(fan-out)**——每个用户关注了很多人,也被很多人关注。
|
||||||
|
|
||||||
[^ii]: 扇出:从电子工程学中借用的术语,它描述了输入连接到另一个门输出的逻辑门数量。 输出需要提供足够的电流来驱动所有连接的输入。 在事务处理系统中,我们使用它来描述为了服务一个传入请求而需要执行其他服务的请求数量。
|
[^ii]: 扇出:从电子工程学中借用的术语,它描述了输入连接到另一个门输出的逻辑门数量。 输出需要提供足够的电流来驱动所有连接的输入。 在事务处理系统中,我们使用它来描述为了服务一个传入请求而需要执行其他服务的请求数量。
|
||||||
|
|
||||||
@ -197,7 +197,7 @@
|
|||||||
|
|
||||||
然而方法2的缺点是,发推现在需要大量的额外工作。平均来说,一条推文会发往约75个关注者,所以每秒4.6k的发推写入,变成了对主页时间线缓存每秒345k的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实,一些用户有超过3000万的粉丝,这意味着一条推文就可能会导致主页时间线缓存的3000万次写入!及时完成这种操作是一个巨大的挑战 —— 推特尝试在5秒内向粉丝发送推文。
|
然而方法2的缺点是,发推现在需要大量的额外工作。平均来说,一条推文会发往约75个关注者,所以每秒4.6k的发推写入,变成了对主页时间线缓存每秒345k的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实,一些用户有超过3000万的粉丝,这意味着一条推文就可能会导致主页时间线缓存的3000万次写入!及时完成这种操作是一个巨大的挑战 —— 推特尝试在5秒内向粉丝发送推文。
|
||||||
|
|
||||||
在推特的例子中,每个用户粉丝数的分布(可能按这些用户的发推频率来加权)是探讨可扩展性的一个关键负载参数,因为它决定了扇出负载。你的应用程序可能具有非常不同的特征,但可以采用相似的原则来考虑它的负载。
|
在推特的例子中,每个用户粉丝数的分布(可能按这些用户的发推频率来加权)是探讨可伸缩性的一个关键负载参数,因为它决定了扇出负载。你的应用程序可能具有非常不同的特征,但可以采用相似的原则来考虑它的负载。
|
||||||
|
|
||||||
推特轶事的最终转折:现在已经稳健地实现了方法2,推特逐步转向了两种方法的混合。大多数用户发的推文会被扇出写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户(即名流)会被排除在外。当用户读取主页时间线时,分别地获取出该用户所关注的每位名流的推文,再与用户的主页时间线缓存合并,如方法1所示。这种混合方法能始终如一地提供良好性能。在[第12章](ch12.md)中我们将重新讨论这个例子,这在覆盖更多技术层面之后。
|
推特轶事的最终转折:现在已经稳健地实现了方法2,推特逐步转向了两种方法的混合。大多数用户发的推文会被扇出写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户(即名流)会被排除在外。当用户读取主页时间线时,分别地获取出该用户所关注的每位名流的推文,再与用户的主页时间线缓存合并,如方法1所示。这种混合方法能始终如一地提供良好性能。在[第12章](ch12.md)中我们将重新讨论这个例子,这在覆盖更多技术层面之后。
|
||||||
|
|
||||||
@ -242,7 +242,7 @@
|
|||||||
|
|
||||||
**排队延迟(queueing delay)** 通常占了高百分位点处响应时间的很大一部分。由于服务器只能并行处理少量的事务(如受其CPU核数的限制),所以只要有少量缓慢的请求就能阻碍后续请求的处理,这种效应有时被称为 **头部阻塞(head-of-line blocking)** 。即使后续请求在服务器上处理的非常迅速,由于需要等待先前请求完成,客户端最终看到的是缓慢的总体响应时间。因为存在这种效应,测量客户端的响应时间非常重要。
|
**排队延迟(queueing delay)** 通常占了高百分位点处响应时间的很大一部分。由于服务器只能并行处理少量的事务(如受其CPU核数的限制),所以只要有少量缓慢的请求就能阻碍后续请求的处理,这种效应有时被称为 **头部阻塞(head-of-line blocking)** 。即使后续请求在服务器上处理的非常迅速,由于需要等待先前请求完成,客户端最终看到的是缓慢的总体响应时间。因为存在这种效应,测量客户端的响应时间非常重要。
|
||||||
|
|
||||||
为测试系统的可扩展性而人为产生负载时,产生负载的客户端要独立于响应时间不断发送请求。如果客户端在发送下一个请求之前等待先前的请求完成,这种行为会产生人为排队的效果,使得测试时的队列比现实情况更短,使测量结果产生偏差【23】。
|
为测试系统的可伸缩性而人为产生负载时,产生负载的客户端要独立于响应时间不断发送请求。如果客户端在发送下一个请求之前等待先前的请求完成,这种行为会产生人为排队的效果,使得测试时的队列比现实情况更短,使测量结果产生偏差【23】。
|
||||||
|
|
||||||
> #### 实践中的百分位点
|
> #### 实践中的百分位点
|
||||||
>
|
>
|
||||||
@ -258,25 +258,25 @@
|
|||||||
|
|
||||||
### 应对负载的方法
|
### 应对负载的方法
|
||||||
|
|
||||||
现在我们已经讨论了用于描述负载的参数和用于衡量性能的指标。可以开始认真讨论可扩展性了:当负载参数增加时,如何保持良好的性能?
|
现在我们已经讨论了用于描述负载的参数和用于衡量性能的指标。可以开始认真讨论可伸缩性了:当负载参数增加时,如何保持良好的性能?
|
||||||
|
|
||||||
适应某个级别负载的架构不太可能应付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)** 非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向扩展),直到扩展成本或可用性需求迫使其改为分布式。
|
跨多台机器部署 **无状态服务(stateless services)** 非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向伸缩),直到伸缩成本或可用性需求迫使其改为分布式。
|
||||||
|
|
||||||
随着分布式系统的工具和抽象越来越好,至少对于某些类型的应用而言,这种常识可能会改变。可以预见分布式数据系统将成为未来的默认设置,即使对不处理大量数据或流量的场景也如此。本书的其余部分将介绍多种分布式数据系统,不仅讨论它们在可扩展性方面的表现,还包括易用性和可维护性。
|
随着分布式系统的工具和抽象越来越好,至少对于某些类型的应用而言,这种常识可能会改变。可以预见分布式数据系统将成为未来的默认设置,即使对不处理大量数据或流量的场景也如此。本书的其余部分将介绍多种分布式数据系统,不仅讨论它们在可伸缩性方面的表现,还包括易用性和可维护性。
|
||||||
|
|
||||||
大规模的系统架构通常是应用特定的—— 没有一招鲜吃遍天的通用可扩展架构(不正式的叫法:**万金油(magic scaling sauce)** )。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。
|
大规模的系统架构通常是应用特定的—— 没有一招鲜吃遍天的通用可伸缩架构(不正式的叫法:**万金油(magic scaling sauce)** )。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。
|
||||||
|
|
||||||
举个例子,用于处理每秒十万个请求(每个大小为1 kB)的系统与用于处理每分钟3个请求(每个大小为2GB)的系统看上去会非常不一样,尽管两个系统有同样的数据吞吐量。
|
举个例子,用于处理每秒十万个请求(每个大小为1 kB)的系统与用于处理每分钟3个请求(每个大小为2GB)的系统看上去会非常不一样,尽管两个系统有同样的数据吞吐量。
|
||||||
|
|
||||||
一个良好适配应用的可扩展架构,是围绕着**假设(assumption)** 建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可扩展至未来的假想负载要重要的多。
|
一个良好适配应用的可伸缩架构,是围绕着**假设(assumption)** 建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为伸缩所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可伸缩至未来的假想负载要重要的多。
|
||||||
|
|
||||||
尽管这些架构是应用程序特定的,但可扩展的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。
|
尽管这些架构是应用程序特定的,但可伸缩的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -298,9 +298,9 @@
|
|||||||
|
|
||||||
***可演化性(evolability)***
|
***可演化性(evolability)***
|
||||||
|
|
||||||
使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为**可扩展性(extensibility)**,**可修改性(modifiability)**或**可塑性(plasticity)**。
|
使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为**可伸缩性(extensibility)**,**可修改性(modifiability)**或**可塑性(plasticity)**。
|
||||||
|
|
||||||
和之前提到的可靠性、可扩展性一样,实现这些目标也没有简单的解决方案。不过我们会试着想象具有可操作性,简单性和可演化性的系统会是什么样子。
|
和之前提到的可靠性、可伸缩性一样,实现这些目标也没有简单的解决方案。不过我们会试着想象具有可操作性,简单性和可演化性的系统会是什么样子。
|
||||||
|
|
||||||
### 可操作性:人生苦短,关爱运维
|
### 可操作性:人生苦短,关爱运维
|
||||||
|
|
||||||
@ -365,16 +365,16 @@
|
|||||||
|
|
||||||
本章探讨了一些关于数据密集型应用的基本思考方式。这些原则将指导我们阅读本书的其余部分,那里将会深入技术细节。
|
本章探讨了一些关于数据密集型应用的基本思考方式。这些原则将指导我们阅读本书的其余部分,那里将会深入技术细节。
|
||||||
|
|
||||||
一个应用必须满足各种需求才称得上有用。有一些**功能需求(functional requirements)**(它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些**非功能性需求(nonfunctional )**(通用属性,例如安全性,可靠性,合规性,可扩展性,兼容性和可维护性)。在本章详细讨论了可靠性,可扩展性和可维护性。
|
一个应用必须满足各种需求才称得上有用。有一些**功能需求(functional requirements)**(它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些**非功能性需求(nonfunctional )**(通用属性,例如安全性,可靠性,合规性,可伸缩性,兼容性和可维护性)。在本章详细讨论了可靠性,可伸缩性和可维护性。
|
||||||
|
|
||||||
|
|
||||||
**可靠性(Reliability)** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的),软件(通常是系统性的Bug,很难处理),和人类(不可避免地时不时出错)。 **容错技术** 可以对终端用户隐藏某些类型的故障。
|
**可靠性(Reliability)** 意味着即使发生故障,系统也能正常工作。故障可能发生在硬件(通常是随机的和不相关的),软件(通常是系统性的Bug,很难处理),和人类(不可避免地时不时出错)。 **容错技术** 可以对终端用户隐藏某些类型的故障。
|
||||||
|
|
||||||
**可扩展性(Scalability)** 意味着即使在负载增加的情况下也有保持性能的策略。为了讨论可扩展性,我们首先需要定量描述负载和性能的方法。我们简要了解了推特主页时间线的例子,介绍描述负载的方法,并将响应时间百分位点作为衡量性能的一种方式。在可扩展的系统中可以添加 **处理容量(processing capacity)** 以在高负载下保持可靠。
|
**可伸缩性(Scalability)** 意味着即使在负载增加的情况下也有保持性能的策略。为了讨论可伸缩性,我们首先需要定量描述负载和性能的方法。我们简要了解了推特主页时间线的例子,介绍描述负载的方法,并将响应时间百分位点作为衡量性能的一种方式。在可伸缩的系统中可以添加 **处理容量(processing capacity)** 以在高负载下保持可靠。
|
||||||
|
|
||||||
**可维护性(Maintainability)** 有许多方面,但实质上是关于工程师和运维团队的生活质量的。良好的抽象可以帮助降低复杂度,并使系统易于修改和适应新的应用场景。良好的可操作性意味着对系统的健康状态具有良好的可见性,并拥有有效的管理手段。
|
**可维护性(Maintainability)** 有许多方面,但实质上是关于工程师和运维团队的生活质量的。良好的抽象可以帮助降低复杂度,并使系统易于修改和适应新的应用场景。良好的可操作性意味着对系统的健康状态具有良好的可见性,并拥有有效的管理手段。
|
||||||
|
|
||||||
不幸的是,使应用可靠、可扩展或可维护并不容易。但是某些模式和技术会不断重新出现在不同的应用中。在接下来的几章中,我们将看到一些数据系统的例子,并分析它们如何实现这些目标。
|
不幸的是,使应用可靠、可伸缩或可维护并不容易。但是某些模式和技术会不断重新出现在不同的应用中。在接下来的几章中,我们将看到一些数据系统的例子,并分析它们如何实现这些目标。
|
||||||
|
|
||||||
在本书后面的[第三部分](part-iii.md)中,我们将看到一种模式:几个组件协同工作以构成一个完整的系统(如[图1-1](img/fig1-1.png)中的例子)
|
在本书后面的[第三部分](part-iii.md)中,我们将看到一种模式:几个组件协同工作以构成一个完整的系统(如[图1-1](img/fig1-1.png)中的例子)
|
||||||
|
|
||||||
|
10
ch10.md
10
ch10.md
@ -28,7 +28,7 @@ z
|
|||||||
|
|
||||||
流处理介于在线和离线(批处理)之间,所以有时候被称为**准实时(near-real-time)**或**准在线(nearline)**处理。像批处理系统一样,流处理消费输入并产生输出(并不需要响应请求)。但是,流式作业在事件发生后不久就会对事件进行操作,而批处理作业则需等待固定的一组输入数据。这种差异使流处理系统比起批处理系统具有更低的延迟。由于流处理基于批处理,我们将在[第11章](ch11.md)讨论它。
|
流处理介于在线和离线(批处理)之间,所以有时候被称为**准实时(near-real-time)**或**准在线(nearline)**处理。像批处理系统一样,流处理消费输入并产生输出(并不需要响应请求)。但是,流式作业在事件发生后不久就会对事件进行操作,而批处理作业则需等待固定的一组输入数据。这种差异使流处理系统比起批处理系统具有更低的延迟。由于流处理基于批处理,我们将在[第11章](ch11.md)讨论它。
|
||||||
|
|
||||||
正如我们将在本章中看到的那样,批处理是构建可靠,可扩展和可维护应用程序的重要组成部分。例如,2004年发布的批处理算法Map-Reduce(可能被过分热情地)被称为“造就Google大规模可扩展性的算法”【2】。随后在各种开源数据系统中得到应用,包括Hadoop,CouchDB和MongoDB。
|
正如我们将在本章中看到的那样,批处理是构建可靠,可伸缩和可维护应用程序的重要组成部分。例如,2004年发布的批处理算法Map-Reduce(可能被过分热情地)被称为“造就Google大规模可伸缩性的算法”【2】。随后在各种开源数据系统中得到应用,包括Hadoop,CouchDB和MongoDB。
|
||||||
|
|
||||||
与多年前为数据仓库开发的并行处理系统【3,4】相比,MapReduce是一个相当低级别的编程模型,但它使得在商用硬件上能进行的处理规模迈上一个新的台阶。虽然MapReduce的重要性正在下降【5】,但它仍然值得去理解,因为它描绘了一幅关于批处理为什么有用,以及如何实用的清晰图景。
|
与多年前为数据仓库开发的并行处理系统【3,4】相比,MapReduce是一个相当低级别的编程模型,但它使得在商用硬件上能进行的处理规模迈上一个新的台阶。虽然MapReduce的重要性正在下降【5】,但它仍然值得去理解,因为它描绘了一幅关于批处理为什么有用,以及如何实用的清晰图景。
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
|
|||||||
|
|
||||||
另一方面,如果作业的工作集大于可用内存,则排序方法的优点是可以高效地使用磁盘。这与我们在“[SSTables和LSM树](ch3.md#SSTables和LSM树)”中讨论过的原理是一样的:数据块可以在内存中排序并作为段文件写入磁盘,然后多个排序好的段可以合并为一个更大的排序文件。 归并排序具有在磁盘上运行良好的顺序访问模式。 (请记住,针对顺序I/O进行优化是[第3章](ch3.md)中反复出现的主题,相同的模式在此重现)
|
另一方面,如果作业的工作集大于可用内存,则排序方法的优点是可以高效地使用磁盘。这与我们在“[SSTables和LSM树](ch3.md#SSTables和LSM树)”中讨论过的原理是一样的:数据块可以在内存中排序并作为段文件写入磁盘,然后多个排序好的段可以合并为一个更大的排序文件。 归并排序具有在磁盘上运行良好的顺序访问模式。 (请记住,针对顺序I/O进行优化是[第3章](ch3.md)中反复出现的主题,相同的模式在此重现)
|
||||||
|
|
||||||
GNU Coreutils(Linux)中的`sort `程序通过溢出至磁盘的方式来自动应对大于内存的数据集,并能同时使用多个CPU核进行并行排序【9】。这意味着我们之前看到的简单的Unix命令链很容易扩展到大数据集,且不会耗尽内存。瓶颈可能是从磁盘读取输入文件的速度。
|
GNU Coreutils(Linux)中的`sort `程序通过溢出至磁盘的方式来自动应对大于内存的数据集,并能同时使用多个CPU核进行并行排序【9】。这意味着我们之前看到的简单的Unix命令链很容易伸缩至大数据集,且不会耗尽内存。瓶颈可能是从磁盘读取输入文件的速度。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
|
|||||||
|
|
||||||
为了容忍机器和磁盘故障,文件块被复制到多台机器上。复制可能意味着多个机器上的相同数据的多个副本,如[第5章](ch5.md)中所述,或者诸如Reed-Solomon码这样的纠删码方案,它允许以比完全复制更低的存储开销以恢复丢失的数据【20,22】。这些技术与RAID相似,可以在连接到同一台机器的多个磁盘上提供冗余;区别在于在分布式文件系统中,文件访问和复制是在传统的数据中心网络上完成的,没有特殊的硬件。
|
为了容忍机器和磁盘故障,文件块被复制到多台机器上。复制可能意味着多个机器上的相同数据的多个副本,如[第5章](ch5.md)中所述,或者诸如Reed-Solomon码这样的纠删码方案,它允许以比完全复制更低的存储开销以恢复丢失的数据【20,22】。这些技术与RAID相似,可以在连接到同一台机器的多个磁盘上提供冗余;区别在于在分布式文件系统中,文件访问和复制是在传统的数据中心网络上完成的,没有特殊的硬件。
|
||||||
|
|
||||||
HDFS已经扩展的很不错了:在撰写本书时,最大的HDFS部署运行在上万台机器上,总存储容量达数百PB【23】。如此大的规模已经变得可行,因为使用商品硬件和开源软件的HDFS上的数据存储和访问成本远低于专用存储设备上的同等容量【24】。
|
HDFS的可伸缩性已经很不错了:在撰写本书时,最大的HDFS部署运行在上万台机器上,总存储容量达数百PB【23】。如此大的规模已经变得可行,因为使用商品硬件和开源软件的HDFS上的数据存储和访问成本远低于专用存储设备上的同等容量【24】。
|
||||||
|
|
||||||
### MapReduce作业执行
|
### MapReduce作业执行
|
||||||
|
|
||||||
@ -676,11 +676,11 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
|
|||||||
|
|
||||||
然而数据流引擎已经发现,支持除连接之外的更多**声明式特性**还有其他的优势。例如,如果一个回调函数只包含一个简单的过滤条件,或者只是从一条记录中选择了一些字段,那么在为每条记录调用函数时会有相当大的额外CPU开销。如果以声明方式表示这些简单的过滤和映射操作,那么查询优化器可以利用面向列的存储布局(参阅“[列存储](ch3.md#列存储)”),只从磁盘读取所需的列。 Hive,Spark DataFrames和Impala还使用了向量化执行(参阅“[内存带宽和向量处理](ch3.md#内存带宽和向量处理)”):在对CPU缓存友好的内部循环中迭代数据,避免函数调用。Spark生成JVM字节码【79】,Impala使用LLVM为这些内部循环生成本机代码【41】。
|
然而数据流引擎已经发现,支持除连接之外的更多**声明式特性**还有其他的优势。例如,如果一个回调函数只包含一个简单的过滤条件,或者只是从一条记录中选择了一些字段,那么在为每条记录调用函数时会有相当大的额外CPU开销。如果以声明方式表示这些简单的过滤和映射操作,那么查询优化器可以利用面向列的存储布局(参阅“[列存储](ch3.md#列存储)”),只从磁盘读取所需的列。 Hive,Spark DataFrames和Impala还使用了向量化执行(参阅“[内存带宽和向量处理](ch3.md#内存带宽和向量处理)”):在对CPU缓存友好的内部循环中迭代数据,避免函数调用。Spark生成JVM字节码【79】,Impala使用LLVM为这些内部循环生成本机代码【41】。
|
||||||
|
|
||||||
通过在高级API中引入声明式的部分,并使查询优化器可以在执行期间利用这些来做优化,批处理框架看起来越来越像MPP数据库了(并且能实现可与之媲美的性能)。同时,通过拥有运行任意代码和以任意格式读取数据的可扩展性,它们保持了灵活性的优势。
|
通过在高级API中引入声明式的部分,并使查询优化器可以在执行期间利用这些来做优化,批处理框架看起来越来越像MPP数据库了(并且能实现可与之媲美的性能)。同时,通过拥有运行任意代码和以任意格式读取数据的可伸缩性,它们保持了灵活性的优势。
|
||||||
|
|
||||||
#### 专业化的不同领域
|
#### 专业化的不同领域
|
||||||
|
|
||||||
尽管能够运行任意代码的可扩展性是很有用的,但是也有很多常见的例子,不断重复着标准的处理模式。因而这些模式值得拥有自己的可重用通用构建模块实现,传统上,MPP数据库满足了商业智能分析和业务报表的需求,但这只是许多使用批处理的领域之一。
|
尽管能够运行任意代码的可伸缩性是很有用的,但是也有很多常见的例子,不断重复着标准的处理模式。因而这些模式值得拥有自己的可重用通用构建模块实现,传统上,MPP数据库满足了商业智能分析和业务报表的需求,但这只是许多使用批处理的领域之一。
|
||||||
|
|
||||||
另一个越来越重要的领域是统计和数值算法,它们是机器学习应用所需要的(例如分类器和推荐系统)。可重用的实现正在出现:例如,Mahout在MapReduce,Spark和Flink之上实现了用于机器学习的各种算法,而MADlib在关系型MPP数据库(Apache HAWQ)中实现了类似的功能【54】。
|
另一个越来越重要的领域是统计和数值算法,它们是机器学习应用所需要的(例如分类器和推荐系统)。可重用的实现正在出现:例如,Mahout在MapReduce,Spark和Flink之上实现了用于机器学习的各种算法,而MADlib在关系型MPP数据库(Apache HAWQ)中实现了类似的功能【54】。
|
||||||
|
|
||||||
|
4
ch11.md
4
ch11.md
@ -19,7 +19,7 @@
|
|||||||
日常批处理中的问题是,输入的变更只会在一天之后的输出中反映出来,这对于许多急躁的用户来说太慢了。为了减少延迟,我们可以更频繁地运行处理 —— 比如说,在每秒钟的末尾 —— 或者甚至更连续一些,完全抛开固定的时间切片,当事件发生时就立即进行处理,这就是**流处理(stream processing)**背后的想法。
|
日常批处理中的问题是,输入的变更只会在一天之后的输出中反映出来,这对于许多急躁的用户来说太慢了。为了减少延迟,我们可以更频繁地运行处理 —— 比如说,在每秒钟的末尾 —— 或者甚至更连续一些,完全抛开固定的时间切片,当事件发生时就立即进行处理,这就是**流处理(stream processing)**背后的想法。
|
||||||
|
|
||||||
一般来说,“流”是指随着时间的推移逐渐可用的数据。这个概念出现在很多地方:Unix的stdin和stdout,编程语言(惰性列表)【2】,文件系统API(如Java的`FileInputStream`),TCP连接,通过互联网传送音频和视频等等。
|
一般来说,“流”是指随着时间的推移逐渐可用的数据。这个概念出现在很多地方:Unix的stdin和stdout,编程语言(惰性列表)【2】,文件系统API(如Java的`FileInputStream`),TCP连接,通过互联网传送音频和视频等等。
|
||||||
在本章中,我们将把**事件流(event stream)**视为一种数据管理机制:无界限,增量处理,与[上一章](ch10.md)中批量数据相对应。我们将首先讨论怎样表示、存储、通过网络传输流。在“[数据库和流](#数据库和流)”中,我们将研究流和数据库之间的关系。最后在“[流处理](#流处理)”中,我们将研究连续处理这些流的方法和工具,以及它们用于应用构建的方式。
|
在本章中,我们将把**事件流(event stream)**视为一种数据管理机制:无界限,增量处理,与[上一章](ch10.md)中批量数据相对应。我们将首先讨论怎样表示、存储、通过网络传输流。在“[数据库和流](#数据库和流)”中,我们将研究流和数据库之间的关系。最后在“[流处理](#流处理)”中,我们将研究连续处理这些流的方法和工具,以及它们用于应用构建的方式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +142,7 @@
|
|||||||
|
|
||||||
同样的结构可以用于实现消息代理:生产者通过将消息追加到日志末尾来发送消息,而消费者通过依次读取日志来接收消息。如果消费者读到日志末尾,则会等待新消息追加的通知。 Unix工具`tail -f` 能监视文件被追加写入的数据,基本上就是这样工作的。
|
同样的结构可以用于实现消息代理:生产者通过将消息追加到日志末尾来发送消息,而消费者通过依次读取日志来接收消息。如果消费者读到日志末尾,则会等待新消息追加的通知。 Unix工具`tail -f` 能监视文件被追加写入的数据,基本上就是这样工作的。
|
||||||
|
|
||||||
为了扩展到比单个磁盘所能提供的更高吞吐量,可以对日志进行**分区**(在[第6章](ch6.md)的意义上)。不同的分区可以托管在不同的机器上,且每个分区都拆分出一份能独立于其他分区进行读写的日志。一个主题可以定义为一组携带相同类型消息的分区。这种方法如[图11-3](img/fig11-3.png)所示。
|
为了伸缩超出单个磁盘所能提供的更高吞吐量,可以对日志进行**分区**(在[第6章](ch6.md)的意义上)。不同的分区可以托管在不同的机器上,且每个分区都拆分出一份能独立于其他分区进行读写的日志。一个主题可以定义为一组携带相同类型消息的分区。这种方法如[图11-3](img/fig11-3.png)所示。
|
||||||
|
|
||||||
在每个分区内,代理为每个消息分配一个单调递增的序列号或**偏移量(offset)**(在[图11-3](img/fig11-3.png)中,框中的数字是消息偏移量)。这种序列号是有意义的,因为分区是仅追加写入的,所以分区内的消息是完全有序的。没有跨不同分区的顺序保证。
|
在每个分区内,代理为每个消息分配一个单调递增的序列号或**偏移量(offset)**(在[图11-3](img/fig11-3.png)中,框中的数字是消息偏移量)。这种序列号是有意义的,因为分区是仅追加写入的,所以分区内的消息是完全有序的。没有跨不同分区的顺序保证。
|
||||||
|
|
||||||
|
28
ch12.md
28
ch12.md
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
对未来的看法与推测当然具有很大的主观性。所以在撰写本章时,当提及我个人的观点时会使用第一人称。您完全可以不同意这些观点并提出自己的看法,但我希望本章中的概念,至少能成为富有成效的讨论出发点,并澄清一些经常被混淆的概念。
|
对未来的看法与推测当然具有很大的主观性。所以在撰写本章时,当提及我个人的观点时会使用第一人称。您完全可以不同意这些观点并提出自己的看法,但我希望本章中的概念,至少能成为富有成效的讨论出发点,并澄清一些经常被混淆的概念。
|
||||||
|
|
||||||
[第1章](ch1.md)概述了本书的目标:探索如何创建**可靠**,**可扩展**和**可维护**的应用与系统。这一主题贯穿了所有的章节:例如,我们讨论了许多有助于提高可靠性的容错算法,有助于提高可扩展性的分区,以及有助于提高可维护性的演化与抽象机制。在本章中,我们将把所有这些想法结合在一起,并在它们的基础上展望未来。我们的目标是,发现如何设计出比现有应用更好的应用 —— 健壮,正确,可演化,且最终对人类有益。
|
[第1章](ch1.md)概述了本书的目标:探索如何创建**可靠**,**可伸缩**和**可维护**的应用与系统。这一主题贯穿了所有的章节:例如,我们讨论了许多有助于提高可靠性的容错算法,有助于提高可伸缩性的分区,以及有助于提高可维护性的演化与抽象机制。在本章中,我们将把所有这些想法结合在一起,并在它们的基础上展望未来。我们的目标是,发现如何设计出比现有应用更好的应用 —— 健壮,正确,可演化,且最终对人类有益。
|
||||||
|
|
||||||
## 数据集成
|
## 数据集成
|
||||||
|
|
||||||
@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
#### 全局有序的限制
|
#### 全局有序的限制
|
||||||
|
|
||||||
对于足够小的系统,构建一个完全有序的事件日志是完全可行的(正如单主复制数据库的流行所证明的那样,这正好建立了这样一种日志)。但是,随着系统向更大更复杂的工作负载扩展,限制开始出现:
|
对于足够小的系统,构建一个完全有序的事件日志是完全可行的(正如单主复制数据库的流行所证明的那样,这正好建立了这样一种日志)。但是,随着系统向更大更复杂的工作负载伸缩,限制开始出现:
|
||||||
|
|
||||||
* 在大多数情况下,构建完全有序的日志,需要所有事件汇集于决定顺序的单个领导节点。如果事件吞吐量大于单台计算机的处理能力,则需要将其分割到多台计算机上(参见“[分区日志](ch11.md#分区日志)”)。然后两个不同分区中的事件顺序关系就不明确了。
|
* 在大多数情况下,构建完全有序的日志,需要所有事件汇集于决定顺序的单个领导节点。如果事件吞吐量大于单台计算机的处理能力,则需要将其分割到多台计算机上(参见“[分区日志](ch11.md#分区日志)”)。然后两个不同分区中的事件顺序关系就不明确了。
|
||||||
|
|
||||||
@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
* 某些应用程序在客户端保存状态,该状态在用户输入时立即更新(无需等待服务器确认),甚至可以继续脱机工作(参阅“[脱机操作的客户端](ch5.md#脱机操作的客户端)”)。有了这样的应用程序,客户端和服务器很可能以不同的顺序看到事件。
|
* 某些应用程序在客户端保存状态,该状态在用户输入时立即更新(无需等待服务器确认),甚至可以继续脱机工作(参阅“[脱机操作的客户端](ch5.md#脱机操作的客户端)”)。有了这样的应用程序,客户端和服务器很可能以不同的顺序看到事件。
|
||||||
|
|
||||||
在形式上,决定事件的全局顺序称为**全序广播**,相当于**共识**(参阅“[共识算法和全序广播](ch9.md#共识算法和全序广播)”)。大多数共识算法都是针对单个节点的吞吐量足以处理整个事件流的情况而设计的,并且这些算法不提供多个节点共享事件排序工作的机制。设计可以扩展到单个节点的吞吐量之上,且在地理散布环境中仍然工作良好的的共识算法仍然是一个开放的研究问题。
|
在形式上,决定事件的全局顺序称为**全序广播**,相当于**共识**(参阅“[共识算法和全序广播](ch9.md#共识算法和全序广播)”)。大多数共识算法都是针对单个节点的吞吐量足以处理整个事件流的情况而设计的,并且这些算法不提供多个节点共享事件排序工作的机制。设计可以伸缩至单个节点的吞吐量之上,且在地理散布环境中仍然工作良好的的共识算法仍然是一个开放的研究问题。
|
||||||
|
|
||||||
#### 排序事件以捕捉因果关系
|
#### 排序事件以捕捉因果关系
|
||||||
|
|
||||||
@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
原则上,衍生数据系统可以同步地维护,就像关系数据库在与被索引表写入操作相同的事务中同步更新辅助索引一样。然而,异步是基于事件日志的系统稳健的原因:它允许系统的一部分故障被抑制在本地,而如果任何一个参与者失败,分布式事务将中止,因此它们倾向于通过将故障传播到系统的其余部分来放大故障(请参阅“[分布式事务的限制](ch9.md#分布式事务的限制)”)。
|
原则上,衍生数据系统可以同步地维护,就像关系数据库在与被索引表写入操作相同的事务中同步更新辅助索引一样。然而,异步是基于事件日志的系统稳健的原因:它允许系统的一部分故障被抑制在本地,而如果任何一个参与者失败,分布式事务将中止,因此它们倾向于通过将故障传播到系统的其余部分来放大故障(请参阅“[分布式事务的限制](ch9.md#分布式事务的限制)”)。
|
||||||
|
|
||||||
我们在“[分区与次级索引](ch6.md#分区与次级索引)”中看到,二级索引经常跨越分区边界。具有二级索引的分区系统需要将写入发送到多个分区(如果索引按关键词分区的话)或将读取发送到所有分区(如果索引是按文档分区的话)。如果索引是异步维护的,这种交叉分区通信也是最可靠和最可扩展的【8】(另请参阅“[多分区数据处理](ch11.md#多分区数据处理)”)。
|
我们在“[分区与次级索引](ch6.md#分区与次级索引)”中看到,二级索引经常跨越分区边界。具有二级索引的分区系统需要将写入发送到多个分区(如果索引按关键词分区的话)或将读取发送到所有分区(如果索引是按文档分区的话)。如果索引是异步维护的,这种交叉分区通信也是最可靠和最可伸缩的【8】(另请参阅“[多分区数据处理](ch11.md#多分区数据处理)”)。
|
||||||
|
|
||||||
#### 应用演化后重新处理数据
|
#### 应用演化后重新处理数据
|
||||||
|
|
||||||
@ -216,7 +216,7 @@
|
|||||||
|
|
||||||
#### 开展分拆工作
|
#### 开展分拆工作
|
||||||
|
|
||||||
联合和分拆是一个硬币的两面:用不同的组件构成可靠,可扩展和可维护的系统。联合只读查询需要将一个数据模型映射到另一个数据模型,这需要一些思考,但最终还是一个可解决的问题。我认为同步写入到几个存储系统是更困难的工程问题,所以我将重点关注它。
|
联合和分拆是一个硬币的两面:用不同的组件构成可靠,可伸缩和可维护的系统。联合只读查询需要将一个数据模型映射到另一个数据模型,这需要一些思考,但最终还是一个可解决的问题。我认为同步写入到几个存储系统是更困难的工程问题,所以我将重点关注它。
|
||||||
|
|
||||||
传统的同步写入方法需要跨异构存储系统的分布式事务【18】,我认为这是错误的解决方案(请参阅“[导出的数据与分布式事务]()”第495页)。单个存储或流处理系统内的事务是可行的,但是当数据跨越不同技术之间的边界时,我认为具有幂等写入的异步事件日志是一种更加健壮和实用的方法。
|
传统的同步写入方法需要跨异构存储系统的分布式事务【18】,我认为这是错误的解决方案(请参阅“[导出的数据与分布式事务]()”第495页)。单个存储或流处理系统内的事务是可行的,但是当数据跨越不同技术之间的边界时,我认为具有幂等写入的异步事件日志是一种更加健壮和实用的方法。
|
||||||
|
|
||||||
@ -253,7 +253,7 @@
|
|||||||
|
|
||||||
即使是**电子表格**也在数据流编程能力上甩开大多数主流编程语言几条街【33】。在电子表格中,可以将公式放入一个单元格中(例如,另一列中的单元格求和值),并且只要公式的任何输入发生变更,公式的结果都会自动重新计算。这正是我们在数据系统层次所需要的:当数据库中的记录发生变更时,我们希望自动更新该记录的任何索引,并且自动刷新依赖于记录的任何缓存视图或聚合。您不必担心这种刷新如何发生的技术细节,但能够简单地相信它可以正常工作。
|
即使是**电子表格**也在数据流编程能力上甩开大多数主流编程语言几条街【33】。在电子表格中,可以将公式放入一个单元格中(例如,另一列中的单元格求和值),并且只要公式的任何输入发生变更,公式的结果都会自动重新计算。这正是我们在数据系统层次所需要的:当数据库中的记录发生变更时,我们希望自动更新该记录的任何索引,并且自动刷新依赖于记录的任何缓存视图或聚合。您不必担心这种刷新如何发生的技术细节,但能够简单地相信它可以正常工作。
|
||||||
|
|
||||||
因此,我认为绝大多数数据系统仍然可以从VisiCalc在1979年已经具备的功能中学习【34】。与电子表格的不同之处在于,今天的数据系统需要具有容错性,可扩展性以及持久存储数据。它们还需要能够整合不同人群编写的不同技术,并重用现有的库和服务:期望使用某种特定语言,框架或工具开发所有软件是不切实际的。
|
因此,我认为绝大多数数据系统仍然可以从VisiCalc在1979年已经具备的功能中学习【34】。与电子表格的不同之处在于,今天的数据系统需要具有容错性,可伸缩性以及持久存储数据。它们还需要能够整合不同人群编写的不同技术,并重用现有的库和服务:期望使用某种特定语言,框架或工具开发所有软件是不切实际的。
|
||||||
|
|
||||||
在本节中,我将详细介绍这些想法,并探讨一些围绕分拆数据库和数据流的想法构建应用的方法。
|
在本节中,我将详细介绍这些想法,并探讨一些围绕分拆数据库和数据流的想法构建应用的方法。
|
||||||
|
|
||||||
@ -307,7 +307,7 @@
|
|||||||
|
|
||||||
#### 流处理器和服务
|
#### 流处理器和服务
|
||||||
|
|
||||||
当今流行的应用开发风格涉及将功能分解为一组通过同步网络请求(如REST API)进行通信的**服务(service)**(参阅“[通过服务实现数据流:REST和RPC](ch4.md#通过服务实现数据流:REST和RPC)”)。这种面向服务的架构优于单一庞大应用的优势主要在于:通过松散耦合来提供组织上的可扩展性:不同的团队可以专职于不同的服务上,从而减少团队之间的协调工作(因为服务可以独立部署和更新)。
|
当今流行的应用开发风格涉及将功能分解为一组通过同步网络请求(如REST API)进行通信的**服务(service)**(参阅“[通过服务实现数据流:REST和RPC](ch4.md#通过服务实现数据流:REST和RPC)”)。这种面向服务的架构优于单一庞大应用的优势主要在于:通过松散耦合来提供组织上的可伸缩性:不同的团队可以专职于不同的服务上,从而减少团队之间的协调工作(因为服务可以独立部署和更新)。
|
||||||
|
|
||||||
在数据流中组装流算子与微服务方法有很多相似之处【40】。但底层通信机制是有很大区别:数据流采用单向异步消息流,而不是同步的请求/响应式交互。
|
在数据流中组装流算子与微服务方法有很多相似之处【40】。但底层通信机制是有很大区别:数据流采用单向异步消息流,而不是同步的请求/响应式交互。
|
||||||
|
|
||||||
@ -424,7 +424,7 @@
|
|||||||
|
|
||||||
我们希望构建可靠且**正确**的应用(即使面对各种故障,程序的语义也能被很好地定义与理解)。约四十年来,原子性,隔离性和持久性([第7章](ch7.md))等事务特性一直是构建正确应用的首选工具。然而这些地基没有看上去那么牢固:例如弱隔离级别带来的困惑可以佐证(请参见“[弱隔离级别](ch7.md#弱隔离级别)”)。
|
我们希望构建可靠且**正确**的应用(即使面对各种故障,程序的语义也能被很好地定义与理解)。约四十年来,原子性,隔离性和持久性([第7章](ch7.md))等事务特性一直是构建正确应用的首选工具。然而这些地基没有看上去那么牢固:例如弱隔离级别带来的困惑可以佐证(请参见“[弱隔离级别](ch7.md#弱隔离级别)”)。
|
||||||
|
|
||||||
事务在某些领域被完全抛弃,并被提供更好性能与可扩展性的模型取代,但更复杂的语义(例如,参阅“[无领导者复制](ch5.md#无领导者复制)”)。**一致性(Consistency)** 经常被谈起,但其定义并不明确(“[一致性](ch5.md#一致性)”和[第9章](ch9.md))。有些人断言我们应当为了高可用而“拥抱弱一致性”,但却对这些概念实际上意味着什么缺乏清晰的认识。
|
事务在某些领域被完全抛弃,并被提供更好性能与可伸缩性的模型取代,但更复杂的语义(例如,参阅“[无领导者复制](ch5.md#无领导者复制)”)。**一致性(Consistency)** 经常被谈起,但其定义并不明确(“[一致性](ch5.md#一致性)”和[第9章](ch9.md))。有些人断言我们应当为了高可用而“拥抱弱一致性”,但却对这些概念实际上意味着什么缺乏清晰的认识。
|
||||||
|
|
||||||
对于如此重要的话题,我们的理解,以及我们的工程方法却是惊人地薄弱。例如,确定在特定事务隔离等级或复制配置下运行特定应用是否安全是非常困难的【51,52】。通常简单的解决方案似乎在低并发性的情况下工作正常,并且没有错误,但在要求更高的情况下却会出现许多微妙的错误。
|
对于如此重要的话题,我们的理解,以及我们的工程方法却是惊人地薄弱。例如,确定在特定事务隔离等级或复制配置下运行特定应用是否安全是非常困难的【51,52】。通常简单的解决方案似乎在低并发性的情况下工作正常,并且没有错误,但在要求更高的情况下却会出现许多微妙的错误。
|
||||||
|
|
||||||
@ -476,7 +476,7 @@ COMMIT;
|
|||||||
#### 操作标识符
|
#### 操作标识符
|
||||||
|
|
||||||
要在通过几跳的网络通信上使操作具有幂等性,仅仅依赖数据库提供的事务机制是不够的 —— 你需要考虑**端到端(end-to-end)** 的请求流。
|
要在通过几跳的网络通信上使操作具有幂等性,仅仅依赖数据库提供的事务机制是不够的 —— 你需要考虑**端到端(end-to-end)** 的请求流。
|
||||||
例如,你可以为操作生成一个唯一的标识符(例如UUID),并将其作为隐藏表单字段包含在客户端应用中,或通过计算所有表单相关字段的散列来生成操作ID 【3】。如果Web浏览器提交了两次POST请求,这两个请求将具有相同的操作ID。然后,你可以将该操作ID一路传递到数据库,并检查你是否曾经使用给定的ID执行过一个操作,如[例12-2]()中所示。
|
例如,你可以为操作生成一个唯一的标识符(例如UUID),并将其作为隐藏表单字段包含在客户端应用中,或通过计算所有表单相关字段的散列来生成操作ID 【3】。如果Web浏览器提交了两次POST请求,这两个请求将具有相同的操作ID。然后,你可以将该操作ID一路传递到数据库,并检查你是否曾经使用给定的ID执行过一个操作,如[例12-2]()中所示。
|
||||||
|
|
||||||
**例12-2 使用唯一ID来抑制重复请求**
|
**例12-2 使用唯一ID来抑制重复请求**
|
||||||
|
|
||||||
@ -536,7 +536,7 @@ COMMIT;
|
|||||||
|
|
||||||
达成这一共识的最常见方式是使单个节点作为领导,并使其负责所有决策。只要你不介意所有请求都挤过单个节点(即使客户端位于世界的另一端),只要该节点没有失效,系统就能正常工作。如果你需要容忍领导者失效,那么就又回到了共识问题(参阅“[单领导者复制与共识](ch9.md#单领导者复制与共识)”)。
|
达成这一共识的最常见方式是使单个节点作为领导,并使其负责所有决策。只要你不介意所有请求都挤过单个节点(即使客户端位于世界的另一端),只要该节点没有失效,系统就能正常工作。如果你需要容忍领导者失效,那么就又回到了共识问题(参阅“[单领导者复制与共识](ch9.md#单领导者复制与共识)”)。
|
||||||
|
|
||||||
唯一性检查可以通过对唯一性字段分区做横向扩展。例如,如果需要通过请求ID确保唯一性(如[例12-2]()所示),你可以确保所有具有相同请求ID的请求都被路由到同一分区(参阅[第6章](ch6.md))。如果你需要让用户名是唯一的,则可以按用户名的散列值做分区。
|
唯一性检查可以通过对唯一性字段分区做横向伸缩。例如,如果需要通过请求ID确保唯一性(如[例12-2]()所示),你可以确保所有具有相同请求ID的请求都被路由到同一分区(参阅[第6章](ch6.md))。如果你需要让用户名是唯一的,则可以按用户名的散列值做分区。
|
||||||
|
|
||||||
但异步多主复制排除在外,因为可能会发生不同主库同时接受冲突写操作的情况,因而这些值不再是唯一的(参阅“[实现可线性化系统](ch9.md#实现可线性化系统)”)。如果你想立刻拒绝任何违背约束的写入,同步协调是无法避免的【56】。
|
但异步多主复制排除在外,因为可能会发生不同主库同时接受冲突写操作的情况,因而这些值不再是唯一的(参阅“[实现可线性化系统](ch9.md#实现可线性化系统)”)。如果你想立刻拒绝任何违背约束的写入,同步协调是无法避免的【56】。
|
||||||
|
|
||||||
@ -551,7 +551,7 @@ COMMIT;
|
|||||||
3. 请求用户名的客户端监视输出流,等待与其请求相对应的成功或拒绝消息。
|
3. 请求用户名的客户端监视输出流,等待与其请求相对应的成功或拒绝消息。
|
||||||
|
|
||||||
|
|
||||||
该算法基本上与“[使用全序广播实现线性一致的存储](ch9.md#使用全序广播实现线性一致的存储)”中的算法相同。它可以简单地通过增加分区数扩展至较大的请求吞吐量,因为每个分区可以被独立处理。
|
该算法基本上与“[使用全序广播实现线性一致的存储](ch9.md#使用全序广播实现线性一致的存储)”中的算法相同。它可以简单地通过增加分区数伸缩至较大的请求吞吐量,因为每个分区可以被独立处理。
|
||||||
|
|
||||||
该方法不仅适用于唯一性约束,而且适用于许多其他类型的约束。其基本原理是,任何可能冲突的写入都会路由到相同的分区并按顺序处理。正如“[什么是冲突?](ch5.md#什么是冲突?)”与“[写入偏差与幻读](ch7.md#写入偏差与幻读)”中所述,冲突的定义可能取决于应用,但流处理器可以使用任意逻辑来验证请求。这个想法与Bayou在90年代开创的方法类似【58】。
|
该方法不仅适用于唯一性约束,而且适用于许多其他类型的约束。其基本原理是,任何可能冲突的写入都会路由到相同的分区并按顺序处理。正如“[什么是冲突?](ch5.md#什么是冲突?)”与“[写入偏差与幻读](ch7.md#写入偏差与幻读)”中所述,冲突的定义可能取决于应用,但流处理器可以使用任意逻辑来验证请求。这个想法与Bayou在90年代开创的方法类似【58】。
|
||||||
|
|
||||||
@ -732,7 +732,7 @@ COMMIT;
|
|||||||
|
|
||||||
## 做正确的事情
|
## 做正确的事情
|
||||||
|
|
||||||
在本书的最后部分,我想退后一步。在本书中,我们考察了各种不同的数据系统架构,评价了它们的优点与缺点,并探讨了构建可靠,可扩展,可维护应用的技术。但是,我们忽略了讨论中一个重要而基础的部分,现在我想补充一下。
|
在本书的最后部分,我想退后一步。在本书中,我们考察了各种不同的数据系统架构,评价了它们的优点与缺点,并探讨了构建可靠,可伸缩,可维护应用的技术。但是,我们忽略了讨论中一个重要而基础的部分,现在我想补充一下。
|
||||||
|
|
||||||
每个系统都服务于一个目的;我们采取的每个举措都会同时产生期望的后果与意外的后果。这个目的可能只是简单地赚钱,但其对世界的影响,可能会远远超出最初的目的。我们,建立这些系统的工程师,有责任去仔细考虑这些后果,并有意识地决定,我们希望生活在怎样的世界中。
|
每个系统都服务于一个目的;我们采取的每个举措都会同时产生期望的后果与意外的后果。这个目的可能只是简单地赚钱,但其对世界的影响,可能会远远超出最初的目的。我们,建立这些系统的工程师,有责任去仔细考虑这些后果,并有意识地决定,我们希望生活在怎样的世界中。
|
||||||
|
|
||||||
@ -834,7 +834,7 @@ COMMIT;
|
|||||||
|
|
||||||
允许在线服务的用户控制其隐私设置,例如控制其他用户可以看到哪些东西,是将一些控制交还给用户的第一步。但无论怎么设置,服务本身仍然可以不受限制地访问数据,并能以隐私策略允许的任何方式自由使用它。即使服务承诺不会将数据出售给第三方,它通常会授予自己不受限制的权利,以便在内部处理与分析数据,而且往往比用户公开可见的部分要深入的多。
|
允许在线服务的用户控制其隐私设置,例如控制其他用户可以看到哪些东西,是将一些控制交还给用户的第一步。但无论怎么设置,服务本身仍然可以不受限制地访问数据,并能以隐私策略允许的任何方式自由使用它。即使服务承诺不会将数据出售给第三方,它通常会授予自己不受限制的权利,以便在内部处理与分析数据,而且往往比用户公开可见的部分要深入的多。
|
||||||
|
|
||||||
这种从个体到公司的大规模隐私权转移在历史上是史无前例的【99】。监控一直存在,但它过去是昂贵的,手动的,不是可扩展的,自动化的。信任关系始终存在,例如患者与其医生之间,或被告与其律师之间 —— 但在这些情况下,数据的使用严格受到道德,法律和监管限制的约束。互联网服务使得在未经有意义的同意下收集大量敏感信息变得容易得多,而且无需用户理解他们的私人数据到底发生了什么。
|
这种从个体到公司的大规模隐私权转移在历史上是史无前例的【99】。监控一直存在,但它过去是昂贵的,手动的,不是可伸缩的,自动化的。信任关系始终存在,例如患者与其医生之间,或被告与其律师之间 —— 但在这些情况下,数据的使用严格受到道德,法律和监管限制的约束。互联网服务使得在未经有意义的同意下收集大量敏感信息变得容易得多,而且无需用户理解他们的私人数据到底发生了什么。
|
||||||
|
|
||||||
#### 数据资产与权力
|
#### 数据资产与权力
|
||||||
|
|
||||||
@ -892,7 +892,7 @@ COMMIT;
|
|||||||
|
|
||||||
衍生状态可以通过观察底层数据的变更来更新。此外,衍生状态本身可以进一步被下游消费者观察。我们甚至可以将这种数据流一路传送至显示数据的终端用户设备,从而构建可动态更新以反映数据变更,并在离线时能继续工作的用户界面。
|
衍生状态可以通过观察底层数据的变更来更新。此外,衍生状态本身可以进一步被下游消费者观察。我们甚至可以将这种数据流一路传送至显示数据的终端用户设备,从而构建可动态更新以反映数据变更,并在离线时能继续工作的用户界面。
|
||||||
|
|
||||||
接下来,我们讨论了如何确保所有这些处理在出现故障时保持正确。我们看到可扩展的强完整性保证可以通过异步事件处理来实现,通过使用端到端操作标识符使操作幂等,以及通过异步检查约束。客户端可以等到检查通过,或者不等待继续前进,但是可能会冒有违反约束需要道歉的风险。这种方法比使用分布式事务的传统方法更具可扩展性与可靠性,并且在实践中适用于很多业务流程。
|
接下来,我们讨论了如何确保所有这些处理在出现故障时保持正确。我们看到可伸缩的强完整性保证可以通过异步事件处理来实现,通过使用端到端操作标识符使操作幂等,以及通过异步检查约束。客户端可以等到检查通过,或者不等待继续前进,但是可能会冒有违反约束需要道歉的风险。这种方法比使用分布式事务的传统方法更具可伸缩性与可靠性,并且在实践中适用于很多业务流程。
|
||||||
|
|
||||||
通过围绕数据流构建应用,并异步检查约束,我们可以避免绝大多数的协调工作,创建保证完整性且性能仍然表现良好的系统,即使在地理散布的情况下与出现故障时亦然。然后,我们对使用审计来验证数据完整性,以及损坏检测进行了一些讨论。
|
通过围绕数据流构建应用,并异步检查约束,我们可以避免绝大多数的协调工作,创建保证完整性且性能仍然表现良好的系统,即使在地理散布的情况下与出现故障时亦然。然后,我们对使用审计来验证数据完整性,以及损坏检测进行了一些讨论。
|
||||||
|
|
||||||
|
11
ch2.md
11
ch2.md
@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
采用NoSQL数据库的背后有几个驱动因素,其中包括:
|
采用NoSQL数据库的背后有几个驱动因素,其中包括:
|
||||||
|
|
||||||
* 需要比关系数据库更好的可扩展性,包括非常大的数据集或非常高的写入吞吐量
|
* 需要比关系数据库更好的可伸缩性,包括非常大的数据集或非常高的写入吞吐量
|
||||||
* 相比商业数据库产品,免费和开源软件更受偏爱。
|
* 相比商业数据库产品,免费和开源软件更受偏爱。
|
||||||
* 关系模型不能很好地支持一些特殊的查询操作
|
* 关系模型不能很好地支持一些特殊的查询操作
|
||||||
* 受挫于关系模型的限制性,渴望一种更具多动态性与表现力的数据模型【5】
|
* 受挫于关系模型的限制性,渴望一种更具多动态性与表现力的数据模型【5】
|
||||||
@ -459,7 +459,7 @@ db.observations.mapReduce(function map() {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
* 可以声明式地指定只考虑鲨鱼种类的过滤器(这是一个针对MapReduce的特定于MongoDB的扩展)。
|
* 可以声明式地指定一个只考虑鲨鱼种类的过滤器(这是MongoDB特定的MapReduce扩展)。
|
||||||
* 每个匹配查询的文档都会调用一次JavaScript函数`map`,将`this`设置为文档对象。
|
* 每个匹配查询的文档都会调用一次JavaScript函数`map`,将`this`设置为文档对象。
|
||||||
* `map`函数发出一个键(包括年份和月份的字符串,如`"2013-12"`或`"2014-1"`)和一个值(该观察记录中的动物数量)。
|
* `map`函数发出一个键(包括年份和月份的字符串,如`"2013-12"`或`"2014-1"`)和一个值(该观察记录中的动物数量)。
|
||||||
* `map`发出的键值对按键来分组。对于具有相同键(即,相同的月份和年份)的所有键值对,调用一次`reduce`函数。
|
* `map`发出的键值对按键来分组。对于具有相同键(即,相同的月份和年份)的所有键值对,调用一次`reduce`函数。
|
||||||
@ -1040,6 +1040,7 @@ Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步
|
|||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
| 上一章 | 目录 | 下一章 |
|
| 上一章 | 目录 | 下一章 |
|
||||||
| -------------------------------------- | ------------------------------- | ---------------------------- |
|
| -------------------------------------------- | ------------------------------- | ---------------------------- |
|
||||||
| [第一章:可靠、可扩展、可维护](ch1.md) | [设计数据密集型应用](README.md) | [第三章:存储与检索](ch3.md) |
|
| [第一章:可靠性、可伸缩性、可维护性](ch1.md) | [设计数据密集型应用](README.md) | [第三章:存储与检索](ch3.md) |
|
||||||
|
|
||||||
|
4
ch4.md
4
ch4.md
@ -334,7 +334,7 @@ Avro为静态类型编程语言提供了可选的代码生成功能,但是它
|
|||||||
|
|
||||||
向后兼容性显然是必要的。否则你未来的自己将无法解码你以前写的东西。
|
向后兼容性显然是必要的。否则你未来的自己将无法解码你以前写的东西。
|
||||||
|
|
||||||
一般来说,几个不同的进程同时访问数据库是很常见的。这些进程可能是几个不同的应用程序或服务,或者它们可能只是几个相同服务的实例(为了可扩展性或容错性而并行运行)。无论哪种方式,在应用程序发生变化的环境中,访问数据库的某些进程可能会运行较新的代码,有些进程可能会运行较旧的代码,例如,因为新版本当前正在部署在滚动升级,所以有些实例已经更新,而其他实例尚未更新。
|
一般来说,几个不同的进程同时访问数据库是很常见的。这些进程可能是几个不同的应用程序或服务,或者它们可能只是几个相同服务的实例(为了可伸缩性或容错性而并行运行)。无论哪种方式,在应用程序发生变化的环境中,访问数据库的某些进程可能会运行较新的代码,有些进程可能会运行较旧的代码,例如,因为新版本当前正在部署在滚动升级,所以有些实例已经更新,而其他实例尚未更新。
|
||||||
|
|
||||||
这意味着数据库中的一个值可能会被更新版本的代码写入,然后被仍旧运行的旧版本的代码读取。因此,数据库也经常需要向前兼容。
|
这意味着数据库中的一个值可能会被更新版本的代码写入,然后被仍旧运行的旧版本的代码读取。因此,数据库也经常需要向前兼容。
|
||||||
|
|
||||||
@ -480,7 +480,7 @@ RPC方案的前后向兼容性属性从它使用的编码方式中继承
|
|||||||
|
|
||||||
Actor模型是单个进程中并发的编程模型。逻辑被封装在角色中,而不是直接处理线程(以及竞争条件,锁定和死锁的相关问题)。每个角色通常代表一个客户或实体,它可能有一些本地状态(不与其他任何角色共享),它通过发送和接收异步消息与其他角色通信。消息传送不保证:在某些错误情况下,消息将丢失。由于每个角色一次只能处理一条消息,因此不需要担心线程,每个角色可以由框架独立调度。
|
Actor模型是单个进程中并发的编程模型。逻辑被封装在角色中,而不是直接处理线程(以及竞争条件,锁定和死锁的相关问题)。每个角色通常代表一个客户或实体,它可能有一些本地状态(不与其他任何角色共享),它通过发送和接收异步消息与其他角色通信。消息传送不保证:在某些错误情况下,消息将丢失。由于每个角色一次只能处理一条消息,因此不需要担心线程,每个角色可以由框架独立调度。
|
||||||
|
|
||||||
在分布式的行为者框架中,这个编程模型被用来跨越多个节点来扩展应用程序。不管发送方和接收方是在同一个节点上还是在不同的节点上,都使用相同的消息传递机制。如果它们在不同的节点上,则该消息被透明地编码成字节序列,通过网络发送,并在另一侧解码。
|
在分布式Actor框架中,此编程模型用于跨多个节点伸缩应用程序。不管发送方和接收方是在同一个节点上还是在不同的节点上,都使用相同的消息传递机制。如果它们在不同的节点上,则该消息被透明地编码成字节序列,通过网络发送,并在另一侧解码。
|
||||||
|
|
||||||
位置透明在actor模型中比在RPC中效果更好,因为actor模型已经假定消息可能会丢失,即使在单个进程中也是如此。尽管网络上的延迟可能比同一个进程中的延迟更高,但是在使用参与者模型时,本地和远程通信之间的基本不匹配是较少的。
|
位置透明在actor模型中比在RPC中效果更好,因为actor模型已经假定消息可能会丢失,即使在单个进程中也是如此。尽管网络上的延迟可能比同一个进程中的延迟更高,但是在使用参与者模型时,本地和远程通信之间的基本不匹配是较少的。
|
||||||
|
|
||||||
|
12
ch5.md
12
ch5.md
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
* 使得数据与用户在地理上接近(从而减少延迟)
|
* 使得数据与用户在地理上接近(从而减少延迟)
|
||||||
* 即使系统的一部分出现故障,系统也能继续工作(从而提高可用性)
|
* 即使系统的一部分出现故障,系统也能继续工作(从而提高可用性)
|
||||||
* 扩展可以接受读请求的机器数量(从而提高读取吞吐量)
|
* 伸缩可以接受读请求的机器数量(从而提高读取吞吐量)
|
||||||
|
|
||||||
本章将假设你的数据集非常小,每台机器都可以保存整个数据集的副本。在[第6章](ch6.md)中将放宽这个假设,讨论对单个机器来说太大的数据集的分割(分片)。在后面的章节中,我们将讨论复制数据系统中可能发生的各种故障,以及如何处理这些故障。
|
本章将假设你的数据集非常小,每台机器都可以保存整个数据集的副本。在[第6章](ch6.md)中将放宽这个假设,讨论对单个机器来说太大的数据集的分割(分片)。在后面的章节中,我们将讨论复制数据系统中可能发生的各种故障,以及如何处理这些故障。
|
||||||
|
|
||||||
@ -185,11 +185,11 @@
|
|||||||
|
|
||||||
## 复制延迟问题
|
## 复制延迟问题
|
||||||
|
|
||||||
容忍节点故障只是需要复制的一个原因。正如在[第二部分](part-ii.md)的介绍中提到的,另一个原因是可扩展性(处理比单个机器更多的请求)和延迟(让副本在地理位置上更接近用户)。
|
容忍节点故障只是需要复制的一个原因。正如在[第二部分](part-ii.md)的介绍中提到的,另一个原因是可伸缩性(处理比单个机器更多的请求)和延迟(让副本在地理位置上更接近用户)。
|
||||||
|
|
||||||
基于主库的复制要求所有写入都由单个节点处理,但只读查询可以由任何副本处理。所以对于读多写少的场景(Web上的常见模式),一个有吸引力的选择是创建很多从库,并将读请求分散到所有的从库上去。这样能减小主库的负载,并允许向最近的副本发送读请求。
|
基于主库的复制要求所有写入都由单个节点处理,但只读查询可以由任何副本处理。所以对于读多写少的场景(Web上的常见模式),一个有吸引力的选择是创建很多从库,并将读请求分散到所有的从库上去。这样能减小主库的负载,并允许向最近的副本发送读请求。
|
||||||
|
|
||||||
在这种扩展体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制——如果尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。而且越多的节点越有可能会被关闭,所以完全同步的配置是非常不可靠的。
|
在这种伸缩体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制——如果尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。而且越多的节点越有可能会被关闭,所以完全同步的配置是非常不可靠的。
|
||||||
|
|
||||||
不幸的是,当应用程序从异步从库读取时,如果从库落后,它可能会看到过时的信息。这会导致数据库中出现明显的不一致:同时对主库和从库执行相同的查询,可能得到不同的结果,因为并非所有的写入都反映在从库中。这种不一致只是一个暂时的状态——如果停止写入数据库并等待一段时间,从库最终会赶上并与主库保持一致。出于这个原因,这种效应被称为 **最终一致性(eventually consistency)**[^iii]【22,23】
|
不幸的是,当应用程序从异步从库读取时,如果从库落后,它可能会看到过时的信息。这会导致数据库中出现明显的不一致:同时对主库和从库执行相同的查询,可能得到不同的结果,因为并非所有的写入都反映在从库中。这种不一致只是一个暂时的状态——如果停止写入数据库并等待一段时间,从库最终会赶上并与主库保持一致。出于这个原因,这种效应被称为 **最终一致性(eventually consistency)**[^iii]【22,23】
|
||||||
|
|
||||||
@ -289,7 +289,7 @@
|
|||||||
|
|
||||||
如果应用程序开发人员不必担心微妙的复制问题,并可以信赖他们的数据库“做了正确的事情”,那该多好呀。这就是**事务(transaction)**存在的原因:**数据库通过事务提供强大的保证**,所以应用程序可以更加简单。
|
如果应用程序开发人员不必担心微妙的复制问题,并可以信赖他们的数据库“做了正确的事情”,那该多好呀。这就是**事务(transaction)**存在的原因:**数据库通过事务提供强大的保证**,所以应用程序可以更加简单。
|
||||||
|
|
||||||
单节点事务已经存在了很长时间。然而在走向分布式(复制和分区)数据库时,许多系统放弃了事务。声称事务在性能和可用性上的代价太高,并断言在可扩展系统中最终一致性是不可避免的。这个叙述有一些道理,但过于简单了,本书其余部分将提出更为细致的观点。第七章和第九章将回到事务的话题,并讨论一些替代机制。
|
单节点事务已经存在了很长时间。然而在走向分布式(复制和分区)数据库时,许多系统放弃了事务。声称事务在性能和可用性上的代价太高,并断言在可伸缩系统中最终一致性是不可避免的。这个叙述有一些道理,但过于简单了,本书其余部分将提出更为细致的观点。第七章和第九章将回到事务的话题,并讨论一些替代机制。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -435,7 +435,7 @@
|
|||||||
|
|
||||||
其他类型的冲突可能更为微妙,难以发现。例如,考虑一个会议室预订系统:它记录谁订了哪个时间段的哪个房间。应用需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。
|
其他类型的冲突可能更为微妙,难以发现。例如,考虑一个会议室预订系统:它记录谁订了哪个时间段的哪个房间。应用需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。
|
||||||
|
|
||||||
现在还没有一个现成的答案,但在接下来的章节中,我们将更好地了解这个问题。我们将在第7章中看到更多的冲突示例,在[第12章](ch12.md)中我们将讨论用于检测和解决复制系统中冲突的可扩展方法。
|
现在还没有一个现成的答案,但在接下来的章节中,我们将更好地了解这个问题。我们将在第7章中看到更多的冲突示例,在[第12章](ch12.md)中我们将讨论用于检测和解决复制系统中冲突的可伸缩方法。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -737,7 +737,7 @@
|
|||||||
|
|
||||||
将数据放置在距离用户较近的地方,以便用户能够更快地与其交互
|
将数据放置在距离用户较近的地方,以便用户能够更快地与其交互
|
||||||
|
|
||||||
***可扩展性***
|
***可伸缩性***
|
||||||
|
|
||||||
能够处理比单个机器更高的读取量可以通过对副本进行读取来处理
|
能够处理比单个机器更高的读取量可以通过对副本进行读取来处理
|
||||||
|
|
||||||
|
4
ch6.md
4
ch6.md
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
通常情况下,每条数据(每条记录,每行或每个文档)属于且仅属于一个分区。有很多方法可以实现这一点,本章将进行深入讨论。实际上,每个分区都是自己的小型数据库,尽管数据库可能支持同时进行多个分区的操作。
|
通常情况下,每条数据(每条记录,每行或每个文档)属于且仅属于一个分区。有很多方法可以实现这一点,本章将进行深入讨论。实际上,每个分区都是自己的小型数据库,尽管数据库可能支持同时进行多个分区的操作。
|
||||||
|
|
||||||
分区主要是为了**可扩展性**。不同的分区可以放在不共享集群中的不同节点上(参阅[第二部分](part-ii.md)关于[无共享架构](part-ii.md#无共享架构)的定义)。因此,大数据集可以分布在多个磁盘上,并且查询负载可以分布在多个处理器上。
|
分区主要是为了**可伸缩性**。不同的分区可以放在不共享集群中的不同节点上(参阅[第二部分](part-ii.md)关于[无共享架构](part-ii.md#无共享架构)的定义)。因此,大数据集可以分布在多个磁盘上,并且查询负载可以分布在多个处理器上。
|
||||||
|
|
||||||
对于在单个分区上运行的查询,每个节点可以独立执行对自己的查询,因此可以通过添加更多的节点来扩大查询吞吐量。大型,复杂的查询可能会跨越多个节点并行处理,尽管这也带来了新的困难。
|
对于在单个分区上运行的查询,每个节点可以独立执行对自己的查询,因此可以通过添加更多的节点来扩大查询吞吐量。大型,复杂的查询可能会跨越多个节点并行处理,尽管这也带来了新的困难。
|
||||||
|
|
||||||
@ -321,7 +321,7 @@
|
|||||||
|
|
||||||
最后,我们讨论了将查询路由到适当的分区的技术,从简单的分区负载平衡到复杂的并行查询执行引擎。
|
最后,我们讨论了将查询路由到适当的分区的技术,从简单的分区负载平衡到复杂的并行查询执行引擎。
|
||||||
|
|
||||||
按照设计,多数情况下每个分区是独立运行的 — 这就是分区数据库可以扩展到多台机器的原因。但是,需要写入多个分区的操作结果可能难以预料:例如,如果写入一个分区成功,但另一个分区失败,会发生什么情况?我们将在下面的章节中讨论这个问题。
|
按照设计,多数情况下每个分区是独立运行的 — 这就是分区数据库可以伸缩到多台机器的原因。但是,需要写入多个分区的操作结果可能难以预料:例如,如果写入一个分区成功,但另一个分区失败,会发生什么情况?我们将在下面的章节中讨论这个问题。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
8
ch7.md
8
ch7.md
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
2000年以后,非关系(NoSQL)数据库开始普及。它们的目标是通过提供新的数据模型选择(参见第2章),并通过默认包含复制(第5章)和分区(第6章)来改善关系现状。事务是这种运动的主要原因:这些新一代数据库中的许多数据库完全放弃了事务,或者重新定义了这个词,描述比以前理解所更弱的一套保证【4】。
|
2000年以后,非关系(NoSQL)数据库开始普及。它们的目标是通过提供新的数据模型选择(参见第2章),并通过默认包含复制(第5章)和分区(第6章)来改善关系现状。事务是这种运动的主要原因:这些新一代数据库中的许多数据库完全放弃了事务,或者重新定义了这个词,描述比以前理解所更弱的一套保证【4】。
|
||||||
|
|
||||||
随着这种新型分布式数据库的炒作,人们普遍认为事务是可扩展性的对立面,任何大型系统都必须放弃事务以保持良好的性能和高可用性【5,6】。另一方面,数据库厂商有时将事务保证作为“重要应用”和“有价值数据”的基本要求。这两种观点都是**纯粹的夸张**。
|
随着这种新型分布式数据库的炒作,人们普遍认为事务是可伸缩性的对立面,任何大型系统都必须放弃事务以保持良好的性能和高可用性【5,6】。另一方面,数据库厂商有时将事务保证作为“重要应用”和“有价值数据”的基本要求。这两种观点都是**纯粹的夸张**。
|
||||||
|
|
||||||
事实并非如此简单:与其他技术设计选择一样,事务有其优势和局限性。为了理解这些权衡,让我们了解事务所提供保证的细节——无论是在正常运行中还是在各种极端(但是现实存在)情况下。
|
事实并非如此简单:与其他技术设计选择一样,事务有其优势和局限性。为了理解这些权衡,让我们了解事务所提供保证的细节——无论是在正常运行中还是在各种极端(但是现实存在)情况下。
|
||||||
|
|
||||||
@ -650,7 +650,7 @@ VoltDB还使用存储过程进行复制:但不是将事务的写入结果从
|
|||||||
|
|
||||||
顺序执行所有事务使并发控制简单多了,但数据库的事务吞吐量被限制为单机单核的速度。只读事务可以使用快照隔离在其它地方执行,但对于写入吞吐量较高的应用,单线程事务处理器可能成为一个严重的瓶颈。
|
顺序执行所有事务使并发控制简单多了,但数据库的事务吞吐量被限制为单机单核的速度。只读事务可以使用快照隔离在其它地方执行,但对于写入吞吐量较高的应用,单线程事务处理器可能成为一个严重的瓶颈。
|
||||||
|
|
||||||
为了扩展到多个CPU核心和多个节点,可以对数据进行分区(参见[第6章](ch6.md)),在VoltDB中支持这样做。如果你可以找到一种对数据集进行分区的方法,以便每个事务只需要在单个分区中读写数据,那么每个分区就可以拥有自己独立运行的事务处理线程。在这种情况下可以为每个分区指派一个独立的CPU核,事务吞吐量就可以与CPU核数保持线性扩展【47】。
|
为了伸缩至多个CPU核心和多个节点,可以对数据进行分区(参见[第6章](ch6.md)),在VoltDB中支持这样做。如果你可以找到一种对数据集进行分区的方法,以便每个事务只需要在单个分区中读写数据,那么每个分区就可以拥有自己独立运行的事务处理线程。在这种情况下可以为每个分区指派一个独立的CPU核,事务吞吐量就可以与CPU核数保持线性伸缩【47】。
|
||||||
|
|
||||||
但是,对于需要访问多个分区的任何事务,数据库必须在触及的所有分区之间协调事务。存储过程需要跨越所有分区锁定执行,以确保整个系统的可串行性。
|
但是,对于需要访问多个分区的任何事务,数据库必须在触及的所有分区之间协调事务。存储过程需要跨越所有分区锁定执行,以确保整个系统的可串行性。
|
||||||
|
|
||||||
@ -756,7 +756,7 @@ WHERE room_id = 123 AND
|
|||||||
|
|
||||||
### 序列化快照隔离(SSI)
|
### 序列化快照隔离(SSI)
|
||||||
|
|
||||||
本章描绘了数据库中并发控制的黯淡画面。一方面,我们实现了性能不好(2PL)或者扩展性不好(串行执行)的可序列化隔离级别。另一方面,我们有性能良好的弱隔离级别,但容易出现各种竞争条件(丢失更新,写入偏差,幻读等)。序列化的隔离级别和高性能是从根本上相互矛盾的吗?
|
本章描绘了数据库中并发控制的黯淡画面。一方面,我们实现了性能不好(2PL)或者伸缩性不好(串行执行)的可序列化隔离级别。另一方面,我们有性能良好的弱隔离级别,但容易出现各种竞争条件(丢失更新,写入偏差,幻读等)。序列化的隔离级别和高性能是从根本上相互矛盾的吗?
|
||||||
|
|
||||||
也许不是:一个称为**可序列化快照隔离(SSI, serializable snapshot isolation)** 的算法是非常有前途的。它提供了完整的可序列化隔离级别,但与快照隔离相比只有只有很小的性能损失。 SSI是相当新的:它在2008年首次被描述【40】,并且是Michael Cahill的博士论文【51】的主题。
|
也许不是:一个称为**可序列化快照隔离(SSI, serializable snapshot isolation)** 的算法是非常有前途的。它提供了完整的可序列化隔离级别,但与快照隔离相比只有只有很小的性能损失。 SSI是相当新的:它在2008年首次被描述【40】,并且是Michael Cahill的博士论文【51】的主题。
|
||||||
|
|
||||||
@ -865,7 +865,7 @@ WHERE room_id = 123 AND
|
|||||||
|
|
||||||
事务读取符合某些搜索条件的对象。另一个客户端进行写入,影响搜索结果。快照隔离可以防止直接的幻像读取,但是写入偏差上下文中的幻读需要特殊处理,例如索引范围锁定。
|
事务读取符合某些搜索条件的对象。另一个客户端进行写入,影响搜索结果。快照隔离可以防止直接的幻像读取,但是写入偏差上下文中的幻读需要特殊处理,例如索引范围锁定。
|
||||||
|
|
||||||
弱隔离级别可以防止其中一些异常情况,此外让应用程序开发人员手动处理剩余那些(例如,使用显式锁定)。只有可序列化的隔离才能防范所有这些问题。我们讨论了实现可序列化事务的三种不同方法:
|
弱隔离级别可以防止其中一些异常情况,此外让应用程序开发人员手动处理剩余那些(例如,使用显式锁定)。只有可序列化的隔离才能防范所有这些问题。我们讨论了实现可序列化事务的三种不同方法:
|
||||||
|
|
||||||
***字面意义上的串行执行***
|
***字面意义上的串行执行***
|
||||||
|
|
||||||
|
4
ch8.md
4
ch8.md
@ -361,7 +361,7 @@
|
|||||||
|
|
||||||
但是当数据库分布在许多机器上,也许可能在多个数据中心中时,由于需要协调,(跨所有分区)全局单调递增的事务ID会很难生成。事务ID必须反映因果关系:如果事务B读取由事务A写入的值,则B必须具有比A更大的事务ID,否则快照就无法保持一致。在有大量的小规模、高频率的事务情景下,在分布式系统中创建事务ID成为一个难以防守的瓶颈[^vi]。
|
但是当数据库分布在许多机器上,也许可能在多个数据中心中时,由于需要协调,(跨所有分区)全局单调递增的事务ID会很难生成。事务ID必须反映因果关系:如果事务B读取由事务A写入的值,则B必须具有比A更大的事务ID,否则快照就无法保持一致。在有大量的小规模、高频率的事务情景下,在分布式系统中创建事务ID成为一个难以防守的瓶颈[^vi]。
|
||||||
|
|
||||||
[^vi]: 存在分布式序列号生成器,例如Twitter的雪花(Snowflake),其以可扩展的方式(例如,通过将ID空间的块分配给不同节点)近似单调地增加唯一ID。但是,它们通常无法保证与因果关系一致的排序,因为分配的ID块的时间范围比数据库读取和写入的时间范围要长。另请参阅“[顺序保证](#顺序保证)”。
|
[^vi]: 存在分布式序列号生成器,例如Twitter的雪花(Snowflake),其以可伸缩的方式(例如,通过将ID空间的块分配给不同节点)近似单调地增加唯一ID。但是,它们通常无法保证与因果关系一致的排序,因为分配的ID块的时间范围比数据库读取和写入的时间范围要长。另请参阅“[顺序保证](#顺序保证)”。
|
||||||
|
|
||||||
我们可以使用同步时钟的时间戳作为事务ID吗?如果我们能够获得足够好的同步性,那么这种方法将具有很合适的属性:更晚的事务会有更大的时间戳。当然,问题在于时钟精度的不确定性。
|
我们可以使用同步时钟的时间戳作为事务ID吗?如果我们能够获得足够好的同步性,那么这种方法将具有很合适的属性:更晚的事务会有更大的时间戳。当然,问题在于时钟精度的不确定性。
|
||||||
|
|
||||||
@ -652,7 +652,7 @@ while(true){
|
|||||||
|
|
||||||
如果你习惯于在理想化的数学完美(同一个操作总能确定地返回相同的结果)的单机环境中编写软件,那么转向分布式系统的凌乱的物理现实可能会有些令人震惊。相反,如果能够在单台计算机上解决一个问题,那么分布式系统工程师通常会认为这个问题是平凡的【5】,现在单个计算机确实可以做很多事情【95】。如果你可以避免打开潘多拉的盒子,把东西放在一台机器上,那么通常是值得的。
|
如果你习惯于在理想化的数学完美(同一个操作总能确定地返回相同的结果)的单机环境中编写软件,那么转向分布式系统的凌乱的物理现实可能会有些令人震惊。相反,如果能够在单台计算机上解决一个问题,那么分布式系统工程师通常会认为这个问题是平凡的【5】,现在单个计算机确实可以做很多事情【95】。如果你可以避免打开潘多拉的盒子,把东西放在一台机器上,那么通常是值得的。
|
||||||
|
|
||||||
但是,正如在[第二部分](part-ii.md)的介绍中所讨论的那样,可扩展性并不是使用分布式系统的唯一原因。容错和低延迟(通过将数据放置在距离用户较近的地方)是同等重要的目标,而这些不能用单个节点实现。
|
但是,正如在[第二部分](part-ii.md)的介绍中所讨论的那样,可伸缩性并不是使用分布式系统的唯一原因。容错和低延迟(通过将数据放置在距离用户较近的地方)是同等重要的目标,而这些不能用单个节点实现。
|
||||||
|
|
||||||
在本章中,我们也转换了几次话题,探讨了网络,时钟和进程的不可靠性是否是不可避免的自然规律。我们看到这并不是:有可能给网络提供硬实时的响应保证和有限的延迟,但是这样做非常昂贵,且导致硬件资源的利用率降低。大多数非安全关键系统会选择**便宜而不可靠**,而不是**昂贵和可靠**。
|
在本章中,我们也转换了几次话题,探讨了网络,时钟和进程的不可靠性是否是不可避免的自然规律。我们看到这并不是:有可能给网络提供硬实时的响应保证和有限的延迟,但是这样做非常昂贵,且导致硬件资源的利用率降低。大多数非安全关键系统会选择**便宜而不可靠**,而不是**昂贵和可靠**。
|
||||||
|
|
||||||
|
2
ch9.md
2
ch9.md
@ -411,7 +411,7 @@
|
|||||||
* 可以将时钟(物理时钟)时间戳附加到每个操作上【55】。这种时间戳并不连续,但是如果它具有足够高的分辨率,那也许足以提供一个操作的全序关系。这一事实应用于 *最后写入胜利* 的冲突解决方法中(参阅“[有序事件的时间戳](ch8.md#有序事件的时间戳)”)。
|
* 可以将时钟(物理时钟)时间戳附加到每个操作上【55】。这种时间戳并不连续,但是如果它具有足够高的分辨率,那也许足以提供一个操作的全序关系。这一事实应用于 *最后写入胜利* 的冲突解决方法中(参阅“[有序事件的时间戳](ch8.md#有序事件的时间戳)”)。
|
||||||
* 可以预先分配序列号区块。例如,节点 A 可能要求从序列号1到1,000区块的所有权,而节点 B 可能要求序列号1,001到2,000区块的所有权。然后每个节点可以独立分配所属区块中的序列号,并在序列号告急时请求分配一个新的区块。
|
* 可以预先分配序列号区块。例如,节点 A 可能要求从序列号1到1,000区块的所有权,而节点 B 可能要求序列号1,001到2,000区块的所有权。然后每个节点可以独立分配所属区块中的序列号,并在序列号告急时请求分配一个新的区块。
|
||||||
|
|
||||||
这三个选项都比单一主库的自增计数器表现要好,并且更具可扩展性。它们为每个操作生成一个唯一的,近似自增的序列号。然而它们都有同一个问题:生成的序列号与因果不一致。
|
这三个选项都比单一主库的自增计数器表现要好,并且更具可伸缩性。它们为每个操作生成一个唯一的,近似自增的序列号。然而它们都有同一个问题:生成的序列号与因果不一致。
|
||||||
|
|
||||||
因为这些序列号生成器不能正确地捕获跨节点的操作顺序,所以会出现因果关系的问题:
|
因为这些序列号生成器不能正确地捕获跨节点的操作顺序,所以会出现因果关系的问题:
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
本书前四章介绍了数据系统底层的基础概念,无论是在单台机器上运行的单点数据系统,还是分布在多台机器上的分布式数据系统都适用。
|
本书前四章介绍了数据系统底层的基础概念,无论是在单台机器上运行的单点数据系统,还是分布在多台机器上的分布式数据系统都适用。
|
||||||
|
|
||||||
1. [第一章](ch1.md)将介绍本书使用的术语和方法。**可靠性,可扩展性和可维护性** ,这些词汇到底意味着什么?如何实现这些目标?
|
1. [第一章](ch1.md)将介绍本书使用的术语和方法。**可靠性,可伸缩性和可维护性** ,这些词汇到底意味着什么?如何实现这些目标?
|
||||||
2. [第二章](ch2.md)将对几种不同的**数据模型和查询语言**进行比较。从程序员的角度看,这是数据库之间最明显的区别。不同的数据模型适用于不同的应用场景。
|
2. [第二章](ch2.md)将对几种不同的**数据模型和查询语言**进行比较。从程序员的角度看,这是数据库之间最明显的区别。不同的数据模型适用于不同的应用场景。
|
||||||
3. [第三章](ch3.md)将深入**存储引擎**内部,研究数据库如何在磁盘上摆放数据。不同的存储引擎针对不同的负载进行优化,选择合适的存储引擎对系统性能有巨大影响。
|
3. [第三章](ch3.md)将深入**存储引擎**内部,研究数据库如何在磁盘上摆放数据。不同的存储引擎针对不同的负载进行优化,选择合适的存储引擎对系统性能有巨大影响。
|
||||||
4. [第四章](ch4)将对几种不同的 **数据编码**进行比较。特别研究了这些格式在应用需求经常变化、模式需要随时间演变的环境中表现如何。
|
4. [第四章](ch4)将对几种不同的 **数据编码**进行比较。特别研究了这些格式在应用需求经常变化、模式需要随时间演变的环境中表现如何。
|
||||||
@ -14,7 +14,7 @@
|
|||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
|
|
||||||
1. [可靠性、可扩展性、可维护性](ch1.md)
|
1. [可靠性、可伸缩性、可维护性](ch1.md)
|
||||||
2. [数据模型与查询语言](ch2.md)
|
2. [数据模型与查询语言](ch2.md)
|
||||||
3. [存储与检索](ch3.md)
|
3. [存储与检索](ch3.md)
|
||||||
4. [编码与演化](ch4.md)
|
4. [编码与演化](ch4.md)
|
||||||
@ -26,4 +26,4 @@
|
|||||||
|
|
||||||
| 上一章 | 目录 | 下一章 |
|
| 上一章 | 目录 | 下一章 |
|
||||||
| ------------------ | ------------------------------- | -------------------------------------------- |
|
| ------------------ | ------------------------------- | -------------------------------------------- |
|
||||||
| [序言](preface.md) | [设计数据密集型应用](README.md) | [第一章:可靠性、可扩展性、可维护性](ch1.md) |
|
| [序言](preface.md) | [设计数据密集型应用](README.md) | [第一章:可靠性、可伸缩性、可维护性](ch1.md) |
|
||||||
|
10
part-ii.md
10
part-ii.md
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
你可能会出于各种各样的原因,希望将数据库分布到多台机器上:
|
你可能会出于各种各样的原因,希望将数据库分布到多台机器上:
|
||||||
|
|
||||||
***可扩展性***
|
***可伸缩性***
|
||||||
|
|
||||||
如果你的数据量、读取负载、写入负载超出单台机器的处理能力,可以将负载分散到多台计算机上。
|
如果你的数据量、读取负载、写入负载超出单台机器的处理能力,可以将负载分散到多台计算机上。
|
||||||
|
|
||||||
@ -23,9 +23,9 @@
|
|||||||
|
|
||||||
如果在世界各地都有用户,你也许会考虑在全球范围部署多个服务器,从而每个用户可以从地理上最近的数据中心获取服务,避免了等待网络数据包穿越半个世界。
|
如果在世界各地都有用户,你也许会考虑在全球范围部署多个服务器,从而每个用户可以从地理上最近的数据中心获取服务,避免了等待网络数据包穿越半个世界。
|
||||||
|
|
||||||
### 扩展至更高的载荷
|
### 伸缩至更高的载荷
|
||||||
|
|
||||||
如果你需要的只是扩展至更高的**载荷(load)**,最简单的方法就是购买更强大的机器(有时称为**垂直扩展(vertical scaling)**或**向上扩展(scale up)**)。许多处理器,内存和磁盘可以在同一个操作系统下相互连接,快速的相互连接允许任意处理器访问内存或磁盘的任意部分。在这种**共享内存架构(shared-memory architecture)**中,所有的组件都可以看作一台单独的机器。
|
如果你需要的只是伸缩至更高的**载荷(load)**,最简单的方法就是购买更强大的机器(有时称为**垂直伸缩(vertical scaling)**或**向上伸缩(scale up)**)。许多处理器,内存和磁盘可以在同一个操作系统下相互连接,快速的相互连接允许任意处理器访问内存或磁盘的任意部分。在这种**共享内存架构(shared-memory architecture)**中,所有的组件都可以看作一台单独的机器。
|
||||||
|
|
||||||
[^i]: 在大型机中,尽管任意处理器都可以访问内存的任意部分,但总有一些内存区域与一些处理器更接近(称为**非均匀内存访问(nonuniform memory access, NUMA)**【1】)。 为了有效利用这种架构特性,需要对处理进行细分,以便每个处理器主要访问临近的内存,这意味着即使表面上看起来只有一台机器在运行,**分区(partitioning)**仍然是必要的。
|
[^i]: 在大型机中,尽管任意处理器都可以访问内存的任意部分,但总有一些内存区域与一些处理器更接近(称为**非均匀内存访问(nonuniform memory access, NUMA)**【1】)。 为了有效利用这种架构特性,需要对处理进行细分,以便每个处理器主要访问临近的内存,这意味着即使表面上看起来只有一台机器在运行,**分区(partitioning)**仍然是必要的。
|
||||||
|
|
||||||
@ -33,13 +33,13 @@
|
|||||||
|
|
||||||
共享内存架构可以提供有限的容错能力,高端机器可以使用热插拔的组件(不关机更换磁盘,内存模块,甚至处理器)——但它必然囿于单个地理位置的桎梏。
|
共享内存架构可以提供有限的容错能力,高端机器可以使用热插拔的组件(不关机更换磁盘,内存模块,甚至处理器)——但它必然囿于单个地理位置的桎梏。
|
||||||
|
|
||||||
另一种方法是**共享磁盘架构(shared-disk architecture)**,它使用多台具有独立处理器和内存的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接[^ii]。这种架构用于某些数据仓库,但竞争和锁定的开销限制了共享磁盘方法的可扩展性【2】。
|
另一种方法是**共享磁盘架构(shared-disk architecture)**,它使用多台具有独立处理器和内存的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接[^ii]。这种架构用于某些数据仓库,但竞争和锁定的开销限制了共享磁盘方法的可伸缩性【2】。
|
||||||
|
|
||||||
[^ii]: 网络附属存储(Network Attached Storage, NAS),或**存储区网络(Storage Area Network, SAN)**
|
[^ii]: 网络附属存储(Network Attached Storage, NAS),或**存储区网络(Storage Area Network, SAN)**
|
||||||
|
|
||||||
#### 无共享架构
|
#### 无共享架构
|
||||||
|
|
||||||
相比之下,**无共享架构(shared-nothing architecture)**(有时称为**水平扩展(horizontal scale)** 或**向外扩展(scale out)**)已经相当普及。在这种架构中,运行数据库软件的每台机器/虚拟机都称为**节点(node)**。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。
|
相比之下,**无共享架构(shared-nothing architecture)**(有时称为**水平伸缩(horizontal scale)** 或**向外伸缩(scale out)**)已经相当普及。在这种架构中,运行数据库软件的每台机器/虚拟机都称为**节点(node)**。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。
|
||||||
|
|
||||||
无共享系统不需要使用特殊的硬件,所以你可以用任意机器——比如性价比最好的机器。你也许可以跨多个地理区域分布数据从而减少用户延迟,或者在损失一整个数据中心的情况下幸免于难。随着云端虚拟机部署的出现,即使是小公司,现在无需Google级别的运维,也可以实现异地分布式架构。
|
无共享系统不需要使用特殊的硬件,所以你可以用任意机器——比如性价比最好的机器。你也许可以跨多个地理区域分布数据从而减少用户延迟,或者在损失一整个数据中心的情况下幸免于难。随着云端虚拟机部署的出现,即使是小公司,现在无需Google级别的运维,也可以实现异地分布式架构。
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
## 章节概述
|
## 章节概述
|
||||||
|
|
||||||
我们将从[第十章](ch10.md)开始,研究例如MapReduce这样**面向批处理(batch-oriented)**的数据流系统。对于建设大规模数据系统,我们将看到,它们提供了优秀的工具和思想。[第十一章](ch11.md)将把这些思想应用到**流式数据(data streams)**中,使我们能用更低的延迟完成同样的任务。[第十二章](ch12.md)将对本书进行总结,探讨如何使用这些工具来构建可靠,可扩展和可维护的应用。
|
我们将从[第十章](ch10.md)开始,研究例如MapReduce这样**面向批处理(batch-oriented)**的数据流系统。对于建设大规模数据系统,我们将看到,它们提供了优秀的工具和思想。[第十一章](ch11.md)将把这些思想应用到**流式数据(data streams)**中,使我们能用更低的延迟完成同样的任务。[第十二章](ch12.md)将对本书进行总结,探讨如何使用这些工具来构建可靠,可伸缩和可维护的应用。
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
|
10
preface.md
10
preface.md
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
幸运的是,在技术迅速变化的背后总是存在一些持续成立的原则,无论您使用了特定工具的哪个版本。如果您理解了这些原则,就可以领会这些工具的适用场景,如何充分利用它们,以及如何避免其中的陷阱。这正是本书的初衷。
|
幸运的是,在技术迅速变化的背后总是存在一些持续成立的原则,无论您使用了特定工具的哪个版本。如果您理解了这些原则,就可以领会这些工具的适用场景,如何充分利用它们,以及如何避免其中的陷阱。这正是本书的初衷。
|
||||||
|
|
||||||
本书的目标是帮助您在飞速变化的数据处理和数据存储技术大观园中找到方向。本书并不是某个特定工具的教程,也不是一本充满枯燥理论的教科书。相反,我们将看到一些成功数据系统的样例:许多流行应用每天都要在生产中会满足可扩展性、性能、以及可靠性的要求,而这些技术构成了这些应用的基础。
|
本书的目标是帮助您在飞速变化的数据处理和数据存储技术大观园中找到方向。本书并不是某个特定工具的教程,也不是一本充满枯燥理论的教科书。相反,我们将看到一些成功数据系统的样例:许多流行应用每天都要在生产中会满足可伸缩性、性能、以及可靠性的要求,而这些技术构成了这些应用的基础。
|
||||||
|
|
||||||
我们将深入这些系统的内部,理清它们的关键算法,讨论背后的原则和它们必须做出的权衡。在这个过程中,我们将尝试寻找**思考**数据系统的有效方式 —— 不仅关于它们**如何**工作,还包括它们**为什么**以这种方式工作,以及哪些问题是我们需要问的。
|
我们将深入这些系统的内部,理清它们的关键算法,讨论背后的原则和它们必须做出的权衡。在这个过程中,我们将尝试寻找**思考**数据系统的有效方式 —— 不仅关于它们**如何**工作,还包括它们**为什么**以这种方式工作,以及哪些问题是我们需要问的。
|
||||||
|
|
||||||
@ -37,12 +37,12 @@
|
|||||||
|
|
||||||
如果以下任意一条对您为真,你会发现这本书很有价值:
|
如果以下任意一条对您为真,你会发现这本书很有价值:
|
||||||
|
|
||||||
* 您想了解如何使数据系统可扩展,例如,支持拥有数百万用户的Web或移动应用。
|
* 您想了解如何使数据系统可伸缩,例如,支持拥有数百万用户的Web或移动应用。
|
||||||
* 您需要提高应用程序的可用性(最大限度地减少停机时间),保持稳定运行。
|
* 您需要提高应用程序的可用性(最大限度地减少停机时间),保持稳定运行。
|
||||||
* 您正在寻找使系统在长期运行过程易于维护的方法,即使系统规模增长,需求与技术也发生变化。
|
* 您正在寻找使系统在长期运行过程易于维护的方法,即使系统规模增长,需求与技术也发生变化。
|
||||||
* 您对事物的运作方式有着天然的好奇心,并且希望知道一些主流网站和在线服务背后发生的事情。这本书打破了各种数据库和数据处理系统的内幕,探索这些系统设计中的智慧是非常有趣的。
|
* 您对事物的运作方式有着天然的好奇心,并且希望知道一些主流网站和在线服务背后发生的事情。这本书打破了各种数据库和数据处理系统的内幕,探索这些系统设计中的智慧是非常有趣的。
|
||||||
|
|
||||||
有时在讨论可扩展的数据系统时,人们会说:“你又不在谷歌或亚马逊,别操心可扩展性了,直接上关系型数据库”。这个陈述有一定的道理:为了不必要的扩展性而设计程序,不仅会浪费不必要的精力,并且可能会把你锁死在一个不灵活的设计中。实际上这是一种“过早优化”的形式。不过,选择合适的工具确实很重要,而不同的技术各有优缺点。我们将看到,关系数据库虽然很重要,但绝不是数据处理的终章。
|
有时在讨论可伸缩的数据系统时,人们会说:“你又不在谷歌或亚马逊,别操心可伸缩性了,直接上关系型数据库”。这个陈述有一定的道理:为了不必要的伸缩性而设计程序,不仅会浪费不必要的精力,并且可能会把你锁死在一个不灵活的设计中。实际上这是一种“过早优化”的形式。不过,选择合适的工具确实很重要,而不同的技术各有优缺点。我们将看到,关系数据库虽然很重要,但绝不是数据处理的终章。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -62,9 +62,9 @@
|
|||||||
|
|
||||||
本书分为三部分:
|
本书分为三部分:
|
||||||
|
|
||||||
1. 在[第一部分](part-i.md)中,我们会讨论设计数据密集型应用所赖的基本思想。我们从[第1章](ch1.md)开始,讨论我们实际要达到的目标:可靠性,可扩展性和可维护性;我们该如何思考这些概念;以及如何实现它们。在[第2章](ch2.md)中,我们比较了几种不同的数据模型和查询语言,看看它们如何适用于不同的场景。在[第3章](ch3.md)中将讨论存储引擎:数据库如何在磁盘上摆放数据,以便能高效地再次找到它。[第4章](ch4.md)转向数据编码(序列化),以及随时间演化的模式。
|
1. 在[第一部分](part-i.md)中,我们会讨论设计数据密集型应用所赖的基本思想。我们从[第1章](ch1.md)开始,讨论我们实际要达到的目标:可靠性,可伸缩性和可维护性;我们该如何思考这些概念;以及如何实现它们。在[第2章](ch2.md)中,我们比较了几种不同的数据模型和查询语言,看看它们如何适用于不同的场景。在[第3章](ch3.md)中将讨论存储引擎:数据库如何在磁盘上摆放数据,以便能高效地再次找到它。[第4章](ch4.md)转向数据编码(序列化),以及随时间演化的模式。
|
||||||
|
|
||||||
2. 在[第二部分](part-ii.md)中,我们从讨论存储在一台机器上的数据转向讨论分布在多台机器上的数据。这对于可扩展性通常是必需的,但带来了各种独特的挑战。我们首先讨论复制([第5章](ch5.md)),分区/分片([第6章](ch6.md))和事务([第7章](ch7.md))。然后我们将探索关于分布式系统问题的更多细节([第8章](ch8.md)),以及在分布式系统中实现一致性与共识意味着什么([第9章](ch9.md))。
|
2. 在[第二部分](part-ii.md)中,我们从讨论存储在一台机器上的数据转向讨论分布在多台机器上的数据。这对于可伸缩性通常是必需的,但带来了各种独特的挑战。我们首先讨论复制([第5章](ch5.md)),分区/分片([第6章](ch6.md))和事务([第7章](ch7.md))。然后我们将探索关于分布式系统问题的更多细节([第8章](ch8.md)),以及在分布式系统中实现一致性与共识意味着什么([第9章](ch9.md))。
|
||||||
|
|
||||||
3. 在[第三部分](part-iii.md)中,我们讨论那些从其他数据集衍生出一些数据集的系统。衍生数据经常出现在异构系统中:当没有单个数据库可以把所有事情都做的很好时,应用需要集成几种不同的数据库,缓存,索引等。在[第10章](ch10.md)中我们将从一种衍生数据的批处理方法开始,然后在此基础上建立在[第11章](ch11.md)中讨论的流处理。最后,在[第12章](ch12.md)中,我们将所有内容汇总,讨论在将来构建可靠,可伸缩和可维护的应用程序的方法。
|
3. 在[第三部分](part-iii.md)中,我们讨论那些从其他数据集衍生出一些数据集的系统。衍生数据经常出现在异构系统中:当没有单个数据库可以把所有事情都做的很好时,应用需要集成几种不同的数据库,缓存,索引等。在[第10章](ch10.md)中我们将从一种衍生数据的批处理方法开始,然后在此基础上建立在[第11章](ch11.md)中讨论的流处理。最后,在[第12章](ch12.md)中,我们将所有内容汇总,讨论在将来构建可靠,可伸缩和可维护的应用程序的方法。
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user