2
0
Fork 0
mirror of https://github.com/Vonng/ddia.git synced 2026-06-25 19:06:55 +08:00

update text spacing with pangu and some manual adjustments

This commit is contained in:
Gang Yin 2022-01-20 22:07:24 +08:00
parent 34fdbe28c4
commit 8547560e62
10 changed files with 2370 additions and 2370 deletions

28
ch10.md
View file

@ -10,7 +10,7 @@
[TOC] [TOC]
在本书的前两部分中,我们讨论了很多关于**请求**和**查询**以及相应的**响应**或**结果**。许多现有数据系统中都采用这种数据处理方式:你发送请求指令,一段时间后(我们期望)系统会给出一个结果。数据库、缓存、搜索索引、Web服务器以及其他一些系统都以这种方式工作。 在本书的前两部分中,我们讨论了很多关于 **请求****查询** 以及相应的 **响应****结果**。许多现有数据系统中都采用这种数据处理方式:你发送请求指令,一段时间后(我们期望)系统会给出一个结果。数据库、缓存、搜索索引、Web 服务器以及其他一些系统都以这种方式工作。
像这样的 **在线online** 系统,无论是浏览器请求页面还是调用远程 API 的服务,我们通常认为请求是由人类用户触发的,并且正在等待响应。他们不应该等太久,所以我们非常关注系统的响应时间(请参阅 “[描述性能](ch1.md#描述性能)”)。 像这样的 **在线online** 系统,无论是浏览器请求页面还是调用远程 API 的服务,我们通常认为请求是由人类用户触发的,并且正在等待响应。他们不应该等太久,所以我们非常关注系统的响应时间(请参阅 “[描述性能](ch1.md#描述性能)”)。
@ -87,9 +87,9 @@ cat /var/log/nginx/access.log | #1
915 /css/typography.css 915 /css/typography.css
``` ```
如果你不熟悉Unix工具上面的命令行可能看起来有点吃力但是它非常强大。它能在几秒钟内处理几GB的日志文件并且你可以根据需要轻松修改命令。例如如果要从报告中省略CSS文件可以将awk参数更改为`'$7 !~ /\.css$/ {print $7}'`,如果想统计最多的客户端IP地址,可以把awk参数改为`'{print $1}'`等等。 如果你不熟悉 Unix 工具,上面的命令行可能看起来有点吃力,但是它非常强大。它能在几秒钟内处理几 GB 的日志文件,并且你可以根据需要轻松修改命令。例如,如果要从报告中省略 CSS 文件,可以将 awk 参数更改为 `'$7 !~ /\.css$/ {print $7}'`, 如果想统计最多的客户端 IP 地址,可以把 awk 参数改为 `'{print $1}'` 等等。
我们不会在这里详细探索Unix工具但是它非常值得学习。令人惊讶的是使用awksedgrepsortuniq和xargs的组合可以在几分钟内完成许多数据分析并且它们的性能相当的好【8】。 我们不会在这里详细探索 Unix 工具,但是它非常值得学习。令人惊讶的是,使用 awk、sed、grep、sort、uniq 和 xargs 的组合可以在几分钟内完成许多数据分析并且它们的性能相当的好【8】。
#### 命令链与自定义程序 #### 命令链与自定义程序
@ -131,7 +131,7 @@ GNU CoreutilsLinux中的`sort `程序通过溢出至磁盘的方式来自
我们可以非常容易地使用前一个例子中的一系列命令来分析日志文件,这并非巧合:事实上,这实际上是 Unix 的关键设计思想之一,而且它直至今天也仍然令人讶异地重要。让我们更深入地研究一下,以便从 Unix 中借鉴一些想法【10】。 我们可以非常容易地使用前一个例子中的一系列命令来分析日志文件,这并非巧合:事实上,这实际上是 Unix 的关键设计思想之一,而且它直至今天也仍然令人讶异地重要。让我们更深入地研究一下,以便从 Unix 中借鉴一些想法【10】。
Unix管道的发明者道格·麦克罗伊Doug McIlroy在1964年首先描述了这种情况【11】“我们需要一种类似园艺胶管的方式来拼接程序 —— 当我们需要将消息从一个程序传递另一个程序时直接接上去就行。I/O应该也按照这种方式进行“。水管的类比仍然在生效通过管道连接程序的想法成为了现在被称为**Unix哲学**的一部分 —— 这一组设计原则在Unix用户与开发者之间流行起来该哲学在1978年表述如下【12,13】 Unix 管道的发明者道格・麦克罗伊Doug McIlroy在 1964 年首先描述了这种情况【11】“我们需要一种类似园艺胶管的方式来拼接程序 —— 当我们需要将消息从一个程序传递另一个程序时直接接上去就行。I/O 应该也按照这种方式进行 “。水管的类比仍然在生效,通过管道连接程序的想法成为了现在被称为 **Unix 哲学** 的一部分 —— 这一组设计原则在 Unix 用户与开发者之间流行起来,该哲学在 1978 年表述如下【12,13】
1. 让每个程序都做好一件事。要做一件新的工作,写一个新程序,而不是通过添加 “功能” 让老程序复杂化。 1. 让每个程序都做好一件事。要做一件新的工作,写一个新程序,而不是通过添加 “功能” 让老程序复杂化。
2. 期待每个程序的输出成为另一个程序的输入。不要将无关信息混入输出。避免使用严格的列数据或二进制输入格式。不要坚持交互式输入。 2. 期待每个程序的输出成为另一个程序的输入。不要将无关信息混入输出。避免使用严格的列数据或二进制输入格式。不要坚持交互式输入。
@ -152,7 +152,7 @@ Unix管道的发明者道格·麦克罗伊Doug McIlroy在1964年首先描
[^ii]: 统一接口的另一个例子是 URL 和 HTTP这是 Web 的基石。 一个 URL 标识一个网站上的一个特定的东西(资源),你可以链接到任何其他网站的任何网址。 具有网络浏览器的用户因此可以通过跟随链接在网站之间无缝跳转,即使服务器可能由完全不相关的组织维护。 这个原则现在似乎非常明显,但它却是网络取能取得今天成就的关键。 之前的系统并不是那么统一例如在公告板系统BBS时代每个系统都有自己的电话号码和波特率配置。 从一个 BBS 到另一个 BBS 的引用必须以电话号码和调制解调器设置的形式;用户将不得不挂断,拨打其他 BBS然后手动找到他们正在寻找的信息。 直接链接到另一个 BBS 内的一些内容当时是不可能的。 [^ii]: 统一接口的另一个例子是 URL 和 HTTP这是 Web 的基石。 一个 URL 标识一个网站上的一个特定的东西(资源),你可以链接到任何其他网站的任何网址。 具有网络浏览器的用户因此可以通过跟随链接在网站之间无缝跳转,即使服务器可能由完全不相关的组织维护。 这个原则现在似乎非常明显,但它却是网络取能取得今天成就的关键。 之前的系统并不是那么统一例如在公告板系统BBS时代每个系统都有自己的电话号码和波特率配置。 从一个 BBS 到另一个 BBS 的引用必须以电话号码和调制解调器设置的形式;用户将不得不挂断,拨打其他 BBS然后手动找到他们正在寻找的信息。 直接链接到另一个 BBS 内的一些内容当时是不可能的。
按照惯例许多但不是全部Unix程序将这个字节序列视为ASCII文本。我们的日志分析示例使用了这个事实`awk``sort``uniq`和`head`都将它们的输入文件视为由`\n`换行符ASCII `0x0A`)字符分隔的记录列表。 `\n`的选择是任意的 —— 可以说ASCII记录分隔符`0x1E`本来就是一个更好的选择因为它是为了这个目的而设计的【14】但是无论如何所有这些程序都使用相同的记录分隔符允许它们互操作。 按照惯例许多但不是全部Unix 程序将这个字节序列视为 ASCII 文本。我们的日志分析示例使用了这个事实:`awk`、`sort`、`uniq` 和 `head` 都将它们的输入文件视为由 `\n`换行符ASCII `0x0A`)字符分隔的记录列表。`\n` 的选择是任意的 —— 可以说ASCII 记录分隔符 `0x1E` 本来就是一个更好的选择因为它是为了这个目的而设计的【14】但是无论如何所有这些程序都使用相同的记录分隔符允许它们互操作。
每条记录(即一行输入)的解析则更加模糊。 Unix 工具通常通过空白或制表符将行分割成字段,但也使用 CSV逗号分隔管道分隔和其他编码。即使像 `xargs` 这样一个相当简单的工具也有六个命令行选项,用于指定如何解析输入。 每条记录(即一行输入)的解析则更加模糊。 Unix 工具通常通过空白或制表符将行分割成字段,但也使用 CSV逗号分隔管道分隔和其他编码。即使像 `xargs` 这样一个相当简单的工具也有六个命令行选项,用于指定如何解析输入。
@ -268,11 +268,11 @@ Reducer调用时会收到一个键和一个迭代器作为参数迭代器
因此,被链接的 MapReduce 作业并没有那么像 Unix 命令管道(它直接将一个进程的输出作为另一个进程的输入,仅用一个很小的内存缓冲区)。它更像是一系列命令,其中每个命令的输出写入临时文件,下一个命令从临时文件中读取。这种设计有利也有弊,我们将在 “[物化中间状态](#物化中间状态)” 中讨论。 因此,被链接的 MapReduce 作业并没有那么像 Unix 命令管道(它直接将一个进程的输出作为另一个进程的输入,仅用一个很小的内存缓冲区)。它更像是一系列命令,其中每个命令的输出写入临时文件,下一个命令从临时文件中读取。这种设计有利也有弊,我们将在 “[物化中间状态](#物化中间状态)” 中讨论。
只有当作业成功完成后批处理作业的输出才会被视为有效的MapReduce会丢弃失败作业的部分输出。因此工作流中的一项作业只有在先前的作业 —— 即生产其输入的作业 —— 成功完成后才能开始。为了处理这些作业之间的依赖,有很多针对Hadoop的工作流调度器被开发出来包括OozieAzkabanLuigiAirflow和Pinball 【28】。 只有当作业成功完成后批处理作业的输出才会被视为有效的MapReduce 会丢弃失败作业的部分输出)。因此,工作流中的一项作业只有在先前的作业 —— 即生产其输入的作业 —— 成功完成后才能开始。为了处理这些作业之间的依赖,有很多针对 Hadoop 的工作流调度器被开发出来,包括 Oozie、Azkaban、Luigi、Airflow 和 Pinball 【28】。
这些调度程序还具有管理功能,在维护大量批处理作业时非常有用。在构建推荐系统时,由 50 到 100 个 MapReduce 作业组成的工作流是常见的【29】。而在大型组织中许多不同的团队可能运行不同的作业来读取彼此的输出。工具支持对于管理这样复杂的数据流而言非常重要。 这些调度程序还具有管理功能,在维护大量批处理作业时非常有用。在构建推荐系统时,由 50 到 100 个 MapReduce 作业组成的工作流是常见的【29】。而在大型组织中许多不同的团队可能运行不同的作业来读取彼此的输出。工具支持对于管理这样复杂的数据流而言非常重要。
Hadoop的各种高级工具如Pig 【30】Hive 【31】Cascading 【32】Crunch 【33】和FlumeJava 【34】也能自动布线组装多个MapReduce阶段生成合适的工作流。 Hadoop 的各种高级工具(如 Pig 【30】、Hive 【31】、Cascading 【32】、Crunch 【33】和 FlumeJava 【34】也能自动布线组装多个 MapReduce 阶段,生成合适的工作流。
### Reduce侧连接与分组 ### Reduce侧连接与分组
@ -438,7 +438,7 @@ Google最初使用MapReduce是为其搜索引擎建立索引其实现为由5
- MapReduce 作业经常并行运行许多任务。如果所有 Mapper 或 Reducer 都同时写入相同的输出数据库并以批处理的预期速率工作那么该数据库很可能被轻易压垮其查询性能可能变差。这可能会导致系统其他部分的运行问题【35】。 - MapReduce 作业经常并行运行许多任务。如果所有 Mapper 或 Reducer 都同时写入相同的输出数据库并以批处理的预期速率工作那么该数据库很可能被轻易压垮其查询性能可能变差。这可能会导致系统其他部分的运行问题【35】。
- 通常情况下MapReduce 为作业输出提供了一个干净利落的 “全有或全无” 保证:如果作业成功,则结果就是每个任务恰好执行一次所产生的输出,即使某些任务失败且必须一路重试。如果整个作业失败,则不会生成输出。然而从作业内部写入外部系统,会产生外部可见的副作用,这种副作用是不能以这种方式被隐藏的。因此,你不得不去操心对其他系统可见的部分完成的作业结果,并需要理解 Hadoop 任务尝试与预测执行的复杂性。 - 通常情况下MapReduce 为作业输出提供了一个干净利落的 “全有或全无” 保证:如果作业成功,则结果就是每个任务恰好执行一次所产生的输出,即使某些任务失败且必须一路重试。如果整个作业失败,则不会生成输出。然而从作业内部写入外部系统,会产生外部可见的副作用,这种副作用是不能以这种方式被隐藏的。因此,你不得不去操心对其他系统可见的部分完成的作业结果,并需要理解 Hadoop 任务尝试与预测执行的复杂性。
更好的解决方案是在批处理作业**内**创建一个全新的数据库,并将其作为文件写入分布式文件系统中作业的输出目录,就像上节中的搜索索引一样。这些数据文件一旦写入就是不可变的,可以批量加载到处理只读查询的服务器中。不少键值存储都支持在MapReduce作业中构建数据库文件包括Voldemort 【46】Terrapin 【47】ElephantDB 【48】和HBase批量加载【49】。 更好的解决方案是在批处理作业 **内** 创建一个全新的数据库,并将其作为文件写入分布式文件系统中作业的输出目录,就像上节中的搜索索引一样。这些数据文件一旦写入就是不可变的,可以批量加载到处理只读查询的服务器中。不少键值存储都支持在 MapReduce 作业中构建数据库文件,包括 Voldemort 【46】、Terrapin 【47】、ElephantDB 【48】和 HBase 批量加载【49】。
构建这些数据库文件是 MapReduce 的一种好用法:使用 Mapper 提取出键并按该键排序已经完成了构建索引所必需的大量工作。由于这些键值存储大多都是只读的文件只能由批处理作业一次性写入然后就不可变所以数据结构非常简单。比如它们就不需要预写式日志WAL请参阅 “[让 B 树更可靠](ch3.md#让B树更可靠)”)。 构建这些数据库文件是 MapReduce 的一种好用法:使用 Mapper 提取出键并按该键排序已经完成了构建索引所必需的大量工作。由于这些键值存储大多都是只读的文件只能由批处理作业一次性写入然后就不可变所以数据结构非常简单。比如它们就不需要预写式日志WAL请参阅 “[让 B 树更可靠](ch3.md#让B树更可靠)”)。
@ -462,7 +462,7 @@ MapReduce作业的输出处理遵循同样的原理。通过将输入视为不
正如我们所看到的Hadoop 有点像 Unix 的分布式版本,其中 HDFS 是文件系统,而 MapReduce 是 Unix 进程的怪异实现(总是在 Map 阶段和 Reduce 阶段运行 `sort` 工具)。我们了解了如何在这些原语的基础上实现各种连接和分组操作。 正如我们所看到的Hadoop 有点像 Unix 的分布式版本,其中 HDFS 是文件系统,而 MapReduce 是 Unix 进程的怪异实现(总是在 Map 阶段和 Reduce 阶段运行 `sort` 工具)。我们了解了如何在这些原语的基础上实现各种连接和分组操作。
当MapReduce论文发表时【1】它从某种意义上来说 —— 并不新鲜。我们在前几节中讨论的所有处理和并行连接算法已经在十多年前所谓的**大规模并行处理MPP massively parallel processing** 数据库中实现了【3,40】。比如Gamma database machineTeradata和Tandem NonStop SQL就是这方面的先驱【52】。 MapReduce 论文发表时【1】它从某种意义上来说 —— 并不新鲜。我们在前几节中讨论的所有处理和并行连接算法已经在十多年前所谓的 **大规模并行处理MPP massively parallel processing** 数据库中实现了【3,40】。比如 Gamma database machine、Teradata 和 Tandem NonStop SQL 就是这方面的先驱【52】。
最大的区别是MPP 数据库专注于在一组机器上并行执行分析 SQL 查询,而 MapReduce 和分布式文件系统【19】的组合则更像是一个可以运行任意程序的通用操作系统。 最大的区别是MPP 数据库专注于在一组机器上并行执行分析 SQL 查询,而 MapReduce 和分布式文件系统【19】的组合则更像是一个可以运行任意程序的通用操作系统。
@ -506,7 +506,7 @@ MapReduce方式更适用于较大的作业要处理如此之多的数据并
但是这些假设有多么现实呢?在大多数集群中,机器故障确实会发生,但是它们不是很频繁 —— 可能少到绝大多数作业都不会经历机器故障。为了容错,真的值得带来这么大的额外开销吗? 但是这些假设有多么现实呢?在大多数集群中,机器故障确实会发生,但是它们不是很频繁 —— 可能少到绝大多数作业都不会经历机器故障。为了容错,真的值得带来这么大的额外开销吗?
要了解MapReduce节约使用内存和在任务的层次进行恢复的原因了解最初设计MapReduce的环境是很有帮助的。 Google有着混用的数据中心在线生产服务和离线批处理作业在同样机器上运行。每个任务都有一个通过容器强制执行的资源配给CPU核心RAM磁盘空间等。每个任务也具有优先级如果优先级较高的任务需要更多的资源则可以终止抢占同一台机器上较低优先级的任务以释放资源。优先级还决定了计算资源的定价团队必须为他们使用的资源付费而优先级更高的进程花费更多【59】。 要了解 MapReduce 节约使用内存和在任务的层次进行恢复的原因,了解最初设计 MapReduce 的环境是很有帮助的。Google 有着混用的数据中心在线生产服务和离线批处理作业在同样机器上运行。每个任务都有一个通过容器强制执行的资源配给CPU 核心、RAM、磁盘空间等。每个任务也具有优先级如果优先级较高的任务需要更多的资源则可以终止抢占同一台机器上较低优先级的任务以释放资源。优先级还决定了计算资源的定价团队必须为他们使用的资源付费而优先级更高的进程花费更多【59】。
这种架构允许非生产(低优先级)计算资源被 **过量使用overcommitted**,因为系统知道必要时它可以回收资源。与分离生产和非生产任务的系统相比,过量使用资源可以更好地利用机器并提高效率。但由于 MapReduce 作业以低优先级运行,它们随时都有被抢占的风险,因为优先级较高的进程可能需要其资源。在高优先级进程拿走所需资源后,批量作业能有效地 “捡面包屑”,利用剩下的任何计算资源。 这种架构允许非生产(低优先级)计算资源被 **过量使用overcommitted**,因为系统知道必要时它可以回收资源。与分离生产和非生产任务的系统相比,过量使用资源可以更好地利用机器并提高效率。但由于 MapReduce 作业以低优先级运行,它们随时都有被抢占的风险,因为优先级较高的进程可能需要其资源。在高优先级进程拿走所需资源后,批量作业能有效地 “捡面包屑”,利用剩下的任何计算资源。
@ -524,7 +524,7 @@ MapReduce方式更适用于较大的作业要处理如此之多的数据并
不管如何,我们在这一章花了大把时间来讨论 MapReduce因为它是一种有用的学习工具它是分布式文件系统的一种相当简单明晰的抽象。在这里**简单** 意味着我们能理解它在做什么,而不是意味着使用它很简单。恰恰相反:使用原始的 MapReduce API 来实现复杂的处理工作实际上是非常困难和费力的 —— 例如任意一种连接算法都需要你从头开始实现【37】。 不管如何,我们在这一章花了大把时间来讨论 MapReduce因为它是一种有用的学习工具它是分布式文件系统的一种相当简单明晰的抽象。在这里**简单** 意味着我们能理解它在做什么,而不是意味着使用它很简单。恰恰相反:使用原始的 MapReduce API 来实现复杂的处理工作实际上是非常困难和费力的 —— 例如任意一种连接算法都需要你从头开始实现【37】。
针对直接使用MapReduce的困难在MapReduce上有很多高级编程模型PigHiveCascadingCrunch被创造出来作为建立在MapReduce之上的抽象。如果你了解MapReduce的原理,那么它们学起来相当简单。而且它们的高级结构能显著简化许多常见批处理任务的实现。 针对直接使用 MapReduce 的困难,在 MapReduce 上有很多高级编程模型Pig、Hive、Cascading、Crunch被创造出来作为建立在 MapReduce 之上的抽象。如果你了解 MapReduce 的原理,那么它们学起来相当简单。而且它们的高级结构能显著简化许多常见批处理任务的实现。
但是MapReduce 执行模型本身也存在一些问题这些问题并没有通过增加另一个抽象层次而解决而对于某些类型的处理它表现得非常差劲。一方面MapReduce 非常稳健:你可以使用它在任务会频繁终止的多租户系统上处理几乎任意大量级的数据,并且仍然可以完成工作(虽然速度很慢)。另一方面,对于某些类型的处理而言,其他工具有时会快上几个数量级。 但是MapReduce 执行模型本身也存在一些问题这些问题并没有通过增加另一个抽象层次而解决而对于某些类型的处理它表现得非常差劲。一方面MapReduce 非常稳健:你可以使用它在任务会频繁终止的多租户系统上处理几乎任意大量级的数据,并且仍然可以完成工作(虽然速度很慢)。另一方面,对于某些类型的处理而言,其他工具有时会快上几个数量级。
@ -577,7 +577,7 @@ Tez是一个相当薄的库它依赖于YARN shuffle服务来实现节点间
完全物化中间状态至分布式文件系统的一个优点是,它具有持久性,这使得 MapReduce 中的容错相当容易:如果一个任务失败,它可以在另一台机器上重新启动,并从文件系统重新读取相同的输入。 完全物化中间状态至分布式文件系统的一个优点是,它具有持久性,这使得 MapReduce 中的容错相当容易:如果一个任务失败,它可以在另一台机器上重新启动,并从文件系统重新读取相同的输入。
SparkFlink和Tez避免将中间状态写入HDFS因此它们采取了不同的方法来容错如果一台机器发生故障并且该机器上的中间状态丢失则它会从其他仍然可用的数据重新计算在可行的情况下是先前的中间状态要么就只能是原始输入数据通常在HDFS上 Spark、Flink 和 Tez 避免将中间状态写入 HDFS因此它们采取了不同的方法来容错如果一台机器发生故障并且该机器上的中间状态丢失则它会从其他仍然可用的数据重新计算在可行的情况下是先前的中间状态要么就只能是原始输入数据通常在 HDFS 上)。
为了实现这种重新计算,框架必须跟踪一个给定的数据是如何计算的 —— 使用了哪些输入分区?应用了哪些算子? Spark 使用 **弹性分布式数据集RDDResilient Distributed Dataset** 的抽象来跟踪数据的谱系【61】而 Flink 对算子状态存档允许恢复运行在执行过程中遇到错误的算子【66】。 为了实现这种重新计算,框架必须跟踪一个给定的数据是如何计算的 —— 使用了哪些输入分区?应用了哪些算子? Spark 使用 **弹性分布式数据集RDDResilient Distributed Dataset** 的抽象来跟踪数据的谱系【61】而 Flink 对算子状态存档允许恢复运行在执行过程中遇到错误的算子【66】。
@ -601,7 +601,7 @@ SparkFlink和Tez避免将中间状态写入HDFS因此它们采取了不同
批处理上下文中的图也很有趣,其目标是在整个图上执行某种离线处理或分析。这种需求经常出现在机器学习应用(如推荐引擎)或排序系统中。例如,最着名的图形分析算法之一是 PageRank 【69】它试图根据链接到某个网页的其他网页来估计该网页的流行度。它作为配方的一部分用于确定网络搜索引擎呈现结果的顺序。 批处理上下文中的图也很有趣,其目标是在整个图上执行某种离线处理或分析。这种需求经常出现在机器学习应用(如推荐引擎)或排序系统中。例如,最着名的图形分析算法之一是 PageRank 【69】它试图根据链接到某个网页的其他网页来估计该网页的流行度。它作为配方的一部分用于确定网络搜索引擎呈现结果的顺序。
> 像SparkFlink和Tez这样的数据流引擎请参阅“[物化中间状态](#物化中间状态)”)通常将算子作为**有向无环图DAG** 的一部分安排在作业中。这与图处理不一样:在数据流引擎中,**从一个算子到另一个算子的数据流**被构造成一个图,而数据本身通常由关系型元组构成。在图处理中,数据本身具有图的形式。又一个不幸的命名混乱! > 像 Spark、Flink 和 Tez 这样的数据流引擎(请参阅 “[物化中间状态](#物化中间状态)”)通常将算子作为 **有向无环图DAG** 的一部分安排在作业中。这与图处理不一样:在数据流引擎中,**从一个算子到另一个算子的数据流** 被构造成一个图,而数据本身通常由关系型元组构成。在图处理中,数据本身具有图的形式。又一个不幸的命名混乱!
许多图算法是通过一次遍历一条边来表示的,将一个顶点与近邻的顶点连接起来,以传播一些信息,并不断重复,直到满足一些条件为止 —— 例如,直到没有更多的边要跟进,或直到一些指标收敛。我们在 [图 2-6](img/fig2-6.png) 中看到一个例子,它通过重复跟进标明地点归属关系的边,生成了数据库中北美包含的所有地点列表(这种算法被称为 **传递闭包**,即 transitive closure 许多图算法是通过一次遍历一条边来表示的,将一个顶点与近邻的顶点连接起来,以传播一些信息,并不断重复,直到满足一些条件为止 —— 例如,直到没有更多的边要跟进,或直到一些指标收敛。我们在 [图 2-6](img/fig2-6.png) 中看到一个例子,它通过重复跟进标明地点归属关系的边,生成了数据库中北美包含的所有地点列表(这种算法被称为 **传递闭包**,即 transitive closure
@ -656,7 +656,7 @@ SparkFlink和Tez避免将中间状态写入HDFS因此它们采取了不同
#### 向声明式查询语言的转变 #### 向声明式查询语言的转变
与硬写执行连接的代码相比,指定连接关系算子的优点是,框架可以分析连接输入的属性,并自动决定哪种上述连接算法最适合当前任务。 HiveSpark和Flink都有基于代价的查询优化器可以做到这一点甚至可以改变连接顺序最小化中间状态的数量【66,77,78,79】。 与硬写执行连接的代码相比,指定连接关系算子的优点是,框架可以分析连接输入的属性,并自动决定哪种上述连接算法最适合当前任务。 Hive、Spark 和 Flink 都有基于代价的查询优化器可以做到这一点甚至可以改变连接顺序最小化中间状态的数量【66,77,78,79】。
连接算法的选择可以对批处理作业的性能产生巨大影响,而无需理解和记住本章中讨论的各种连接算法。如果连接是以 **声明式declarative** 的方式指定的,那这就这是可行的:应用只是简单地说明哪些连接是必需的,查询优化器决定如何最好地执行连接。我们以前在 “[数据查询语言](ch2.md#数据查询语言)” 中见过这个想法。 连接算法的选择可以对批处理作业的性能产生巨大影响,而无需理解和记住本章中讨论的各种连接算法。如果连接是以 **声明式declarative** 的方式指定的,那这就这是可行的:应用只是简单地说明哪些连接是必需的,查询优化器决定如何最好地执行连接。我们以前在 “[数据查询语言](ch2.md#数据查询语言)” 中见过这个想法。

12
ch11.md
View file

@ -4,7 +4,7 @@
> 有效的复杂系统总是从简单的系统演化而来。 反之亦然:从零设计的复杂系统没一个能有效工作的。 > 有效的复杂系统总是从简单的系统演化而来。 反之亦然:从零设计的复杂系统没一个能有效工作的。
> >
> —— 约翰·加尔Systemantics1975 > —— 约翰加尔Systemantics1975
--------------- ---------------
@ -150,7 +150,7 @@
**图 11-3 生产者通过将消息追加写入主题分区文件来发送消息,消费者依次读取这些文件** **图 11-3 生产者通过将消息追加写入主题分区文件来发送消息,消费者依次读取这些文件**
Apache Kafka 【17,18】Amazon Kinesis Streams 【19】和Twitter的DistributedLog 【20,21】都是基于日志的消息代理。 Google Cloud Pub/Sub在架构上类似但对外暴露的是JMS风格的API而不是日志抽象【16】。尽管这些消息代理将所有消息写入磁盘但通过跨多台机器分区每秒能够实现数百万条消息的吞吐量并通过复制消息来实现容错性【22,23】。 Apache Kafka 【17,18】、Amazon Kinesis Streams 【19】和 Twitter 的 DistributedLog 【20,21】都是基于日志的消息代理。 Google Cloud Pub/Sub 在架构上类似,但对外暴露的是 JMS 风格的 API而不是日志抽象【16】。尽管这些消息代理将所有消息写入磁盘但通过跨多台机器分区每秒能够实现数百万条消息的吞吐量并通过复制消息来实现容错性【22,23】。
#### 日志与传统的消息传递相比 #### 日志与传统的消息传递相比
@ -353,7 +353,7 @@ $$
**图 11-6 应用当前状态与事件流之间的关系** **图 11-6 应用当前状态与事件流之间的关系**
如果你持久存储了变更日志,那么重现状态就非常简单。如果你认为事件日志是你的记录系统,而所有的衍生状态都从它派生而来,那么系统中的数据流动就容易理解的多。正如帕特·赫兰Pat Helland所说的【52】 如果你持久存储了变更日志,那么重现状态就非常简单。如果你认为事件日志是你的记录系统,而所有的衍生状态都从它派生而来,那么系统中的数据流动就容易理解的多。正如帕特赫兰Pat Helland所说的【52】
> 事务日志记录了数据库的所有变更。高速追加是更改日志的唯一方法。从这个角度来看,数据库的内容其实是日志中记录最新值的缓存。日志才是真相,数据库是日志子集的缓存,这一缓存子集恰好来自日志中每条记录与索引值的最新值。 > 事务日志记录了数据库的所有变更。高速追加是更改日志的唯一方法。从这个角度来看,数据库的内容其实是日志中记录最新值的缓存。日志才是真相,数据库是日志子集的缓存,这一缓存子集恰好来自日志中每条记录与索引值的最新值。
@ -393,7 +393,7 @@ $$
#### 不变性的局限性 #### 不变性的局限性
许多不使用事件溯源模型的系统也还是依赖不可变性:各种数据库在内部使用不可变的数据结构或多版本数据来支持时间点快照(请参阅“[索引和快照隔离](ch7.md#索引和快照隔离)” )。 GitMercurial和Fossil等版本控制系统也依靠不可变的数据来保存文件的版本历史记录。 许多不使用事件溯源模型的系统也还是依赖不可变性:各种数据库在内部使用不可变的数据结构或多版本数据来支持时间点快照(请参阅 “[索引和快照隔离](ch7.md#索引和快照隔离)” )。 Git、Mercurial 和 Fossil 等版本控制系统也依靠不可变的数据来保存文件的版本历史记录。
永远保持所有变更的不变历史,在多大程度上是可行的?答案取决于数据集的流失率。一些工作负载主要是添加数据,很少更新或删除;它们很容易保持不变。其他工作负载在相对较小的数据集上有较高的更新 / 删除率在这些情况下不可变的历史可能增至难以接受的巨大碎片化可能成为一个问题压缩与垃圾收集的表现对于运维的稳健性变得至关重要【60,61】。 永远保持所有变更的不变历史,在多大程度上是可行的?答案取决于数据集的流失率。一些工作负载主要是添加数据,很少更新或删除;它们很容易保持不变。其他工作负载在相对较小的数据集上有较高的更新 / 删除率在这些情况下不可变的历史可能增至难以接受的巨大碎片化可能成为一个问题压缩与垃圾收集的表现对于运维的稳健性变得至关重要【60,61】。
@ -439,7 +439,7 @@ CEP系统通常使用高层次的声明式查询语言比如SQL或者图
在这些系统中,查询和数据之间的关系与普通数据库相比是颠倒的。通常情况下,数据库会持久存储数据,并将查询视为临时的:当查询进入时,数据库搜索与查询匹配的数据,然后在查询完成时丢掉查询。 CEP 引擎反转了角色查询是长期存储的来自输入流的事件不断流过它们搜索匹配事件模式的查询【68】。 在这些系统中,查询和数据之间的关系与普通数据库相比是颠倒的。通常情况下,数据库会持久存储数据,并将查询视为临时的:当查询进入时,数据库搜索与查询匹配的数据,然后在查询完成时丢掉查询。 CEP 引擎反转了角色查询是长期存储的来自输入流的事件不断流过它们搜索匹配事件模式的查询【68】。
CEP的实现包括Esper【69】IBM InfoSphere Streams【70】ApamaTIBCO StreamBase和SQLstream。像Samza这样的分布式流处理组件支持使用SQL在流上进行声明式查询【71】。 CEP 的实现包括 Esper【69】、IBM InfoSphere Streams【70】、Apama、TIBCO StreamBase 和 SQLstream。像 Samza 这样的分布式流处理组件,支持使用 SQL 在流上进行声明式查询【71】。
#### 流分析 #### 流分析
@ -453,7 +453,7 @@ CEP的实现包括Esper【69】IBM InfoSphere Streams【70】ApamaTIBCO
流分析系统有时会使用概率算法,例如 Bloom filter我们在 “[性能优化](ch3.md#性能优化)” 中遇到过来管理成员资格HyperLogLog【72】用于基数估计以及各种百分比估计算法请参阅 “[实践中的百分位点](ch1.md#实践中的百分位点)“。概率算法产出近似的结果但比起精确算法的优点是内存使用要少得多。使用近似算法有时让人们觉得流处理系统总是有损的和不精确的但这是错误看法流处理并没有任何内在的近似性而概率算法只是一种优化【73】。 流分析系统有时会使用概率算法,例如 Bloom filter我们在 “[性能优化](ch3.md#性能优化)” 中遇到过来管理成员资格HyperLogLog【72】用于基数估计以及各种百分比估计算法请参阅 “[实践中的百分位点](ch1.md#实践中的百分位点)“。概率算法产出近似的结果但比起精确算法的优点是内存使用要少得多。使用近似算法有时让人们觉得流处理系统总是有损的和不精确的但这是错误看法流处理并没有任何内在的近似性而概率算法只是一种优化【73】。
许多开源分布式流处理框架的设计都是针对分析设计的:例如Apache StormSpark StreamingFlinkConcordSamza和Kafka Streams 【74】。托管服务包括Google Cloud Dataflow和Azure Stream Analytics。 许多开源分布式流处理框架的设计都是针对分析设计的:例如 Apache Storm、Spark Streaming、Flink、Concord、Samza 和 Kafka Streams 【74】。托管服务包括 Google Cloud Dataflow 和 Azure Stream Analytics。
#### 维护物化视图 #### 维护物化视图

30
ch12.md
View file

@ -4,7 +4,7 @@
> 如果船长的终极目标是保护船只,他应该永远待在港口。 > 如果船长的终极目标是保护船只,他应该永远待在港口。
> >
> —— 圣托马斯·阿奎那《神学大全》1265-1274 > —— 圣托马斯阿奎那《神学大全》1265-1274
--------------- ---------------
@ -14,11 +14,11 @@
对未来的看法与推测当然具有很大的主观性。所以在撰写本章时,当提及我个人的观点时会使用第一人称。你完全可以不同意这些观点并提出自己的看法,但我希望本章中的概念,至少能成为富有成效的讨论出发点,并澄清一些经常被混淆的概念。 对未来的看法与推测当然具有很大的主观性。所以在撰写本章时,当提及我个人的观点时会使用第一人称。你完全可以不同意这些观点并提出自己的看法,但我希望本章中的概念,至少能成为富有成效的讨论出发点,并澄清一些经常被混淆的概念。
[第一章](ch1.md)概述了本书的目标:探索如何创建**可靠****可伸缩**和**可维护**的应用与系统。这一主题贯穿了所有的章节:例如,我们讨论了许多有助于提高可靠性的容错算法,有助于提高可伸缩性的分区,以及有助于提高可维护性的演化与抽象机制。在本章中,我们将把所有这些想法结合在一起,并在它们的基础上展望未来。我们的目标是,发现如何设计出比现有应用更好的应用 —— 健壮,正确,可演化,且最终对人类有益。 [第一章](ch1.md) 概述了本书的目标:探索如何创建 **可靠**、**可伸缩** 和 **可维护** 的应用与系统。这一主题贯穿了所有的章节:例如,我们讨论了许多有助于提高可靠性的容错算法,有助于提高可伸缩性的分区,以及有助于提高可维护性的演化与抽象机制。在本章中,我们将把所有这些想法结合在一起,并在它们的基础上展望未来。我们的目标是,发现如何设计出比现有应用更好的应用 —— 健壮、正确、可演化、且最终对人类有益。
## 数据集成 ## 数据集成
本书中反复出现的主题是,对于任何给定的问题都会有好几种解决方案,所有这些解决方案都有不同的优缺点与利弊权衡。例如在[第三章](ch3.md)讨论存储引擎时我们看到了日志结构存储B树以及列式存储。在[第五章](ch5.md)讨论复制时,我们看到了单领导者,多领导者,和无领导者的方法。 本书中反复出现的主题是,对于任何给定的问题都会有好几种解决方案,所有这些解决方案都有不同的优缺点与利弊权衡。例如在 [第三章](ch3.md) 讨论存储引擎时我们看到了日志结构存储、B 树以及列式存储。在 [第五章](ch5.md) 讨论复制时,我们看到了单领导者、多领导者和无领导者的方法。
如果你有一个类似于 “我想存储一些数据并稍后再查询” 的问题,那么并没有一种正确的解决方案。但对于不同的具体环境,总会有不同的合适方法。软件实现通常必须选择一种特定的方法。使单条代码路径能做到稳定健壮且表现良好已经是一件非常困难的事情了 —— 尝试在单个软件中完成所有事情,几乎可以保证,实现效果会很差。 如果你有一个类似于 “我想存储一些数据并稍后再查询” 的问题,那么并没有一种正确的解决方案。但对于不同的具体环境,总会有不同的合适方法。软件实现通常必须选择一种特定的方法。使单条代码路径能做到稳定健壮且表现良好已经是一件非常困难的事情了 —— 尝试在单个软件中完成所有事情,几乎可以保证,实现效果会很差。
@ -46,7 +46,7 @@
如果你可以通过单个系统来提供所有用户输入,从而决定所有写入的排序,则通过按相同顺序处理写入,可以更容易地衍生出其他数据表示。 这是状态机复制方法的一个应用,我们在 “[全序广播](ch9.md#全序广播)” 中看到。无论你使用变更数据捕获还是事件溯源日志,都不如简单的基于全序的决策原则更重要。 如果你可以通过单个系统来提供所有用户输入,从而决定所有写入的排序,则通过按相同顺序处理写入,可以更容易地衍生出其他数据表示。 这是状态机复制方法的一个应用,我们在 “[全序广播](ch9.md#全序广播)” 中看到。无论你使用变更数据捕获还是事件溯源日志,都不如简单的基于全序的决策原则更重要。
基于事件日志来更新衍生数据的系统,通常可以做到**确定性**与**幂等性**(请参阅[幂等性](ch11.md#幂等性)”),使得从故障中恢复相当容易。 基于事件日志来更新衍生数据的系统,通常可以做到 **确定性****幂等性**(请参阅 “[幂等性](ch11.md#幂等性)”),使得从故障中恢复相当容易。
#### 衍生数据与分布式事务 #### 衍生数据与分布式事务
@ -91,9 +91,9 @@
### 批处理与流处理 ### 批处理与流处理
我会说数据集成的目标是,确保数据最终能在所有正确的地方表现出正确的形式。这样做需要消费输入,转换,连接,过滤,聚合,训练模型,评估,以及最终写出适当的输出。批处理和流处理是实现这一目标的工具。 我会说数据集成的目标是,确保数据最终能在所有正确的地方表现出正确的形式。这样做需要消费输入、转换、连接、过滤、聚合、训练模型、评估、以及最终写出适当的输出。批处理和流处理是实现这一目标的工具。
批处理和流处理的输出是衍生数据集,例如搜索索引,物化视图,向用户显示的建议,聚合指标等(请参阅“[批处理工作流的输出](ch10.md#批处理工作流的输出)”和“[流处理的应用](ch11.md#流处理的应用)”)。 批处理和流处理的输出是衍生数据集,例如搜索索引、物化视图、向用户显示的建议、聚合指标等(请参阅 “[批处理工作流的输出](ch10.md#批处理工作流的输出)” 和 “[流处理的应用](ch11.md#流处理的应用)”)。
正如我们在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中看到的,批处理和流处理有许多共同的原则,主要的根本区别在于流处理器在无限数据集上运行,而批处理输入是已知的有限大小。处理引擎的实现方式也有很多细节上的差异,但是这些区别已经开始模糊。 正如我们在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中看到的,批处理和流处理有许多共同的原则,主要的根本区别在于流处理器在无限数据集上运行,而批处理输入是已知的有限大小。处理引擎的实现方式也有很多细节上的差异,但是这些区别已经开始模糊。
@ -107,7 +107,7 @@ Spark在批处理引擎上执行流处理将流分解为**微批次microba
原则上,衍生数据系统可以同步地维护,就像关系数据库在与索引表写入操作相同的事务中同步更新次级索引一样。然而,异步是使基于事件日志的系统稳健的原因:它允许系统的一部分故障被抑制在本地。而如果任何一个参与者失败,分布式事务将中止,因此它们倾向于通过将故障传播到系统的其余部分来放大故障(请参阅 “[分布式事务的限制](ch9.md#分布式事务的限制)”)。 原则上,衍生数据系统可以同步地维护,就像关系数据库在与索引表写入操作相同的事务中同步更新次级索引一样。然而,异步是使基于事件日志的系统稳健的原因:它允许系统的一部分故障被抑制在本地。而如果任何一个参与者失败,分布式事务将中止,因此它们倾向于通过将故障传播到系统的其余部分来放大故障(请参阅 “[分布式事务的限制](ch9.md#分布式事务的限制)”)。
我们在“[分区与次级索引](ch6.md#分区与次级索引)”中看到次级索引经常跨越分区边界。具有次级索引的分区系统需要将写入发送到多个分区如果索引按关键词分区的话或将读取发送到所有分区如果索引是按文档分区的话。如果索引是异步维护的这种跨分区通信也是最可靠和最可伸缩的【8】另请参阅“[多分区数据处理](多分区数据处理)”)。 我们在 “[分区与次级索引](ch6.md#分区与次级索引)” 中看到次级索引经常跨越分区边界。具有次级索引的分区系统需要将写入发送到多个分区如果索引按关键词分区的话或将读取发送到所有分区如果索引是按文档分区的话。如果索引是异步维护的这种跨分区通信也是最可靠和最可伸缩的【8】另请参阅 “[多分区数据处理](#多分区数据处理)”)。
#### 应用演化后重新处理数据 #### 应用演化后重新处理数据
@ -160,7 +160,7 @@ Lambda架构是一种有影响力的想法它将数据系统的设计变得
Unix 和关系数据库以非常不同的哲学来处理信息管理问题。Unix 认为它的目的是为程序员提供一种相当低层次的硬件的逻辑抽象而关系数据库则希望为应用程序员提供一种高层次的抽象以隐藏磁盘上数据结构的复杂性并发性崩溃恢复等等。Unix 发展出的管道和文件只是字节序列,而数据库则发展出了 SQL 和事务。 Unix 和关系数据库以非常不同的哲学来处理信息管理问题。Unix 认为它的目的是为程序员提供一种相当低层次的硬件的逻辑抽象而关系数据库则希望为应用程序员提供一种高层次的抽象以隐藏磁盘上数据结构的复杂性并发性崩溃恢复等等。Unix 发展出的管道和文件只是字节序列,而数据库则发展出了 SQL 和事务。
哪种方法更好?当然这取决于你想要的是什么。 Unix“简单的”,因为它是对硬件资源相当薄的包装;关系数据库是“更简单”的,因为一个简短的声明性查询可以利用很多强大的基础设施(查询优化,索引,连接方法,并发控制,复制等),而不需要查询的作者理解其实现细节。 哪种方法更好?当然这取决于你想要的是什么。 Unix“简单的”,因为它是对硬件资源相当薄的包装;关系数据库是 “更简单” 的,因为一个简短的声明性查询可以利用很多强大的基础设施(查询优化、索引、连接方法、并发控制、复制等),而不需要查询的作者理解其实现细节。
这些哲学之间的矛盾已经持续了几十年Unix 和关系模型都出现在 70 年代初),仍然没有解决。例如,我将 NoSQL 运动解释为,希望将类 Unix 的低级别抽象方法应用于分布式 OLTP 数据存储的领域。 这些哲学之间的矛盾已经持续了几十年Unix 和关系模型都出现在 70 年代初),仍然没有解决。例如,我将 NoSQL 运动解释为,希望将类 Unix 的低级别抽象方法应用于分布式 OLTP 数据存储的领域。
@ -173,7 +173,7 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。Unix
* 次级索引,使你可以根据字段的值有效地搜索记录(请参阅 “[其他索引结构](ch3.md#其他索引结构)”) * 次级索引,使你可以根据字段的值有效地搜索记录(请参阅 “[其他索引结构](ch3.md#其他索引结构)”)
* 物化视图,这是一种预计算的查询结果缓存(请参阅 “[聚合:数据立方体和物化视图](ch3.md#聚合:数据立方体和物化视图)”) * 物化视图,这是一种预计算的查询结果缓存(请参阅 “[聚合:数据立方体和物化视图](ch3.md#聚合:数据立方体和物化视图)”)
* 复制日志,保持其他节点上数据的副本最新(请参阅 “[复制日志的实现](ch5.md#复制日志的实现)”) * 复制日志,保持其他节点上数据的副本最新(请参阅 “[复制日志的实现](ch5.md#复制日志的实现)”)
* 全文搜索索引,允许在文本中进行关键字搜索(请参阅“[全文搜索和模糊索引](ch3.md#全文搜4索和模糊索引)”也内置于某些关系数据库【1】 * 全文搜索索引,允许在文本中进行关键字搜索(请参阅 “[全文搜索和模糊索引](ch3.md#全文搜索和模糊索引)”也内置于某些关系数据库【1】
在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中,出现了类似的主题。我们讨论了如何构建全文搜索索引(请参阅 “[批处理工作流的输出](ch10.md#批处理工作流的输出)”),了解了如何维护物化视图(请参阅 “[维护物化视图](ch11.md#维护物化视图)”)以及如何将变更从数据库复制到衍生数据系统(请参阅 “[变更数据捕获](ch11.md#变更数据捕获)”)。 在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中,出现了类似的主题。我们讨论了如何构建全文搜索索引(请参阅 “[批处理工作流的输出](ch10.md#批处理工作流的输出)”),了解了如何维护物化视图(请参阅 “[维护物化视图](ch11.md#维护物化视图)”)以及如何将变更从数据库复制到衍生数据系统(请参阅 “[变更数据捕获](ch11.md#变更数据捕获)”)。
@ -267,13 +267,13 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。Unix
理论上,数据库可以是任意应用代码的部署环境,就如同操作系统一样。然而实践中它们对这一目标适配的很差。它们不满足现代应用开发的要求,例如依赖和软件包管理、版本控制、滚动升级、可演化性、监控、指标、对网络服务的调用以及与外部系统的集成。 理论上,数据库可以是任意应用代码的部署环境,就如同操作系统一样。然而实践中它们对这一目标适配的很差。它们不满足现代应用开发的要求,例如依赖和软件包管理、版本控制、滚动升级、可演化性、监控、指标、对网络服务的调用以及与外部系统的集成。
另一方面MesosYARNDockerKubernetes等部署和集群管理工具专为运行应用代码而设计。通过专注于做好一件事情,他们能够做得比将数据库作为其众多功能之一执行用户定义的功能要好得多。 另一方面Mesos、YARN、Docker、Kubernetes 等部署和集群管理工具专为运行应用代码而设计。通过专注于做好一件事情,他们能够做得比将数据库作为其众多功能之一执行用户定义的功能要好得多。
我认为让系统的某些部分专门用于持久数据存储并让其他部分专门运行应用程序代码是有意义的。这两者可以在保持独立的同时互动。 我认为让系统的某些部分专门用于持久数据存储并让其他部分专门运行应用程序代码是有意义的。这两者可以在保持独立的同时互动。
现在大多数 Web 应用程序都是作为无状态服务部署的其中任何用户请求都可以路由到任何应用程序服务器并且服务器在发送响应后会忘记所有请求。这种部署方式很方便因为可以随意添加或删除服务器但状态必须到某个地方通常是数据库。趋势是将无状态应用程序逻辑与状态管理数据库分开不将应用程序逻辑放入数据库中也不将持久状态置于应用程序中【36】。正如函数式编程社区喜欢开玩笑说的那样“我们相信 **教会Church****国家state** 的分离”【37】 [^i] 现在大多数 Web 应用程序都是作为无状态服务部署的其中任何用户请求都可以路由到任何应用程序服务器并且服务器在发送响应后会忘记所有请求。这种部署方式很方便因为可以随意添加或删除服务器但状态必须到某个地方通常是数据库。趋势是将无状态应用程序逻辑与状态管理数据库分开不将应用程序逻辑放入数据库中也不将持久状态置于应用程序中【36】。正如函数式编程社区喜欢开玩笑说的那样“我们相信 **教会Church****国家state** 的分离”【37】 [^i]
[^i]: 解释笑话很少会让人感觉更好,但我不想让任何人感到被遗漏。 在这里Church指代的是数学家的阿隆佐·邱奇他创立了lambda演算,这是计算的早期形式,是大多数函数式编程语言的基础。 lambda演算不具有可变状态即没有变量可以被覆盖所以可以说可变状态与Church的工作是分离的。 [^i]: 解释笑话很少会让人感觉更好,但我不想让任何人感到被遗漏。 在这里Church 指代的是数学家的阿隆佐・邱奇,他创立了 lambda 演算,这是计算的早期形式,是大多数函数式编程语言的基础。 lambda 演算不具有可变状态(即没有变量可以被覆盖),所以可以说可变状态与 Church 的工作是分离的。
在这个典型的 Web 应用模型中,数据库充当一种可以通过网络同步访问的可变共享变量。应用程序可以读取和更新变量,而数据库负责维持它的持久性,提供一些诸如并发控制和容错的功能。 在这个典型的 Web 应用模型中,数据库充当一种可以通过网络同步访问的可变共享变量。应用程序可以读取和更新变量,而数据库负责维持它的持久性,提供一些诸如并发控制和容错的功能。
@ -371,7 +371,7 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。Unix
#### 端到端的事件流 #### 端到端的事件流
最近用于开发有状态的客户端与用户界面的工具,例如如Elm语言【30】和Facebook的ReactFlux和Redux工具链,已经通过订阅表示用户输入或服务器响应的事件流来管理客户端的内部状态,其结构与事件溯源相似(请参阅“[事件溯源](ch11.md#事件溯源)”)。 最近用于开发有状态的客户端与用户界面的工具,例如如 Elm 语言【30】和 Facebook 的 React、Flux 和 Redux 工具链,已经通过订阅表示用户输入或服务器响应的事件流来管理客户端的内部状态,其结构与事件溯源相似(请参阅 “[事件溯源](ch11.md#事件溯源)”)。
将这种编程模型扩展为:允许服务器将状态变更事件推送到客户端的事件管道中,是非常自然的。因此,状态变化可以通过 **端到端end-to-end** 的写路径流动:从一个设备上的交互触发状态变更开始,经由事件日志,并穿过几个衍生数据系统与流处理器,一直到另一台设备上的用户界面,而有人正在观察用户界面上的状态变化。这些状态变化能以相当低的延迟传播 —— 比如说,在一秒内从一端到另一端。 将这种编程模型扩展为:允许服务器将状态变更事件推送到客户端的事件管道中,是非常自然的。因此,状态变化可以通过 **端到端end-to-end** 的写路径流动:从一个设备上的交互触发状态变更开始,经由事件日志,并穿过几个衍生数据系统与流处理器,一直到另一台设备上的用户界面,而有人正在观察用户界面上的状态变化。这些状态变化能以相当低的延迟传播 —— 比如说,在一秒内从一端到另一端。
@ -488,7 +488,7 @@ COMMIT;
#### 端到端原则 #### 端到端原则
抑制重复事务的这种情况只是一个更普遍的原则的一个例子,这个原则被称为**端到端原则end-to-end argument**它在1984年由SaltzerReed和Clark阐述【55】 抑制重复事务的这种情况只是一个更普遍的原则的一个例子,这个原则被称为 **端到端原则end-to-end argument**,它在 1984 年由 Saltzer、Reed 和 Clark 阐述【55】
> 只有在通信系统两端应用的知识与帮助下,所讨论的功能才能完全地正确地实现。因而将这种被质疑的功能作为通信系统本身的功能是不可能的(有时,通信系统可以提供这种功能的不完备版本,可能有助于提高性能)。 > 只有在通信系统两端应用的知识与帮助下,所讨论的功能才能完全地正确地实现。因而将这种被质疑的功能作为通信系统本身的功能是不可能的(有时,通信系统可以提供这种功能的不完备版本,可能有助于提高性能)。
> >
@ -499,7 +499,7 @@ COMMIT;
类似的原则也适用于加密【55】家庭 WiFi 网络上的密码可以防止人们窃听你的 WiFi 流量,但无法阻止互联网上其他地方攻击者的窥探;客户端与服务器之间的 TLS/SSL 可以阻挡网络攻击者,但无法阻止恶意服务器。只有端到端的加密和认证可以防止所有这些事情。 类似的原则也适用于加密【55】家庭 WiFi 网络上的密码可以防止人们窃听你的 WiFi 流量,但无法阻止互联网上其他地方攻击者的窥探;客户端与服务器之间的 TLS/SSL 可以阻挡网络攻击者,但无法阻止恶意服务器。只有端到端的加密和认证可以防止所有这些事情。
尽管低层级的功能TCP重复抑制以太网校验和WiFi加密无法单独提供所需的端到端功能但它们仍然很有用因为它们能降低较高层级出现问题的可能性。例如如果我们没有TCP来将数据包排成正确的顺序那么HTTP请求通常就会被搅烂。我们只需要记住低级别的可靠性功能本身并不足以确保端到端的正确性。 尽管低层级的功能TCP 重复抑制、以太网校验和、WiFi 加密)无法单独提供所需的端到端功能,但它们仍然很有用,因为它们能降低较高层级出现问题的可能性。例如,如果我们没有 TCP 来将数据包排成正确的顺序,那么 HTTP 请求通常就会被搅烂。我们只需要记住,低级别的可靠性功能本身并不足以确保端到端的正确性。
#### 在数据系统中应用端到端思考 #### 在数据系统中应用端到端思考
@ -705,7 +705,7 @@ ACID意义下的一致性请参阅“[一致性](ch7.md#一致性)”)基
目前,将可审计性作为顶层关注点的数据系统并不多。一些应用实现了自己的审计机制,例如将所有变更记录到单独的审计表中,但是确保审计日志与数据库状态的完整性仍然是很困难的。可以定期使用硬件安全模块对事务日志进行签名来防止篡改,但这无法保证正确的事务一开始就能进入到日志中。 目前,将可审计性作为顶层关注点的数据系统并不多。一些应用实现了自己的审计机制,例如将所有变更记录到单独的审计表中,但是确保审计日志与数据库状态的完整性仍然是很困难的。可以定期使用硬件安全模块对事务日志进行签名来防止篡改,但这无法保证正确的事务一开始就能进入到日志中。
使用密码学工具来证明系统的完整性是十分有趣的,这种方式对于宽泛的硬件与软件问题,甚至是潜在的恶意行为都很稳健有效。加密货币区块链以及诸如比特币以太坊RippleStellar的分布式账本技术已经迅速出现在这一领域【71,72,73】。 使用密码学工具来证明系统的完整性是十分有趣的,这种方式对于宽泛的硬件与软件问题,甚至是潜在的恶意行为都很稳健有效。加密货币、区块链、以及诸如比特币、以太坊、Ripple、Stellar 的分布式账本技术已经迅速出现在这一领域【71,72,73】。
我没有资格评论这些技术用于货币,或者合同商定机制的价值。但从数据系统的角度来看,它们包含了一些有趣的想法。实质上,它们是分布式数据库,具有数据模型与事务机制,而不同副本可以由互不信任的组织托管。副本不断检查其他副本的完整性,并使用共识协议对应当执行的事务达成一致。 我没有资格评论这些技术用于货币,或者合同商定机制的价值。但从数据系统的角度来看,它们包含了一些有趣的想法。实质上,它们是分布式数据库,具有数据模型与事务机制,而不同副本可以由互不信任的组织托管。副本不断检查其他副本的完整性,并使用共识协议对应当执行的事务达成一致。

10
ch9.md
View file

@ -2,7 +2,7 @@
![](img/ch9.png) ![](img/ch9.png)
> 好死还是赖活着? > 好死还是赖活着
> —— Jay Kreps, 关于 Kafka 与 Jepsen 的若干笔记 (2013) > —— Jay Kreps, 关于 Kafka 与 Jepsen 的若干笔记 (2013)
--------------- ---------------
@ -412,7 +412,7 @@ CAP定理的正式定义仅限于很狭隘的范围【30】它只考虑了一
#### 兰伯特时间戳 #### 兰伯特时间戳
尽管刚才描述的三个序列号生成器与因果不一致,但实际上有一个简单的方法来产生与因果关系一致的序列号。它被称为兰伯特时间戳,莱斯利·兰伯特Leslie Lamport于1978年提出【56】现在是分布式系统领域中被引用最多的论文之一。 尽管刚才描述的三个序列号生成器与因果不一致,但实际上有一个简单的方法来产生与因果关系一致的序列号。它被称为兰伯特时间戳,莱斯利・兰伯特Leslie Lamport于 1978 年提出【56】现在是分布式系统领域中被引用最多的论文之一。
[图 9-8](img/fig9-8.png) 说明了兰伯特时间戳的应用。每个节点都有一个唯一标识符,和一个保存自己执行操作数量的计数器。 兰伯特时间戳就是两者的简单组合:(计数器,节点 ID$(counter, node ID)$。两个节点有时可能具有相同的计数器值,但通过在时间戳中包含节点 ID每个时间戳都是唯一的。 [图 9-8](img/fig9-8.png) 说明了兰伯特时间戳的应用。每个节点都有一个唯一标识符,和一个保存自己执行操作数量的计数器。 兰伯特时间戳就是两者的简单组合:(计数器,节点 ID$(counter, node ID)$。两个节点有时可能具有相同的计数器值,但通过在时间戳中包含节点 ID每个时间戳都是唯一的。
@ -683,7 +683,7 @@ CAP定理的正式定义仅限于很狭隘的范围【30】它只考虑了一
#### XA事务 #### XA事务
*X/Open XA***扩展架构eXtended Architecture** 的缩写是跨异构技术实现两阶段提交的标准【76,77】。它于1991年推出并得到了广泛的实现许多传统关系数据库包括PostgreSQLMySQLDB2SQL Server和Oracle和消息代理包括ActiveMQHornetQMSMQ和IBM MQ 都支持XA。 *X/Open XA***扩展架构eXtended Architecture** 的缩写是跨异构技术实现两阶段提交的标准【76,77】。它于 1991 年推出并得到了广泛的实现:许多传统关系数据库(包括 PostgreSQL、MySQL、DB2、SQL Server 和 Oracle和消息代理包括 ActiveMQ、HornetQ、MSMQ 和 IBM MQ 都支持 XA。
XA 不是一个网络协议 —— 它只是一个用来与事务协调者连接的 C API。其他语言也有这种 API 的绑定;例如在 Java EE 应用的世界中XA 事务是使用 **Java 事务 APIJTA, Java Transaction API** 实现的,而许多使用 **Java 数据库连接JDBC, Java Database Connectivity** 的数据库驱动,以及许多使用 **Java 消息服务JMS** API 的消息代理都支持 **Java 事务 APIJTA** XA 不是一个网络协议 —— 它只是一个用来与事务协调者连接的 C API。其他语言也有这种 API 的绑定;例如在 Java EE 应用的世界中XA 事务是使用 **Java 事务 APIJTA, Java Transaction API** 实现的,而许多使用 **Java 数据库连接JDBC, Java Database Connectivity** 的数据库驱动,以及许多使用 **Java 消息服务JMS** API 的消息代理都支持 **Java 事务 APIJTA**
@ -828,7 +828,7 @@ XA事务解决了保持多个参与者数据系统相互一致的现实的
像 ZooKeeper 或 etcd 这样的项目通常被描述为 “分布式键值存储” 或 “协调与配置服务”。这种服务的 API 看起来非常像数据库:你可以读写给定键的值,并遍历键。所以如果它们基本上算是数据库的话,为什么它们要把工夫全花在实现一个共识算法上呢?是什么使它们区别于其他任意类型的数据库? 像 ZooKeeper 或 etcd 这样的项目通常被描述为 “分布式键值存储” 或 “协调与配置服务”。这种服务的 API 看起来非常像数据库:你可以读写给定键的值,并遍历键。所以如果它们基本上算是数据库的话,为什么它们要把工夫全花在实现一个共识算法上呢?是什么使它们区别于其他任意类型的数据库?
为了理解这一点简单了解如何使用ZooKeeper这类服务是很有帮助的。作为应用开发人员你很少需要直接使用ZooKeeper因为它实际上不适合当成通用数据库来用。更有可能的是你会通过其他项目间接依赖它例如HBaseHadoop YARNOpenStack Nova和Kafka都依赖ZooKeeper在后台运行。这些项目从它那里得到了什么? 为了理解这一点,简单了解如何使用 ZooKeeper 这类服务是很有帮助的。作为应用开发人员,你很少需要直接使用 ZooKeeper因为它实际上不适合当成通用数据库来用。更有可能的是你会通过其他项目间接依赖它例如 HBase、Hadoop YARN、OpenStack Nova 和 Kafka 都依赖 ZooKeeper 在后台运行。这些项目从它那里得到了什么?
ZooKeeper 和 etcd 被设计为容纳少量完全可以放在内存中的数据(虽然它们仍然会写入磁盘以保证持久性),所以你不会想着把所有应用数据放到这里。这些少量数据会通过容错的全序广播算法复制到所有节点上。正如前面所讨论的那样,数据库复制需要的就是全序广播:如果每条消息代表对数据库的写入,则以相同的顺序应用相同的写入操作可以使副本之间保持一致。 ZooKeeper 和 etcd 被设计为容纳少量完全可以放在内存中的数据(虽然它们仍然会写入磁盘以保证持久性),所以你不会想着把所有应用数据放到这里。这些少量数据会通过容错的全序广播算法复制到所有节点上。正如前面所讨论的那样,数据库复制需要的就是全序广播:如果每条消息代表对数据库的写入,则以相同的顺序应用相同的写入操作可以使副本之间保持一致。
@ -866,7 +866,7 @@ ZooKeeper/Chubby模型运行良好的一个例子是如果你有几个进程
#### 服务发现 #### 服务发现
ZooKeeperetcd和Consul也经常用于服务发现——也就是找出你需要连接到哪个IP地址才能到达特定的服务。在云数据中心环境中虚拟机来来往往很常见你通常不会事先知道服务的IP地址。相反你可以配置你的服务使其在启动时注册服务注册表中的网络端点然后可以由其他服务找到它们。 ZooKeeper、etcd 和 Consul 也经常用于服务发现 —— 也就是找出你需要连接到哪个 IP 地址才能到达特定的服务。在云数据中心环境中,虚拟机来来往往很常见,你通常不会事先知道服务的 IP 地址。相反,你可以配置你的服务,使其在启动时注册服务注册表中的网络端点,然后可以由其他服务找到它们。
但是,服务发现是否需要达成共识还不太清楚。 DNS 是查找服务名称的 IP 地址的传统方式,它使用多层缓存来实现良好的性能和可用性。从 DNS 读取是绝对不线性一致性的,如果 DNS 查询的结果有点陈旧通常不会有问题【109】。 DNS 的可用性和对网络中断的鲁棒性更重要。 但是,服务发现是否需要达成共识还不太清楚。 DNS 是查找服务名称的 IP 地址的传统方式,它使用多层缓存来实现良好的性能和可用性。从 DNS 读取是绝对不线性一致性的,如果 DNS 查询的结果有点陈旧通常不会有问题【109】。 DNS 的可用性和对网络中断的鲁棒性更重要。

View file

@ -10,7 +10,7 @@
[TOC] [TOC]
在本書的前兩部分中,我們討論了很多關於**請求**和**查詢**以及相應的**響應**或**結果**。許多現有資料系統中都採用這種資料處理方式:你傳送請求指令,一段時間後(我們期望)系統會給出一個結果。資料庫、快取、搜尋索引、Web伺服器以及其他一些系統都以這種方式工作。 在本書的前兩部分中,我們討論了很多關於 **請求****查詢** 以及相應的 **響應****結果**。許多現有資料系統中都採用這種資料處理方式:你傳送請求指令,一段時間後(我們期望)系統會給出一個結果。資料庫、快取、搜尋索引、Web 伺服器以及其他一些系統都以這種方式工作。
像這樣的 **線上online** 系統,無論是瀏覽器請求頁面還是呼叫遠端 API 的服務,我們通常認為請求是由人類使用者觸發的,並且正在等待響應。他們不應該等太久,所以我們非常關注系統的響應時間(請參閱 “[描述效能](ch1.md#描述效能)”)。 像這樣的 **線上online** 系統,無論是瀏覽器請求頁面還是呼叫遠端 API 的服務,我們通常認為請求是由人類使用者觸發的,並且正在等待響應。他們不應該等太久,所以我們非常關注系統的響應時間(請參閱 “[描述效能](ch1.md#描述效能)”)。
@ -87,9 +87,9 @@ cat /var/log/nginx/access.log | #1
915 /css/typography.css 915 /css/typography.css
``` ```
如果你不熟悉Unix工具上面的命令列可能看起來有點吃力但是它非常強大。它能在幾秒鐘內處理幾GB的日誌檔案並且你可以根據需要輕鬆修改命令。例如如果要從報告中省略CSS檔案可以將awk引數更改為`'$7 !~ /\.css$/ {print $7}'`,如果想統計最多的客戶端IP地址,可以把awk引數改為`'{print $1}'`等等。 如果你不熟悉 Unix 工具,上面的命令列可能看起來有點吃力,但是它非常強大。它能在幾秒鐘內處理幾 GB 的日誌檔案,並且你可以根據需要輕鬆修改命令。例如,如果要從報告中省略 CSS 檔案,可以將 awk 引數更改為 `'$7 !~ /\.css$/ {print $7}'`, 如果想統計最多的客戶端 IP 地址,可以把 awk 引數改為 `'{print $1}'` 等等。
我們不會在這裡詳細探索Unix工具但是它非常值得學習。令人驚訝的是使用awksedgrepsortuniq和xargs的組合可以在幾分鐘內完成許多資料分析並且它們的效能相當的好【8】。 我們不會在這裡詳細探索 Unix 工具,但是它非常值得學習。令人驚訝的是,使用 awk、sed、grep、sort、uniq 和 xargs 的組合可以在幾分鐘內完成許多資料分析並且它們的效能相當的好【8】。
#### 命令鏈與自定義程式 #### 命令鏈與自定義程式
@ -131,7 +131,7 @@ GNU CoreutilsLinux中的`sort `程式透過溢位至磁碟的方式來自
我們可以非常容易地使用前一個例子中的一系列命令來分析日誌檔案,這並非巧合:事實上,這實際上是 Unix 的關鍵設計思想之一,而且它直至今天也仍然令人訝異地重要。讓我們更深入地研究一下,以便從 Unix 中借鑑一些想法【10】。 我們可以非常容易地使用前一個例子中的一系列命令來分析日誌檔案,這並非巧合:事實上,這實際上是 Unix 的關鍵設計思想之一,而且它直至今天也仍然令人訝異地重要。讓我們更深入地研究一下,以便從 Unix 中借鑑一些想法【10】。
Unix管道的發明者道格·麥克羅伊Doug McIlroy在1964年首先描述了這種情況【11】“我們需要一種類似園藝膠管的方式來拼接程式 —— 當我們需要將訊息從一個程式傳遞另一個程式時直接接上去就行。I/O應該也按照這種方式進行“。水管的類比仍然在生效透過管道連線程式的想法成為了現在被稱為**Unix哲學**的一部分 —— 這一組設計原則在Unix使用者與開發者之間流行起來該哲學在1978年表述如下【12,13】 Unix 管道的發明者道格・麥克羅伊Doug McIlroy在 1964 年首先描述了這種情況【11】“我們需要一種類似園藝膠管的方式來拼接程式 —— 當我們需要將訊息從一個程式傳遞另一個程式時直接接上去就行。I/O 應該也按照這種方式進行 “。水管的類比仍然在生效,透過管道連線程式的想法成為了現在被稱為 **Unix 哲學** 的一部分 —— 這一組設計原則在 Unix 使用者與開發者之間流行起來,該哲學在 1978 年表述如下【12,13】
1. 讓每個程式都做好一件事。要做一件新的工作,寫一個新程式,而不是透過新增 “功能” 讓老程式複雜化。 1. 讓每個程式都做好一件事。要做一件新的工作,寫一個新程式,而不是透過新增 “功能” 讓老程式複雜化。
2. 期待每個程式的輸出成為另一個程式的輸入。不要將無關資訊混入輸出。避免使用嚴格的列資料或二進位制輸入格式。不要堅持互動式輸入。 2. 期待每個程式的輸出成為另一個程式的輸入。不要將無關資訊混入輸出。避免使用嚴格的列資料或二進位制輸入格式。不要堅持互動式輸入。
@ -152,7 +152,7 @@ Unix管道的發明者道格·麥克羅伊Doug McIlroy在1964年首先描
[^ii]: 統一介面的另一個例子是 URL 和 HTTP這是 Web 的基石。 一個 URL 標識一個網站上的一個特定的東西(資源),你可以連結到任何其他網站的任何網址。 具有網路瀏覽器的使用者因此可以透過跟隨連結在網站之間無縫跳轉,即使伺服器可能由完全不相關的組織維護。 這個原則現在似乎非常明顯,但它卻是網路取能取得今天成就的關鍵。 之前的系統並不是那麼統一例如在公告板系統BBS時代每個系統都有自己的電話號碼和波特率配置。 從一個 BBS 到另一個 BBS 的引用必須以電話號碼和調變解調器設定的形式;使用者將不得不掛斷,撥打其他 BBS然後手動找到他們正在尋找的資訊。 直接連結到另一個 BBS 內的一些內容當時是不可能的。 [^ii]: 統一介面的另一個例子是 URL 和 HTTP這是 Web 的基石。 一個 URL 標識一個網站上的一個特定的東西(資源),你可以連結到任何其他網站的任何網址。 具有網路瀏覽器的使用者因此可以透過跟隨連結在網站之間無縫跳轉,即使伺服器可能由完全不相關的組織維護。 這個原則現在似乎非常明顯,但它卻是網路取能取得今天成就的關鍵。 之前的系統並不是那麼統一例如在公告板系統BBS時代每個系統都有自己的電話號碼和波特率配置。 從一個 BBS 到另一個 BBS 的引用必須以電話號碼和調變解調器設定的形式;使用者將不得不掛斷,撥打其他 BBS然後手動找到他們正在尋找的資訊。 直接連結到另一個 BBS 內的一些內容當時是不可能的。
按照慣例許多但不是全部Unix程式將這個位元組序列視為ASCII文字。我們的日誌分析示例使用了這個事實`awk``sort``uniq`和`head`都將它們的輸入檔案視為由`\n`換行符ASCII `0x0A`)字元分隔的記錄列表。 `\n`的選擇是任意的 —— 可以說ASCII記錄分隔符`0x1E`本來就是一個更好的選擇因為它是為了這個目的而設計的【14】但是無論如何所有這些程式都使用相同的記錄分隔符允許它們互操作。 按照慣例許多但不是全部Unix 程式將這個位元組序列視為 ASCII 文字。我們的日誌分析示例使用了這個事實:`awk`、`sort`、`uniq` 和 `head` 都將它們的輸入檔案視為由 `\n`換行符ASCII `0x0A`)字元分隔的記錄列表。`\n` 的選擇是任意的 —— 可以說ASCII 記錄分隔符 `0x1E` 本來就是一個更好的選擇因為它是為了這個目的而設計的【14】但是無論如何所有這些程式都使用相同的記錄分隔符允許它們互操作。
每條記錄(即一行輸入)的解析則更加模糊。 Unix 工具通常透過空白或製表符將行分割成欄位,但也使用 CSV逗號分隔管道分隔和其他編碼。即使像 `xargs` 這樣一個相當簡單的工具也有六個命令列選項,用於指定如何解析輸入。 每條記錄(即一行輸入)的解析則更加模糊。 Unix 工具通常透過空白或製表符將行分割成欄位,但也使用 CSV逗號分隔管道分隔和其他編碼。即使像 `xargs` 這樣一個相當簡單的工具也有六個命令列選項,用於指定如何解析輸入。
@ -268,11 +268,11 @@ Reducer呼叫時會收到一個鍵和一個迭代器作為引數迭代器
因此,被連結的 MapReduce 作業並沒有那麼像 Unix 命令管道(它直接將一個程序的輸出作為另一個程序的輸入,僅用一個很小的記憶體緩衝區)。它更像是一系列命令,其中每個命令的輸出寫入臨時檔案,下一個命令從臨時檔案中讀取。這種設計有利也有弊,我們將在 “[物化中間狀態](#物化中間狀態)” 中討論。 因此,被連結的 MapReduce 作業並沒有那麼像 Unix 命令管道(它直接將一個程序的輸出作為另一個程序的輸入,僅用一個很小的記憶體緩衝區)。它更像是一系列命令,其中每個命令的輸出寫入臨時檔案,下一個命令從臨時檔案中讀取。這種設計有利也有弊,我們將在 “[物化中間狀態](#物化中間狀態)” 中討論。
只有當作業成功完成後批處理作業的輸出才會被視為有效的MapReduce會丟棄失敗作業的部分輸出。因此工作流中的一項作業只有在先前的作業 —— 即生產其輸入的作業 —— 成功完成後才能開始。為了處理這些作業之間的依賴,有很多針對Hadoop的工作流排程器被開發出來包括OozieAzkabanLuigiAirflow和Pinball 【28】。 只有當作業成功完成後批處理作業的輸出才會被視為有效的MapReduce 會丟棄失敗作業的部分輸出)。因此,工作流中的一項作業只有在先前的作業 —— 即生產其輸入的作業 —— 成功完成後才能開始。為了處理這些作業之間的依賴,有很多針對 Hadoop 的工作流排程器被開發出來,包括 Oozie、Azkaban、Luigi、Airflow 和 Pinball 【28】。
這些排程程式還具有管理功能,在維護大量批處理作業時非常有用。在構建推薦系統時,由 50 到 100 個 MapReduce 作業組成的工作流是常見的【29】。而在大型組織中許多不同的團隊可能執行不同的作業來讀取彼此的輸出。工具支援對於管理這樣複雜的資料流而言非常重要。 這些排程程式還具有管理功能,在維護大量批處理作業時非常有用。在構建推薦系統時,由 50 到 100 個 MapReduce 作業組成的工作流是常見的【29】。而在大型組織中許多不同的團隊可能執行不同的作業來讀取彼此的輸出。工具支援對於管理這樣複雜的資料流而言非常重要。
Hadoop的各種高階工具如Pig 【30】Hive 【31】Cascading 【32】Crunch 【33】和FlumeJava 【34】也能自動佈線組裝多個MapReduce階段生成合適的工作流。 Hadoop 的各種高階工具(如 Pig 【30】、Hive 【31】、Cascading 【32】、Crunch 【33】和 FlumeJava 【34】也能自動佈線組裝多個 MapReduce 階段,生成合適的工作流。
### Reduce側連線與分組 ### Reduce側連線與分組
@ -438,7 +438,7 @@ Google最初使用MapReduce是為其搜尋引擎建立索引其實現為由5
- MapReduce 作業經常並行執行許多工。如果所有 Mapper 或 Reducer 都同時寫入相同的輸出資料庫並以批處理的預期速率工作那麼該資料庫很可能被輕易壓垮其查詢效能可能變差。這可能會導致系統其他部分的執行問題【35】。 - MapReduce 作業經常並行執行許多工。如果所有 Mapper 或 Reducer 都同時寫入相同的輸出資料庫並以批處理的預期速率工作那麼該資料庫很可能被輕易壓垮其查詢效能可能變差。這可能會導致系統其他部分的執行問題【35】。
- 通常情況下MapReduce 為作業輸出提供了一個乾淨利落的 “全有或全無” 保證:如果作業成功,則結果就是每個任務恰好執行一次所產生的輸出,即使某些任務失敗且必須一路重試。如果整個作業失敗,則不會生成輸出。然而從作業內部寫入外部系統,會產生外部可見的副作用,這種副作用是不能以這種方式被隱藏的。因此,你不得不去操心對其他系統可見的部分完成的作業結果,並需要理解 Hadoop 任務嘗試與預測執行的複雜性。 - 通常情況下MapReduce 為作業輸出提供了一個乾淨利落的 “全有或全無” 保證:如果作業成功,則結果就是每個任務恰好執行一次所產生的輸出,即使某些任務失敗且必須一路重試。如果整個作業失敗,則不會生成輸出。然而從作業內部寫入外部系統,會產生外部可見的副作用,這種副作用是不能以這種方式被隱藏的。因此,你不得不去操心對其他系統可見的部分完成的作業結果,並需要理解 Hadoop 任務嘗試與預測執行的複雜性。
更好的解決方案是在批處理作業**內**建立一個全新的資料庫,並將其作為檔案寫入分散式檔案系統中作業的輸出目錄,就像上節中的搜尋索引一樣。這些資料檔案一旦寫入就是不可變的,可以批次載入到處理只讀查詢的伺服器中。不少鍵值儲存都支援在MapReduce作業中構建資料庫檔案包括Voldemort 【46】Terrapin 【47】ElephantDB 【48】和HBase批次載入【49】。 更好的解決方案是在批處理作業 **內** 建立一個全新的資料庫,並將其作為檔案寫入分散式檔案系統中作業的輸出目錄,就像上節中的搜尋索引一樣。這些資料檔案一旦寫入就是不可變的,可以批次載入到處理只讀查詢的伺服器中。不少鍵值儲存都支援在 MapReduce 作業中構建資料庫檔案,包括 Voldemort 【46】、Terrapin 【47】、ElephantDB 【48】和 HBase 批次載入【49】。
構建這些資料庫檔案是 MapReduce 的一種好用法:使用 Mapper 提取出鍵並按該鍵排序已經完成了構建索引所必需的大量工作。由於這些鍵值儲存大多都是隻讀的檔案只能由批處理作業一次性寫入然後就不可變所以資料結構非常簡單。比如它們就不需要預寫式日誌WAL請參閱 “[讓 B 樹更可靠](ch3.md#讓B樹更可靠)”)。 構建這些資料庫檔案是 MapReduce 的一種好用法:使用 Mapper 提取出鍵並按該鍵排序已經完成了構建索引所必需的大量工作。由於這些鍵值儲存大多都是隻讀的檔案只能由批處理作業一次性寫入然後就不可變所以資料結構非常簡單。比如它們就不需要預寫式日誌WAL請參閱 “[讓 B 樹更可靠](ch3.md#讓B樹更可靠)”)。
@ -462,7 +462,7 @@ MapReduce作業的輸出處理遵循同樣的原理。透過將輸入視為不
正如我們所看到的Hadoop 有點像 Unix 的分散式版本,其中 HDFS 是檔案系統,而 MapReduce 是 Unix 程序的怪異實現(總是在 Map 階段和 Reduce 階段執行 `sort` 工具)。我們瞭解瞭如何在這些原語的基礎上實現各種連線和分組操作。 正如我們所看到的Hadoop 有點像 Unix 的分散式版本,其中 HDFS 是檔案系統,而 MapReduce 是 Unix 程序的怪異實現(總是在 Map 階段和 Reduce 階段執行 `sort` 工具)。我們瞭解瞭如何在這些原語的基礎上實現各種連線和分組操作。
當MapReduce論文發表時【1】它從某種意義上來說 —— 並不新鮮。我們在前幾節中討論的所有處理和並行連線演算法已經在十多年前所謂的**大規模並行處理MPP massively parallel processing** 資料庫中實現了【3,40】。比如Gamma database machineTeradata和Tandem NonStop SQL就是這方面的先驅【52】。 MapReduce 論文發表時【1】它從某種意義上來說 —— 並不新鮮。我們在前幾節中討論的所有處理和並行連線演算法已經在十多年前所謂的 **大規模並行處理MPP massively parallel processing** 資料庫中實現了【3,40】。比如 Gamma database machine、Teradata 和 Tandem NonStop SQL 就是這方面的先驅【52】。
最大的區別是MPP 資料庫專注於在一組機器上並行執行分析 SQL 查詢,而 MapReduce 和分散式檔案系統【19】的組合則更像是一個可以執行任意程式的通用作業系統。 最大的區別是MPP 資料庫專注於在一組機器上並行執行分析 SQL 查詢,而 MapReduce 和分散式檔案系統【19】的組合則更像是一個可以執行任意程式的通用作業系統。
@ -506,7 +506,7 @@ MapReduce方式更適用於較大的作業要處理如此之多的資料並
但是這些假設有多麼現實呢?在大多數叢集中,機器故障確實會發生,但是它們不是很頻繁 —— 可能少到絕大多數作業都不會經歷機器故障。為了容錯,真的值得帶來這麼大的額外開銷嗎? 但是這些假設有多麼現實呢?在大多數叢集中,機器故障確實會發生,但是它們不是很頻繁 —— 可能少到絕大多數作業都不會經歷機器故障。為了容錯,真的值得帶來這麼大的額外開銷嗎?
要了解MapReduce節約使用記憶體和在任務的層次進行恢復的原因瞭解最初設計MapReduce的環境是很有幫助的。 Google有著混用的資料中心線上生產服務和離線批處理作業在同樣機器上執行。每個任務都有一個透過容器強制執行的資源配給CPU核心RAM磁碟空間等。每個任務也具有優先順序如果優先順序較高的任務需要更多的資源則可以終止搶佔同一臺機器上較低優先順序的任務以釋放資源。優先順序還決定了計算資源的定價團隊必須為他們使用的資源付費而優先順序更高的程序花費更多【59】。 要了解 MapReduce 節約使用記憶體和在任務的層次進行恢復的原因,瞭解最初設計 MapReduce 的環境是很有幫助的。Google 有著混用的資料中心線上生產服務和離線批處理作業在同樣機器上執行。每個任務都有一個透過容器強制執行的資源配給CPU 核心、RAM、磁碟空間等。每個任務也具有優先順序如果優先順序較高的任務需要更多的資源則可以終止搶佔同一臺機器上較低優先順序的任務以釋放資源。優先順序還決定了計算資源的定價團隊必須為他們使用的資源付費而優先順序更高的程序花費更多【59】。
這種架構允許非生產(低優先順序)計算資源被 **過量使用overcommitted**,因為系統知道必要時它可以回收資源。與分離生產和非生產任務的系統相比,過量使用資源可以更好地利用機器並提高效率。但由於 MapReduce 作業以低優先順序執行,它們隨時都有被搶佔的風險,因為優先順序較高的程序可能需要其資源。在高優先順序程序拿走所需資源後,批次作業能有效地 “撿麵包屑”,利用剩下的任何計算資源。 這種架構允許非生產(低優先順序)計算資源被 **過量使用overcommitted**,因為系統知道必要時它可以回收資源。與分離生產和非生產任務的系統相比,過量使用資源可以更好地利用機器並提高效率。但由於 MapReduce 作業以低優先順序執行,它們隨時都有被搶佔的風險,因為優先順序較高的程序可能需要其資源。在高優先順序程序拿走所需資源後,批次作業能有效地 “撿麵包屑”,利用剩下的任何計算資源。
@ -524,7 +524,7 @@ MapReduce方式更適用於較大的作業要處理如此之多的資料並
不管如何,我們在這一章花了大把時間來討論 MapReduce因為它是一種有用的學習工具它是分散式檔案系統的一種相當簡單明晰的抽象。在這裡**簡單** 意味著我們能理解它在做什麼,而不是意味著使用它很簡單。恰恰相反:使用原始的 MapReduce API 來實現複雜的處理工作實際上是非常困難和費力的 —— 例如任意一種連線演算法都需要你從頭開始實現【37】。 不管如何,我們在這一章花了大把時間來討論 MapReduce因為它是一種有用的學習工具它是分散式檔案系統的一種相當簡單明晰的抽象。在這裡**簡單** 意味著我們能理解它在做什麼,而不是意味著使用它很簡單。恰恰相反:使用原始的 MapReduce API 來實現複雜的處理工作實際上是非常困難和費力的 —— 例如任意一種連線演算法都需要你從頭開始實現【37】。
針對直接使用MapReduce的困難在MapReduce上有很多高階程式設計模型PigHiveCascadingCrunch被創造出來作為建立在MapReduce之上的抽象。如果你瞭解MapReduce的原理,那麼它們學起來相當簡單。而且它們的高階結構能顯著簡化許多常見批處理任務的實現。 針對直接使用 MapReduce 的困難,在 MapReduce 上有很多高階程式設計模型Pig、Hive、Cascading、Crunch被創造出來作為建立在 MapReduce 之上的抽象。如果你瞭解 MapReduce 的原理,那麼它們學起來相當簡單。而且它們的高階結構能顯著簡化許多常見批處理任務的實現。
但是MapReduce 執行模型本身也存在一些問題這些問題並沒有透過增加另一個抽象層次而解決而對於某些型別的處理它表現得非常差勁。一方面MapReduce 非常穩健:你可以使用它在任務會頻繁終止的多租戶系統上處理幾乎任意大量級的資料,並且仍然可以完成工作(雖然速度很慢)。另一方面,對於某些型別的處理而言,其他工具有時會快上幾個數量級。 但是MapReduce 執行模型本身也存在一些問題這些問題並沒有透過增加另一個抽象層次而解決而對於某些型別的處理它表現得非常差勁。一方面MapReduce 非常穩健:你可以使用它在任務會頻繁終止的多租戶系統上處理幾乎任意大量級的資料,並且仍然可以完成工作(雖然速度很慢)。另一方面,對於某些型別的處理而言,其他工具有時會快上幾個數量級。
@ -577,7 +577,7 @@ Tez是一個相當薄的庫它依賴於YARN shuffle服務來實現節點間
完全物化中間狀態至分散式檔案系統的一個優點是,它具有永續性,這使得 MapReduce 中的容錯相當容易:如果一個任務失敗,它可以在另一臺機器上重新啟動,並從檔案系統重新讀取相同的輸入。 完全物化中間狀態至分散式檔案系統的一個優點是,它具有永續性,這使得 MapReduce 中的容錯相當容易:如果一個任務失敗,它可以在另一臺機器上重新啟動,並從檔案系統重新讀取相同的輸入。
SparkFlink和Tez避免將中間狀態寫入HDFS因此它們採取了不同的方法來容錯如果一臺機器發生故障並且該機器上的中間狀態丟失則它會從其他仍然可用的資料重新計算在可行的情況下是先前的中間狀態要麼就只能是原始輸入資料通常在HDFS上 Spark、Flink 和 Tez 避免將中間狀態寫入 HDFS因此它們採取了不同的方法來容錯如果一臺機器發生故障並且該機器上的中間狀態丟失則它會從其他仍然可用的資料重新計算在可行的情況下是先前的中間狀態要麼就只能是原始輸入資料通常在 HDFS 上)。
為了實現這種重新計算,框架必須跟蹤一個給定的資料是如何計算的 —— 使用了哪些輸入分割槽?應用了哪些運算元? Spark 使用 **彈性分散式資料集RDDResilient Distributed Dataset** 的抽象來跟蹤資料的譜系【61】而 Flink 對運算元狀態存檔允許恢復執行在執行過程中遇到錯誤的運算元【66】。 為了實現這種重新計算,框架必須跟蹤一個給定的資料是如何計算的 —— 使用了哪些輸入分割槽?應用了哪些運算元? Spark 使用 **彈性分散式資料集RDDResilient Distributed Dataset** 的抽象來跟蹤資料的譜系【61】而 Flink 對運算元狀態存檔允許恢復執行在執行過程中遇到錯誤的運算元【66】。
@ -601,7 +601,7 @@ SparkFlink和Tez避免將中間狀態寫入HDFS因此它們採取了不同
批處理上下文中的圖也很有趣,其目標是在整個圖上執行某種離線處理或分析。這種需求經常出現在機器學習應用(如推薦引擎)或排序系統中。例如,最著名的圖形分析演算法之一是 PageRank 【69】它試圖根據連結到某個網頁的其他網頁來估計該網頁的流行度。它作為配方的一部分用於確定網路搜尋引擎呈現結果的順序。 批處理上下文中的圖也很有趣,其目標是在整個圖上執行某種離線處理或分析。這種需求經常出現在機器學習應用(如推薦引擎)或排序系統中。例如,最著名的圖形分析演算法之一是 PageRank 【69】它試圖根據連結到某個網頁的其他網頁來估計該網頁的流行度。它作為配方的一部分用於確定網路搜尋引擎呈現結果的順序。
> 像SparkFlink和Tez這樣的資料流引擎請參閱“[物化中間狀態](#物化中間狀態)”)通常將運算元作為**有向無環圖DAG** 的一部分安排在作業中。這與圖處理不一樣:在資料流引擎中,**從一個運算元到另一個運算元的資料流**被構造成一個圖,而資料本身通常由關係型元組構成。在圖處理中,資料本身具有圖的形式。又一個不幸的命名混亂! > 像 Spark、Flink 和 Tez 這樣的資料流引擎(請參閱 “[物化中間狀態](#物化中間狀態)”)通常將運算元作為 **有向無環圖DAG** 的一部分安排在作業中。這與圖處理不一樣:在資料流引擎中,**從一個運算元到另一個運算元的資料流** 被構造成一個圖,而資料本身通常由關係型元組構成。在圖處理中,資料本身具有圖的形式。又一個不幸的命名混亂!
許多圖演算法是透過一次遍歷一條邊來表示的,將一個頂點與近鄰的頂點連線起來,以傳播一些資訊,並不斷重複,直到滿足一些條件為止 —— 例如,直到沒有更多的邊要跟進,或直到一些指標收斂。我們在 [圖 2-6](../img/fig2-6.png) 中看到一個例子,它透過重複跟進標明地點歸屬關係的邊,生成了資料庫中北美包含的所有地點列表(這種演算法被稱為 **傳遞閉包**,即 transitive closure 許多圖演算法是透過一次遍歷一條邊來表示的,將一個頂點與近鄰的頂點連線起來,以傳播一些資訊,並不斷重複,直到滿足一些條件為止 —— 例如,直到沒有更多的邊要跟進,或直到一些指標收斂。我們在 [圖 2-6](../img/fig2-6.png) 中看到一個例子,它透過重複跟進標明地點歸屬關係的邊,生成了資料庫中北美包含的所有地點列表(這種演算法被稱為 **傳遞閉包**,即 transitive closure
@ -656,7 +656,7 @@ SparkFlink和Tez避免將中間狀態寫入HDFS因此它們採取了不同
#### 向宣告式查詢語言的轉變 #### 向宣告式查詢語言的轉變
與硬寫執行連線的程式碼相比,指定連線關係運算元的優點是,框架可以分析連線輸入的屬性,並自動決定哪種上述連線演算法最適合當前任務。 HiveSpark和Flink都有基於代價的查詢最佳化器可以做到這一點甚至可以改變連線順序最小化中間狀態的數量【66,77,78,79】。 與硬寫執行連線的程式碼相比,指定連線關係運算元的優點是,框架可以分析連線輸入的屬性,並自動決定哪種上述連線演算法最適合當前任務。 Hive、Spark 和 Flink 都有基於代價的查詢最佳化器可以做到這一點甚至可以改變連線順序最小化中間狀態的數量【66,77,78,79】。
連線演算法的選擇可以對批處理作業的效能產生巨大影響,而無需理解和記住本章中討論的各種連線演算法。如果連線是以 **宣告式declarative** 的方式指定的,那這就這是可行的:應用只是簡單地說明哪些連線是必需的,查詢最佳化器決定如何最好地執行連線。我們以前在 “[資料查詢語言](ch2.md#資料查詢語言)” 中見過這個想法。 連線演算法的選擇可以對批處理作業的效能產生巨大影響,而無需理解和記住本章中討論的各種連線演算法。如果連線是以 **宣告式declarative** 的方式指定的,那這就這是可行的:應用只是簡單地說明哪些連線是必需的,查詢最佳化器決定如何最好地執行連線。我們以前在 “[資料查詢語言](ch2.md#資料查詢語言)” 中見過這個想法。

View file

@ -4,7 +4,7 @@
> 有效的複雜系統總是從簡單的系統演化而來。 反之亦然:從零設計的複雜系統沒一個能有效工作的。 > 有效的複雜系統總是從簡單的系統演化而來。 反之亦然:從零設計的複雜系統沒一個能有效工作的。
> >
> —— 約翰·加爾Systemantics1975 > —— 約翰加爾Systemantics1975
--------------- ---------------
@ -150,7 +150,7 @@
**圖 11-3 生產者透過將訊息追加寫入主題分割槽檔案來發送訊息,消費者依次讀取這些檔案** **圖 11-3 生產者透過將訊息追加寫入主題分割槽檔案來發送訊息,消費者依次讀取這些檔案**
Apache Kafka 【17,18】Amazon Kinesis Streams 【19】和Twitter的DistributedLog 【20,21】都是基於日誌的訊息代理。 Google Cloud Pub/Sub在架構上類似但對外暴露的是JMS風格的API而不是日誌抽象【16】。儘管這些訊息代理將所有訊息寫入磁碟但透過跨多臺機器分割槽每秒能夠實現數百萬條訊息的吞吐量並透過複製訊息來實現容錯性【22,23】。 Apache Kafka 【17,18】、Amazon Kinesis Streams 【19】和 Twitter 的 DistributedLog 【20,21】都是基於日誌的訊息代理。 Google Cloud Pub/Sub 在架構上類似,但對外暴露的是 JMS 風格的 API而不是日誌抽象【16】。儘管這些訊息代理將所有訊息寫入磁碟但透過跨多臺機器分割槽每秒能夠實現數百萬條訊息的吞吐量並透過複製訊息來實現容錯性【22,23】。
#### 日誌與傳統的訊息傳遞相比 #### 日誌與傳統的訊息傳遞相比
@ -353,7 +353,7 @@ $$
**圖 11-6 應用當前狀態與事件流之間的關係** **圖 11-6 應用當前狀態與事件流之間的關係**
如果你持久儲存了變更日誌,那麼重現狀態就非常簡單。如果你認為事件日誌是你的記錄系統,而所有的衍生狀態都從它派生而來,那麼系統中的資料流動就容易理解的多。正如帕特·赫蘭Pat Helland所說的【52】 如果你持久儲存了變更日誌,那麼重現狀態就非常簡單。如果你認為事件日誌是你的記錄系統,而所有的衍生狀態都從它派生而來,那麼系統中的資料流動就容易理解的多。正如帕特赫蘭Pat Helland所說的【52】
> 事務日誌記錄了資料庫的所有變更。高速追加是更改日誌的唯一方法。從這個角度來看,資料庫的內容其實是日誌中記錄最新值的快取。日誌才是真相,資料庫是日誌子集的快取,這一快取子集恰好來自日誌中每條記錄與索引值的最新值。 > 事務日誌記錄了資料庫的所有變更。高速追加是更改日誌的唯一方法。從這個角度來看,資料庫的內容其實是日誌中記錄最新值的快取。日誌才是真相,資料庫是日誌子集的快取,這一快取子集恰好來自日誌中每條記錄與索引值的最新值。
@ -393,7 +393,7 @@ $$
#### 不變性的侷限性 #### 不變性的侷限性
許多不使用事件溯源模型的系統也還是依賴不可變性:各種資料庫在內部使用不可變的資料結構或多版本資料來支援時間點快照(請參閱“[索引和快照隔離](ch7.md#索引和快照隔離)” )。 GitMercurial和Fossil等版本控制系統也依靠不可變的資料來儲存檔案的版本歷史記錄。 許多不使用事件溯源模型的系統也還是依賴不可變性:各種資料庫在內部使用不可變的資料結構或多版本資料來支援時間點快照(請參閱 “[索引和快照隔離](ch7.md#索引和快照隔離)” )。 Git、Mercurial 和 Fossil 等版本控制系統也依靠不可變的資料來儲存檔案的版本歷史記錄。
永遠保持所有變更的不變歷史,在多大程度上是可行的?答案取決於資料集的流失率。一些工作負載主要是新增資料,很少更新或刪除;它們很容易保持不變。其他工作負載在相對較小的資料集上有較高的更新 / 刪除率在這些情況下不可變的歷史可能增至難以接受的巨大碎片化可能成為一個問題壓縮與垃圾收集的表現對於運維的穩健性變得至關重要【60,61】。 永遠保持所有變更的不變歷史,在多大程度上是可行的?答案取決於資料集的流失率。一些工作負載主要是新增資料,很少更新或刪除;它們很容易保持不變。其他工作負載在相對較小的資料集上有較高的更新 / 刪除率在這些情況下不可變的歷史可能增至難以接受的巨大碎片化可能成為一個問題壓縮與垃圾收集的表現對於運維的穩健性變得至關重要【60,61】。
@ -439,7 +439,7 @@ CEP系統通常使用高層次的宣告式查詢語言比如SQL或者圖
在這些系統中,查詢和資料之間的關係與普通資料庫相比是顛倒的。通常情況下,資料庫會持久儲存資料,並將查詢視為臨時的:當查詢進入時,資料庫搜尋與查詢匹配的資料,然後在查詢完成時丟掉查詢。 CEP 引擎反轉了角色查詢是長期儲存的來自輸入流的事件不斷流過它們搜尋匹配事件模式的查詢【68】。 在這些系統中,查詢和資料之間的關係與普通資料庫相比是顛倒的。通常情況下,資料庫會持久儲存資料,並將查詢視為臨時的:當查詢進入時,資料庫搜尋與查詢匹配的資料,然後在查詢完成時丟掉查詢。 CEP 引擎反轉了角色查詢是長期儲存的來自輸入流的事件不斷流過它們搜尋匹配事件模式的查詢【68】。
CEP的實現包括Esper【69】IBM InfoSphere Streams【70】ApamaTIBCO StreamBase和SQLstream。像Samza這樣的分散式流處理元件支援使用SQL在流上進行宣告式查詢【71】。 CEP 的實現包括 Esper【69】、IBM InfoSphere Streams【70】、Apama、TIBCO StreamBase 和 SQLstream。像 Samza 這樣的分散式流處理元件,支援使用 SQL 在流上進行宣告式查詢【71】。
#### 流分析 #### 流分析
@ -453,7 +453,7 @@ CEP的實現包括Esper【69】IBM InfoSphere Streams【70】ApamaTIBCO
流分析系統有時會使用概率演算法,例如 Bloom filter我們在 “[效能最佳化](ch3.md#效能最佳化)” 中遇到過來管理成員資格HyperLogLog【72】用於基數估計以及各種百分比估計演算法請參閱 “[實踐中的百分位點](ch1.md#實踐中的百分位點)“。概率演算法產出近似的結果但比起精確演算法的優點是記憶體使用要少得多。使用近似演算法有時讓人們覺得流處理系統總是有損的和不精確的但這是錯誤看法流處理並沒有任何內在的近似性而概率演算法只是一種最佳化【73】。 流分析系統有時會使用概率演算法,例如 Bloom filter我們在 “[效能最佳化](ch3.md#效能最佳化)” 中遇到過來管理成員資格HyperLogLog【72】用於基數估計以及各種百分比估計演算法請參閱 “[實踐中的百分位點](ch1.md#實踐中的百分位點)“。概率演算法產出近似的結果但比起精確演算法的優點是記憶體使用要少得多。使用近似演算法有時讓人們覺得流處理系統總是有損的和不精確的但這是錯誤看法流處理並沒有任何內在的近似性而概率演算法只是一種最佳化【73】。
許多開源分散式流處理框架的設計都是針對分析設計的:例如Apache StormSpark StreamingFlinkConcordSamza和Kafka Streams 【74】。託管服務包括Google Cloud Dataflow和Azure Stream Analytics。 許多開源分散式流處理框架的設計都是針對分析設計的:例如 Apache Storm、Spark Streaming、Flink、Concord、Samza 和 Kafka Streams 【74】。託管服務包括 Google Cloud Dataflow 和 Azure Stream Analytics。
#### 維護物化檢視 #### 維護物化檢視

View file

@ -4,7 +4,7 @@
> 如果船長的終極目標是保護船隻,他應該永遠待在港口。 > 如果船長的終極目標是保護船隻,他應該永遠待在港口。
> >
> —— 聖托馬斯·阿奎那《神學大全》1265-1274 > —— 聖托馬斯阿奎那《神學大全》1265-1274
--------------- ---------------
@ -14,11 +14,11 @@
對未來的看法與推測當然具有很大的主觀性。所以在撰寫本章時,當提及我個人的觀點時會使用第一人稱。你完全可以不同意這些觀點並提出自己的看法,但我希望本章中的概念,至少能成為富有成效的討論出發點,並澄清一些經常被混淆的概念。 對未來的看法與推測當然具有很大的主觀性。所以在撰寫本章時,當提及我個人的觀點時會使用第一人稱。你完全可以不同意這些觀點並提出自己的看法,但我希望本章中的概念,至少能成為富有成效的討論出發點,並澄清一些經常被混淆的概念。
[第一章](ch1.md)概述了本書的目標:探索如何建立**可靠****可伸縮**和**可維護**的應用與系統。這一主題貫穿了所有的章節:例如,我們討論了許多有助於提高可靠性的容錯演算法,有助於提高可伸縮性的分割槽,以及有助於提高可維護性的演化與抽象機制。在本章中,我們將把所有這些想法結合在一起,並在它們的基礎上展望未來。我們的目標是,發現如何設計出比現有應用更好的應用 —— 健壯,正確,可演化,且最終對人類有益。 [第一章](ch1.md) 概述了本書的目標:探索如何建立 **可靠**、**可伸縮** 和 **可維護** 的應用與系統。這一主題貫穿了所有的章節:例如,我們討論了許多有助於提高可靠性的容錯演算法,有助於提高可伸縮性的分割槽,以及有助於提高可維護性的演化與抽象機制。在本章中,我們將把所有這些想法結合在一起,並在它們的基礎上展望未來。我們的目標是,發現如何設計出比現有應用更好的應用 —— 健壯、正確、可演化、且最終對人類有益。
## 資料整合 ## 資料整合
本書中反覆出現的主題是,對於任何給定的問題都會有好幾種解決方案,所有這些解決方案都有不同的優缺點與利弊權衡。例如在[第三章](ch3.md)討論儲存引擎時我們看到了日誌結構儲存B樹以及列式儲存。在[第五章](ch5.md)討論複製時,我們看到了單領導者,多領導者,和無領導者的方法。 本書中反覆出現的主題是,對於任何給定的問題都會有好幾種解決方案,所有這些解決方案都有不同的優缺點與利弊權衡。例如在 [第三章](ch3.md) 討論儲存引擎時我們看到了日誌結構儲存、B 樹以及列式儲存。在 [第五章](ch5.md) 討論複製時,我們看到了單領導者、多領導者和無領導者的方法。
如果你有一個類似於 “我想儲存一些資料並稍後再查詢” 的問題,那麼並沒有一種正確的解決方案。但對於不同的具體環境,總會有不同的合適方法。軟體實現通常必須選擇一種特定的方法。使單條程式碼路徑能做到穩定健壯且表現良好已經是一件非常困難的事情了 —— 嘗試在單個軟體中完成所有事情,幾乎可以保證,實現效果會很差。 如果你有一個類似於 “我想儲存一些資料並稍後再查詢” 的問題,那麼並沒有一種正確的解決方案。但對於不同的具體環境,總會有不同的合適方法。軟體實現通常必須選擇一種特定的方法。使單條程式碼路徑能做到穩定健壯且表現良好已經是一件非常困難的事情了 —— 嘗試在單個軟體中完成所有事情,幾乎可以保證,實現效果會很差。
@ -46,7 +46,7 @@
如果你可以透過單個系統來提供所有使用者輸入,從而決定所有寫入的排序,則透過按相同順序處理寫入,可以更容易地衍生出其他資料表示。 這是狀態機複製方法的一個應用,我們在 “[全序廣播](ch9.md#全序廣播)” 中看到。無論你使用變更資料捕獲還是事件溯源日誌,都不如簡單的基於全序的決策原則更重要。 如果你可以透過單個系統來提供所有使用者輸入,從而決定所有寫入的排序,則透過按相同順序處理寫入,可以更容易地衍生出其他資料表示。 這是狀態機複製方法的一個應用,我們在 “[全序廣播](ch9.md#全序廣播)” 中看到。無論你使用變更資料捕獲還是事件溯源日誌,都不如簡單的基於全序的決策原則更重要。
基於事件日誌來更新衍生資料的系統,通常可以做到**確定性**與**冪等性**(請參閱[冪等性](ch11.md#冪等性)”),使得從故障中恢復相當容易。 基於事件日誌來更新衍生資料的系統,通常可以做到 **確定性****冪等性**(請參閱 “[冪等性](ch11.md#冪等性)”),使得從故障中恢復相當容易。
#### 衍生資料與分散式事務 #### 衍生資料與分散式事務
@ -91,9 +91,9 @@
### 批處理與流處理 ### 批處理與流處理
我會說資料整合的目標是,確保資料最終能在所有正確的地方表現出正確的形式。這樣做需要消費輸入,轉換,連線,過濾,聚合,訓練模型,評估,以及最終寫出適當的輸出。批處理和流處理是實現這一目標的工具。 我會說資料整合的目標是,確保資料最終能在所有正確的地方表現出正確的形式。這樣做需要消費輸入、轉換、連線、過濾、聚合、訓練模型、評估、以及最終寫出適當的輸出。批處理和流處理是實現這一目標的工具。
批處理和流處理的輸出是衍生資料集,例如搜尋索引,物化檢視,向用戶顯示的建議,聚合指標等(請參閱“[批處理工作流的輸出](ch10.md#批處理工作流的輸出)”和“[流處理的應用](ch11.md#流處理的應用)”)。 批處理和流處理的輸出是衍生資料集,例如搜尋索引、物化檢視、向用戶顯示的建議、聚合指標等(請參閱 “[批處理工作流的輸出](ch10.md#批處理工作流的輸出)” 和 “[流處理的應用](ch11.md#流處理的應用)”)。
正如我們在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中看到的,批處理和流處理有許多共同的原則,主要的根本區別在於流處理器在無限資料集上執行,而批處理輸入是已知的有限大小。處理引擎的實現方式也有很多細節上的差異,但是這些區別已經開始模糊。 正如我們在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中看到的,批處理和流處理有許多共同的原則,主要的根本區別在於流處理器在無限資料集上執行,而批處理輸入是已知的有限大小。處理引擎的實現方式也有很多細節上的差異,但是這些區別已經開始模糊。
@ -107,7 +107,7 @@ Spark在批處理引擎上執行流處理將流分解為**微批次microba
原則上,衍生資料系統可以同步地維護,就像關係資料庫在與索引表寫入操作相同的事務中同步更新次級索引一樣。然而,非同步是使基於事件日誌的系統穩健的原因:它允許系統的一部分故障被抑制在本地。而如果任何一個參與者失敗,分散式事務將中止,因此它們傾向於透過將故障傳播到系統的其餘部分來放大故障(請參閱 “[分散式事務的限制](ch9.md#分散式事務的限制)”)。 原則上,衍生資料系統可以同步地維護,就像關係資料庫在與索引表寫入操作相同的事務中同步更新次級索引一樣。然而,非同步是使基於事件日誌的系統穩健的原因:它允許系統的一部分故障被抑制在本地。而如果任何一個參與者失敗,分散式事務將中止,因此它們傾向於透過將故障傳播到系統的其餘部分來放大故障(請參閱 “[分散式事務的限制](ch9.md#分散式事務的限制)”)。
我們在“[分割槽與次級索引](ch6.md#分割槽與次級索引)”中看到次級索引經常跨越分割槽邊界。具有次級索引的分割槽系統需要將寫入傳送到多個分割槽如果索引按關鍵詞分割槽的話或將讀取傳送到所有分割槽如果索引是按文件分割槽的話。如果索引是非同步維護的這種跨分割槽通訊也是最可靠和最可伸縮的【8】另請參閱“[多分割槽資料處理](多分割槽資料處理)”)。 我們在 “[分割槽與次級索引](ch6.md#分割槽與次級索引)” 中看到次級索引經常跨越分割槽邊界。具有次級索引的分割槽系統需要將寫入傳送到多個分割槽如果索引按關鍵詞分割槽的話或將讀取傳送到所有分割槽如果索引是按文件分割槽的話。如果索引是非同步維護的這種跨分割槽通訊也是最可靠和最可伸縮的【8】另請參閱 “[多分割槽資料處理](#多分割槽資料處理)”)。
#### 應用演化後重新處理資料 #### 應用演化後重新處理資料
@ -160,7 +160,7 @@ Lambda架構是一種有影響力的想法它將資料系統的設計變得
Unix 和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix 認為它的目的是為程式設計師提供一種相當低層次的硬體的邏輯抽象而關係資料庫則希望為應用程式設計師提供一種高層次的抽象以隱藏磁碟上資料結構的複雜性併發性崩潰恢復等等。Unix 發展出的管道和檔案只是位元組序列,而資料庫則發展出了 SQL 和事務。 Unix 和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix 認為它的目的是為程式設計師提供一種相當低層次的硬體的邏輯抽象而關係資料庫則希望為應用程式設計師提供一種高層次的抽象以隱藏磁碟上資料結構的複雜性併發性崩潰恢復等等。Unix 發展出的管道和檔案只是位元組序列,而資料庫則發展出了 SQL 和事務。
哪種方法更好?當然這取決於你想要的是什麼。 Unix“簡單的”,因為它是對硬體資源相當薄的包裝;關係資料庫是“更簡單”的,因為一個簡短的宣告性查詢可以利用很多強大的基礎設施(查詢最佳化,索引,連線方法,併發控制,複製等),而不需要查詢的作者理解其實現細節。 哪種方法更好?當然這取決於你想要的是什麼。 Unix“簡單的”,因為它是對硬體資源相當薄的包裝;關係資料庫是 “更簡單” 的,因為一個簡短的宣告性查詢可以利用很多強大的基礎設施(查詢最佳化、索引、連線方法、併發控制、複製等),而不需要查詢的作者理解其實現細節。
這些哲學之間的矛盾已經持續了幾十年Unix 和關係模型都出現在 70 年代初),仍然沒有解決。例如,我將 NoSQL 運動解釋為,希望將類 Unix 的低級別抽象方法應用於分散式 OLTP 資料儲存的領域。 這些哲學之間的矛盾已經持續了幾十年Unix 和關係模型都出現在 70 年代初),仍然沒有解決。例如,我將 NoSQL 運動解釋為,希望將類 Unix 的低級別抽象方法應用於分散式 OLTP 資料儲存的領域。
@ -173,7 +173,7 @@ Unix和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix
* 次級索引,使你可以根據欄位的值有效地搜尋記錄(請參閱 “[其他索引結構](ch3.md#其他索引結構)”) * 次級索引,使你可以根據欄位的值有效地搜尋記錄(請參閱 “[其他索引結構](ch3.md#其他索引結構)”)
* 物化檢視,這是一種預計算的查詢結果快取(請參閱 “[聚合:資料立方體和物化檢視](ch3.md#聚合:資料立方體和物化檢視)”) * 物化檢視,這是一種預計算的查詢結果快取(請參閱 “[聚合:資料立方體和物化檢視](ch3.md#聚合:資料立方體和物化檢視)”)
* 複製日誌,保持其他節點上資料的副本最新(請參閱 “[複製日誌的實現](ch5.md#複製日誌的實現)”) * 複製日誌,保持其他節點上資料的副本最新(請參閱 “[複製日誌的實現](ch5.md#複製日誌的實現)”)
* 全文搜尋索引,允許在文字中進行關鍵字搜尋(請參閱“[全文搜尋和模糊索引](ch3.md#全文搜4索和模糊索引)”也內置於某些關係資料庫【1】 * 全文搜尋索引,允許在文字中進行關鍵字搜尋(請參閱 “[全文搜尋和模糊索引](ch3.md#全文搜尋和模糊索引)”也內置於某些關係資料庫【1】
在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中,出現了類似的主題。我們討論瞭如何構建全文搜尋索引(請參閱 “[批處理工作流的輸出](ch10.md#批處理工作流的輸出)”),瞭解瞭如何維護物化檢視(請參閱 “[維護物化檢視](ch11.md#維護物化檢視)”)以及如何將變更從資料庫複製到衍生資料系統(請參閱 “[變更資料捕獲](ch11.md#變更資料捕獲)”)。 在 [第十章](ch10.md) 和 [第十一章](ch11.md) 中,出現了類似的主題。我們討論瞭如何構建全文搜尋索引(請參閱 “[批處理工作流的輸出](ch10.md#批處理工作流的輸出)”),瞭解瞭如何維護物化檢視(請參閱 “[維護物化檢視](ch11.md#維護物化檢視)”)以及如何將變更從資料庫複製到衍生資料系統(請參閱 “[變更資料捕獲](ch11.md#變更資料捕獲)”)。
@ -267,13 +267,13 @@ Unix和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix
理論上,資料庫可以是任意應用程式碼的部署環境,就如同作業系統一樣。然而實踐中它們對這一目標適配的很差。它們不滿足現代應用開發的要求,例如依賴和軟體包管理、版本控制、滾動升級、可演化性、監控、指標、對網路服務的呼叫以及與外部系統的整合。 理論上,資料庫可以是任意應用程式碼的部署環境,就如同作業系統一樣。然而實踐中它們對這一目標適配的很差。它們不滿足現代應用開發的要求,例如依賴和軟體包管理、版本控制、滾動升級、可演化性、監控、指標、對網路服務的呼叫以及與外部系統的整合。
另一方面MesosYARNDockerKubernetes等部署和叢集管理工具專為執行應用程式碼而設計。透過專注於做好一件事情,他們能夠做得比將資料庫作為其眾多功能之一執行使用者定義的功能要好得多。 另一方面Mesos、YARN、Docker、Kubernetes 等部署和叢集管理工具專為執行應用程式碼而設計。透過專注於做好一件事情,他們能夠做得比將資料庫作為其眾多功能之一執行使用者定義的功能要好得多。
我認為讓系統的某些部分專門用於持久資料儲存並讓其他部分專門執行應用程式程式碼是有意義的。這兩者可以在保持獨立的同時互動。 我認為讓系統的某些部分專門用於持久資料儲存並讓其他部分專門執行應用程式程式碼是有意義的。這兩者可以在保持獨立的同時互動。
現在大多數 Web 應用程式都是作為無狀態服務部署的其中任何使用者請求都可以路由到任何應用程式伺服器並且伺服器在傳送響應後會忘記所有請求。這種部署方式很方便因為可以隨意新增或刪除伺服器但狀態必須到某個地方通常是資料庫。趨勢是將無狀態應用程式邏輯與狀態管理資料庫分開不將應用程式邏輯放入資料庫中也不將持久狀態置於應用程式中【36】。正如函數語言程式設計社群喜歡開玩笑說的那樣“我們相信 **教會Church****國家state** 的分離”【37】 [^i] 現在大多數 Web 應用程式都是作為無狀態服務部署的其中任何使用者請求都可以路由到任何應用程式伺服器並且伺服器在傳送響應後會忘記所有請求。這種部署方式很方便因為可以隨意新增或刪除伺服器但狀態必須到某個地方通常是資料庫。趨勢是將無狀態應用程式邏輯與狀態管理資料庫分開不將應用程式邏輯放入資料庫中也不將持久狀態置於應用程式中【36】。正如函數語言程式設計社群喜歡開玩笑說的那樣“我們相信 **教會Church****國家state** 的分離”【37】 [^i]
[^i]: 解釋笑話很少會讓人感覺更好,但我不想讓任何人感到被遺漏。 在這裡Church指代的是數學家的阿隆佐·邱奇他創立了lambda演算,這是計算的早期形式,是大多數函數語言程式設計語言的基礎。 lambda演算不具有可變狀態即沒有變數可以被覆蓋所以可以說可變狀態與Church的工作是分離的。 [^i]: 解釋笑話很少會讓人感覺更好,但我不想讓任何人感到被遺漏。 在這裡Church 指代的是數學家的阿隆佐・邱奇,他創立了 lambda 演算,這是計算的早期形式,是大多數函數語言程式設計語言的基礎。 lambda 演算不具有可變狀態(即沒有變數可以被覆蓋),所以可以說可變狀態與 Church 的工作是分離的。
在這個典型的 Web 應用模型中,資料庫充當一種可以透過網路同步訪問的可變共享變數。應用程式可以讀取和更新變數,而資料庫負責維持它的永續性,提供一些諸如併發控制和容錯的功能。 在這個典型的 Web 應用模型中,資料庫充當一種可以透過網路同步訪問的可變共享變數。應用程式可以讀取和更新變數,而資料庫負責維持它的永續性,提供一些諸如併發控制和容錯的功能。
@ -371,7 +371,7 @@ Unix和關係資料庫以非常不同的哲學來處理資訊管理問題。Unix
#### 端到端的事件流 #### 端到端的事件流
最近用於開發有狀態的客戶端與使用者介面的工具,例如如Elm語言【30】和Facebook的ReactFlux和Redux工具鏈,已經透過訂閱表示使用者輸入或伺服器響應的事件流來管理客戶端的內部狀態,其結構與事件溯源相似(請參閱“[事件溯源](ch11.md#事件溯源)”)。 最近用於開發有狀態的客戶端與使用者介面的工具,例如如 Elm 語言【30】和 Facebook 的 React、Flux 和 Redux 工具鏈,已經透過訂閱表示使用者輸入或伺服器響應的事件流來管理客戶端的內部狀態,其結構與事件溯源相似(請參閱 “[事件溯源](ch11.md#事件溯源)”)。
將這種程式設計模型擴充套件為:允許伺服器將狀態變更事件推送到客戶端的事件管道中,是非常自然的。因此,狀態變化可以透過 **端到端end-to-end** 的寫路徑流動:從一個裝置上的互動觸發狀態變更開始,經由事件日誌,並穿過幾個衍生資料系統與流處理器,一直到另一臺裝置上的使用者介面,而有人正在觀察使用者介面上的狀態變化。這些狀態變化能以相當低的延遲傳播 —— 比如說,在一秒內從一端到另一端。 將這種程式設計模型擴充套件為:允許伺服器將狀態變更事件推送到客戶端的事件管道中,是非常自然的。因此,狀態變化可以透過 **端到端end-to-end** 的寫路徑流動:從一個裝置上的互動觸發狀態變更開始,經由事件日誌,並穿過幾個衍生資料系統與流處理器,一直到另一臺裝置上的使用者介面,而有人正在觀察使用者介面上的狀態變化。這些狀態變化能以相當低的延遲傳播 —— 比如說,在一秒內從一端到另一端。
@ -488,7 +488,7 @@ COMMIT;
#### 端到端原則 #### 端到端原則
抑制重複事務的這種情況只是一個更普遍的原則的一個例子,這個原則被稱為**端到端原則end-to-end argument**它在1984年由SaltzerReed和Clark闡述【55】 抑制重複事務的這種情況只是一個更普遍的原則的一個例子,這個原則被稱為 **端到端原則end-to-end argument**,它在 1984 年由 Saltzer、Reed 和 Clark 闡述【55】
> 只有在通訊系統兩端應用的知識與幫助下,所討論的功能才能完全地正確地實現。因而將這種被質疑的功能作為通訊系統本身的功能是不可能的(有時,通訊系統可以提供這種功能的不完備版本,可能有助於提高效能)。 > 只有在通訊系統兩端應用的知識與幫助下,所討論的功能才能完全地正確地實現。因而將這種被質疑的功能作為通訊系統本身的功能是不可能的(有時,通訊系統可以提供這種功能的不完備版本,可能有助於提高效能)。
> >
@ -499,7 +499,7 @@ COMMIT;
類似的原則也適用於加密【55】家庭 WiFi 網路上的密碼可以防止人們竊聽你的 WiFi 流量,但無法阻止網際網路上其他地方攻擊者的窺探;客戶端與伺服器之間的 TLS/SSL 可以阻擋網路攻擊者,但無法阻止惡意伺服器。只有端到端的加密和認證可以防止所有這些事情。 類似的原則也適用於加密【55】家庭 WiFi 網路上的密碼可以防止人們竊聽你的 WiFi 流量,但無法阻止網際網路上其他地方攻擊者的窺探;客戶端與伺服器之間的 TLS/SSL 可以阻擋網路攻擊者,但無法阻止惡意伺服器。只有端到端的加密和認證可以防止所有這些事情。
儘管低層級的功能TCP重複抑制乙太網校驗和WiFi加密無法單獨提供所需的端到端功能但它們仍然很有用因為它們能降低較高層級出現問題的可能性。例如如果我們沒有TCP來將資料包排成正確的順序那麼HTTP請求通常就會被攪爛。我們只需要記住低級別的可靠性功能本身並不足以確保端到端的正確性。 儘管低層級的功能TCP 重複抑制、乙太網校驗和、WiFi 加密)無法單獨提供所需的端到端功能,但它們仍然很有用,因為它們能降低較高層級出現問題的可能性。例如,如果我們沒有 TCP 來將資料包排成正確的順序,那麼 HTTP 請求通常就會被攪爛。我們只需要記住,低級別的可靠性功能本身並不足以確保端到端的正確性。
#### 在資料系統中應用端到端思考 #### 在資料系統中應用端到端思考
@ -705,7 +705,7 @@ ACID意義下的一致性請參閱“[一致性](ch7.md#一致性)”)基
目前,將可審計性作為頂層關注點的資料系統並不多。一些應用實現了自己的審計機制,例如將所有變更記錄到單獨的審計表中,但是確保審計日誌與資料庫狀態的完整性仍然是很困難的。可以定期使用硬體安全模組對事務日誌進行簽名來防止篡改,但這無法保證正確的事務一開始就能進入到日誌中。 目前,將可審計性作為頂層關注點的資料系統並不多。一些應用實現了自己的審計機制,例如將所有變更記錄到單獨的審計表中,但是確保審計日誌與資料庫狀態的完整性仍然是很困難的。可以定期使用硬體安全模組對事務日誌進行簽名來防止篡改,但這無法保證正確的事務一開始就能進入到日誌中。
使用密碼學工具來證明系統的完整性是十分有趣的,這種方式對於寬泛的硬體與軟體問題,甚至是潛在的惡意行為都很穩健有效。加密貨幣區塊鏈以及諸如比特幣以太坊RippleStellar的分散式賬本技術已經迅速出現在這一領域【71,72,73】。 使用密碼學工具來證明系統的完整性是十分有趣的,這種方式對於寬泛的硬體與軟體問題,甚至是潛在的惡意行為都很穩健有效。加密貨幣、區塊鏈、以及諸如比特幣、以太坊、Ripple、Stellar 的分散式賬本技術已經迅速出現在這一領域【71,72,73】。
我沒有資格評論這些技術用於貨幣,或者合同商定機制的價值。但從資料系統的角度來看,它們包含了一些有趣的想法。實質上,它們是分散式資料庫,具有資料模型與事務機制,而不同副本可以由互不信任的組織託管。副本不斷檢查其他副本的完整性,並使用共識協議對應當執行的事務達成一致。 我沒有資格評論這些技術用於貨幣,或者合同商定機制的價值。但從資料系統的角度來看,它們包含了一些有趣的想法。實質上,它們是分散式資料庫,具有資料模型與事務機制,而不同副本可以由互不信任的組織託管。副本不斷檢查其他副本的完整性,並使用共識協議對應當執行的事務達成一致。

View file

@ -2,7 +2,7 @@
![](../img/ch9.png) ![](../img/ch9.png)
> 好死還是賴活著? > 好死還是賴活著
> —— Jay Kreps, 關於 Kafka 與 Jepsen 的若干筆記 (2013) > —— Jay Kreps, 關於 Kafka 與 Jepsen 的若干筆記 (2013)
--------------- ---------------
@ -412,7 +412,7 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】它只考慮了一
#### 蘭伯特時間戳 #### 蘭伯特時間戳
儘管剛才描述的三個序列號生成器與因果不一致,但實際上有一個簡單的方法來產生與因果關係一致的序列號。它被稱為蘭伯特時間戳,萊斯利·蘭伯特Leslie Lamport於1978年提出【56】現在是分散式系統領域中被引用最多的論文之一。 儘管剛才描述的三個序列號生成器與因果不一致,但實際上有一個簡單的方法來產生與因果關係一致的序列號。它被稱為蘭伯特時間戳,萊斯利・蘭伯特Leslie Lamport於 1978 年提出【56】現在是分散式系統領域中被引用最多的論文之一。
[圖 9-8](../img/fig9-8.png) 說明了蘭伯特時間戳的應用。每個節點都有一個唯一識別符號,和一個儲存自己執行運算元量的計數器。 蘭伯特時間戳就是兩者的簡單組合:(計數器,節點 ID$(counter, node ID)$。兩個節點有時可能具有相同的計數器值,但透過在時間戳中包含節點 ID每個時間戳都是唯一的。 [圖 9-8](../img/fig9-8.png) 說明了蘭伯特時間戳的應用。每個節點都有一個唯一識別符號,和一個儲存自己執行運算元量的計數器。 蘭伯特時間戳就是兩者的簡單組合:(計數器,節點 ID$(counter, node ID)$。兩個節點有時可能具有相同的計數器值,但透過在時間戳中包含節點 ID每個時間戳都是唯一的。
@ -683,7 +683,7 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】它只考慮了一
#### XA事務 #### XA事務
*X/Open XA***擴充套件架構eXtended Architecture** 的縮寫是跨異構技術實現兩階段提交的標準【76,77】。它於1991年推出並得到了廣泛的實現許多傳統關係資料庫包括PostgreSQLMySQLDB2SQL Server和Oracle和訊息代理包括ActiveMQHornetQMSMQ和IBM MQ 都支援XA。 *X/Open XA***擴充套件架構eXtended Architecture** 的縮寫是跨異構技術實現兩階段提交的標準【76,77】。它於 1991 年推出並得到了廣泛的實現:許多傳統關係資料庫(包括 PostgreSQL、MySQL、DB2、SQL Server 和 Oracle和訊息代理包括 ActiveMQ、HornetQ、MSMQ 和 IBM MQ 都支援 XA。
XA 不是一個網路協議 —— 它只是一個用來與事務協調者連線的 C API。其他語言也有這種 API 的繫結;例如在 Java EE 應用的世界中XA 事務是使用 **Java 事務 APIJTA, Java Transaction API** 實現的,而許多使用 **Java 資料庫連線JDBC, Java Database Connectivity** 的資料庫驅動,以及許多使用 **Java 訊息服務JMS** API 的訊息代理都支援 **Java 事務 APIJTA** XA 不是一個網路協議 —— 它只是一個用來與事務協調者連線的 C API。其他語言也有這種 API 的繫結;例如在 Java EE 應用的世界中XA 事務是使用 **Java 事務 APIJTA, Java Transaction API** 實現的,而許多使用 **Java 資料庫連線JDBC, Java Database Connectivity** 的資料庫驅動,以及許多使用 **Java 訊息服務JMS** API 的訊息代理都支援 **Java 事務 APIJTA**
@ -828,7 +828,7 @@ XA事務解決了保持多個參與者資料系統相互一致的現實的
像 ZooKeeper 或 etcd 這樣的專案通常被描述為 “分散式鍵值儲存” 或 “協調與配置服務”。這種服務的 API 看起來非常像資料庫:你可以讀寫給定鍵的值,並遍歷鍵。所以如果它們基本上算是資料庫的話,為什麼它們要把工夫全花在實現一個共識演算法上呢?是什麼使它們區別於其他任意型別的資料庫? 像 ZooKeeper 或 etcd 這樣的專案通常被描述為 “分散式鍵值儲存” 或 “協調與配置服務”。這種服務的 API 看起來非常像資料庫:你可以讀寫給定鍵的值,並遍歷鍵。所以如果它們基本上算是資料庫的話,為什麼它們要把工夫全花在實現一個共識演算法上呢?是什麼使它們區別於其他任意型別的資料庫?
為了理解這一點簡單瞭解如何使用ZooKeeper這類服務是很有幫助的。作為應用開發人員你很少需要直接使用ZooKeeper因為它實際上不適合當成通用資料庫來用。更有可能的是你會透過其他專案間接依賴它例如HBaseHadoop YARNOpenStack Nova和Kafka都依賴ZooKeeper在後臺執行。這些專案從它那裡得到了什麼? 為了理解這一點,簡單瞭解如何使用 ZooKeeper 這類服務是很有幫助的。作為應用開發人員,你很少需要直接使用 ZooKeeper因為它實際上不適合當成通用資料庫來用。更有可能的是你會透過其他專案間接依賴它例如 HBase、Hadoop YARN、OpenStack Nova 和 Kafka 都依賴 ZooKeeper 在後臺執行。這些專案從它那裡得到了什麼?
ZooKeeper 和 etcd 被設計為容納少量完全可以放在記憶體中的資料(雖然它們仍然會寫入磁碟以保證永續性),所以你不會想著把所有應用資料放到這裡。這些少量資料會透過容錯的全序廣播演算法複製到所有節點上。正如前面所討論的那樣,資料庫複製需要的就是全序廣播:如果每條訊息代表對資料庫的寫入,則以相同的順序應用相同的寫入操作可以使副本之間保持一致。 ZooKeeper 和 etcd 被設計為容納少量完全可以放在記憶體中的資料(雖然它們仍然會寫入磁碟以保證永續性),所以你不會想著把所有應用資料放到這裡。這些少量資料會透過容錯的全序廣播演算法複製到所有節點上。正如前面所討論的那樣,資料庫複製需要的就是全序廣播:如果每條訊息代表對資料庫的寫入,則以相同的順序應用相同的寫入操作可以使副本之間保持一致。
@ -866,7 +866,7 @@ ZooKeeper/Chubby模型執行良好的一個例子是如果你有幾個程序
#### 服務發現 #### 服務發現
ZooKeeperetcd和Consul也經常用於服務發現——也就是找出你需要連線到哪個IP地址才能到達特定的服務。在雲資料中心環境中虛擬機器來來往往很常見你通常不會事先知道服務的IP地址。相反你可以配置你的服務使其在啟動時註冊服務登錄檔中的網路端點然後可以由其他服務找到它們。 ZooKeeper、etcd 和 Consul 也經常用於服務發現 —— 也就是找出你需要連線到哪個 IP 地址才能到達特定的服務。在雲資料中心環境中,虛擬機器來來往往很常見,你通常不會事先知道服務的 IP 地址。相反,你可以配置你的服務,使其在啟動時註冊服務登錄檔中的網路端點,然後可以由其他服務找到它們。
但是,服務發現是否需要達成共識還不太清楚。 DNS 是查詢服務名稱的 IP 地址的傳統方式,它使用多層快取來實現良好的效能和可用性。從 DNS 讀取是絕對不線性一致性的,如果 DNS 查詢的結果有點陳舊通常不會有問題【109】。 DNS 的可用性和對網路中斷的魯棒性更重要。 但是,服務發現是否需要達成共識還不太清楚。 DNS 是查詢服務名稱的 IP 地址的傳統方式,它使用多層快取來實現良好的效能和可用性。從 DNS 讀取是絕對不線性一致性的,如果 DNS 查詢的結果有點陳舊通常不會有問題【109】。 DNS 的可用性和對網路中斷的魯棒性更重要。