Merge branch 'master' of github.com:Vonng/ddia

This commit is contained in:
Vonng 2021-08-19 17:16:28 +08:00
commit ee7c94f9b5
29 changed files with 248 additions and 11165 deletions

View File

@ -3,8 +3,8 @@ import os, sys, opencc
def convert(src_path, dst_path, cfg='s2twp.json'):
converter = opencc.OpenCC(cfg)
with open(src_path, "r") as src, open(dst_path, "w+") as dst:
dst.write("\n".join(converter.convert(line) for line in src))
with open(src_path, "r", encoding='utf-8') as src, open(dst_path, "w+", encoding='utf-8') as dst:
dst.write("\n".join(converter.convert(line.rstrip()).replace('(img/', '(../img/') for line in src))
print("convert %s to %s" % (src_path, dst_path))

190
ch10.md
View File

@ -10,9 +10,9 @@
[TOC]
在本书的前两部分中,我们讨论了很多关于**请求**和**查询**以及相应的**响应**或**结果**。许多现有数据系统中都采用这种数据处理方式:你发送请求指令,一段时间后(我们期望)系统会给出一个结果。数据库,缓存,搜索索引,Web服务器以及其他一些系统都以这种方式工作。
在本书的前两部分中,我们讨论了很多关于**请求**和**查询**以及相应的**响应**或**结果**。许多现有数据系统中都采用这种数据处理方式:你发送请求指令,一段时间后(我们期望)系统会给出一个结果。数据库、缓存、搜索索引、Web服务器以及其他一些系统都以这种方式工作。
像这样的**在线online**系统无论是浏览器请求页面还是调用远程API的服务我们通常认为请求是由人类用户触发的并且正在等待响应。他们不应该等太久所以我们非常关注系统的响应时间参阅“[描述性能](ch1.md)”)。
像这样的**在线online**系统无论是浏览器请求页面还是调用远程API的服务我们通常认为请求是由人类用户触发的并且正在等待响应。他们不应该等太久所以我们非常关注系统的响应时间参阅“[描述性能](ch1.md#描述性能)”)。
Web和越来越多的基于HTTP/REST的API使交互的请求/响应风格变得如此普遍,以至于很容易将其视为理所当然。但我们应该记住,这不是构建系统的唯一方式,其他方法也有其优点。我们来看看三种不同类型的系统:
@ -28,19 +28,19 @@
流处理介于在线和离线(批处理)之间,所以有时候被称为**准实时near-real-time**或**准在线nearline**处理。像批处理系统一样,流处理消费输入并产生输出(并不需要响应请求)。但是,流式作业在事件发生后不久就会对事件进行操作,而批处理作业则需等待固定的一组输入数据。这种差异使流处理系统比起批处理系统具有更低的延迟。由于流处理基于批处理,我们将在[第11章](ch11.md)讨论它。
正如我们将在本章中看到的那样,批处理是构建可靠可伸缩和可维护应用程序的重要组成部分。例如2004年发布的批处理算法Map-Reduce可能被过分热情地被称为“造就Google大规模可伸缩性的算法”【2】。随后在各种开源数据系统中得到应用包括HadoopCouchDB和MongoDB。
正如我们将在本章中看到的那样,批处理是构建可靠可伸缩和可维护应用程序的重要组成部分。例如2004年发布的批处理算法Map-Reduce可能被过分热情地被称为“造就Google大规模可伸缩性的算法”【2】。随后在各种开源数据系统中得到应用包括HadoopCouchDB和MongoDB。
与多年前为数据仓库开发的并行处理系统【3,4】相比MapReduce是一个相当低级别的编程模型但它使得在商用硬件上能进行的处理规模迈上一个新的台阶。虽然MapReduce的重要性正在下降【5】但它仍然值得去理解因为它描绘了一幅关于批处理为什么有用以及如何用的清晰图景。
与多年前为数据仓库开发的并行处理系统【3,4】相比MapReduce是一个相当低级别的编程模型但它使得在商用硬件上能进行的处理规模迈上一个新的台阶。虽然MapReduce的重要性正在下降【5】但它仍然值得去理解因为它描绘了一幅关于批处理为什么有用以及如何做到有用的清晰图景。
实际上批处理是一种非常古老的计算方式。早在可编程数字计算机诞生之前打孔卡制表机例如1890年美国人口普查【6】中使用的霍尔里斯机实现了半机械化的批处理形式从大量输入中汇总计算。 Map-Reduce与1940年代和1950年代广泛用于商业数据处理的机电IBM卡片分类机器有着惊人的相似之处【7】。正如我们所说历史总是在不断重复自己。
在本章中我们将了解MapReduce和其他一些批处理算法和框架并探索它们在现代数据系统中的作用。但首先我们将看看使用标准Unix工具的数据处理。即使你已经熟悉了它们Unix的哲学也值得一读Unix的思想和经验教训可以迁移到大规模异构的分布式数据系统中。
在本章中我们将了解MapReduce和其他一些批处理算法和框架并探索它们在现代数据系统中的作用。但首先我们将看看使用标准Unix工具的数据处理。即使你已经熟悉了它们Unix的哲学也值得一读Unix的思想和经验教训可以迁移到大规模异构的分布式数据系统中。
## 使用Unix工具的批处理
我们从一个简单的例子开始。假设您有一台Web服务器每次处理请求时都会在日志文件中附加一行。例如使用nginx默认访问日志格式日志的一行可能如下所示
我们从一个简单的例子开始。假设您有一台Web服务器每次处理请求时都会在日志文件中附加一行。例如使用nginx默认访问日志格式,日志的一行可能如下所示:
```bash
216.58.210.78 - - [27/Feb/2015:17:55:11 +0000] "GET /css/typography.css HTTP/1.1"
@ -59,7 +59,7 @@ AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36"
### 分析简单日志
### 简单日志分析
很多工具可以从这些日志文件生成关于网站流量的漂亮的报告但为了练手让我们使用基本的Unix功能创建自己的工具。 例如,假设你想在你的网站上找到五个最受欢迎的网页。 则可以在Unix shell中这样做[^i]
@ -132,13 +132,13 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
### Unix哲学
我们可以非常容易地使用前一个例子中的一系列命令来分析日志文件这并非巧合事实上这实际上是Unix的关键设计思想之一且它今天仍然令人讶异地关联。让我们更深入地研究一下以便从Unix中借鉴一些想法【10】。
我们可以非常容易地使用前一个例子中的一系列命令来分析日志文件这并非巧合事实上这实际上是Unix的关键设计思想之一而且它直至今天也仍然令人讶异地重要。让我们更深入地研究一下以便从Unix中借鉴一些想法【10】。
Unix管道的发明者道格·麦克罗伊Doug McIlroy在1964年首先描述了这种情况【11】“当我们需要将消息从一个程序传递另一个程序时我们需要一种类似水管法兰的拼接程序的方式【a】 I/O应该也按照这种方式进行“。水管的类比仍然在生效通过管道连接程序的想法成为了现在被称为**Unix哲学**的一部分 —— 这一组设计原则在Unix用户与开发者之间流行起来该哲学在1978年表述如下【12,13】
Unix管道的发明者道格·麦克罗伊Doug McIlroy在1964年首先描述了这种情况【11】我们需要一种类似园艺胶管的方式来拼接程序 —— 当我们需要将消息从一个程序传递另一个程序时,直接接上去就行。I/O应该也按照这种方式进行“。水管的类比仍然在生效通过管道连接程序的想法成为了现在被称为**Unix哲学**的一部分 —— 这一组设计原则在Unix用户与开发者之间流行起来该哲学在1978年表述如下【12,13】
1. 让每个程序都做好一件事。要做一件新的工作,写一个新程序,而不是通过添加“功能”让老程序复杂化。
2. 期待每个程序的输出成为另一个程序的输入。不要将无关信息混入输出。避免使用严格的列数据或二进制输入格式。不要坚持交互式输入。
3. 设计和构建软件,甚至是操作系统,要尽早尝试,最好在几周内完成。不要犹豫,扔掉笨拙的部分,重建它们。
3. 设计和构建软件时,即使是操作系统,也让它们能够尽早地被试用,最好在几周内完成。不要犹豫,扔掉笨拙的部分,重建它们。
4. 优先使用工具来减轻编程任务,即使必须曲线救国编写工具,且在用完后很可能要扔掉大部分。
这种方法 —— 自动化,快速原型设计,增量式迭代,对实验友好,将大型项目分解成可管理的块 —— 听起来非常像今天的敏捷开发和DevOps运动。奇怪的是四十年来变化不大。
@ -163,7 +163,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
尽管几十年后还不够完美但统一的Unix接口仍然是非常出色的设计。没有多少软件能像Unix工具一样交互组合的这么好你不能通过自定义分析工具轻松地将电子邮件帐户的内容和在线购物历史记录以管道传送至电子表格中并将结果发布到社交网络或维基。今天像Unix工具一样流畅地运行程序是一种例外而不是规范。
即使是具有**相同数据模型**的数据库,将数据从一种导出再导入另一种也并不容易。缺乏整合导致了数据的**巴尔干化**[^译注i]。
即使是具有**相同数据模型**的数据库,将数据从一种数据库导出再导入另一种数据库也并不容易。缺乏整合导致了数据的**巴尔干化**[^译注i]。
[^译注i]: **巴尔干化Balkanization**是一个常带有贬义的地缘政治学术语,其定义为:一个国家或政区分裂成多个互相敌对的国家或政区的过程。
@ -173,13 +173,13 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
Unix工具的另一个特点是使用标准输入`stdin`)和标准输出(`stdout`)。如果你运行一个程序,而不指定任何其他的东西,标准输入来自键盘,标准输出指向屏幕。但是,你也可以从文件输入和/或将输出重定向到文件。管道允许你将一个进程的标准输出附加到另一个进程的标准输入(有个小内存缓冲区,而不需要将整个中间数据流写入磁盘)。
程序仍然可以直接读取和写入文件,但如果程序不担心特定的文件路径只使用标准输入和标准输出则Unix方法效果最好。这允许shell用户以任何他们想要的方式连接输入和输出该程序不知道或不关心输入来自哪里以及输出到哪里。 (人们可以说这是一种**松耦合loose coupling****晚期绑定late binding**【15】或**控制反转inversion of control**【16】。将输入/输出布线与程序逻辑分开,可以将小工具组合成更大的系统。
如果需要,程序仍然可以直接读取和写入文件,但Unix方法在程序不关心特定的文件路径、只使用标准输入和标准输出时效果最好。这允许shell用户以任何他们想要的方式连接输入和输出该程序不知道或不关心输入来自哪里以及输出到哪里。 (人们可以说这是一种**松耦合loose coupling****晚期绑定late binding**【15】或**控制反转inversion of control**【16】。将输入/输出布线与程序逻辑分开,可以将小工具组合成更大的系统。
你甚至可以编写自己的程序并将它们与操作系统提供的工具组合在一起。你的程序只需要从标准输入读取输入并将输出写入标准输出它就可以加入数据处理的管道中。在日志分析示例中你可以编写一个将Usage-Agent字符串转换为更灵敏的浏览器标识符或者将IP地址转换为国家代码的工具并将其插入管道。`sort`程序并不关心它是否与操作系统的另一部分或者你写的程序通信。
但是,使用`stdin`和`stdout`能做的事情是有限的。需要多个输入或输出的程序是可能的,但非常棘手。你没法将程序的输出管道连接至网络连接中【17,18】[^iii] 。如果程序直接打开文件进行读取和写入或者将另一个程序作为子进程启动或者打开网络连接那么I/O的布线就取决于程序本身了。它仍然可以被配置例如通过命令行选项但在Shell中对输入和输出进行布线的灵活性就少了。
但是,使用`stdin`和`stdout`能做的事情是有限的。需要多个输入或输出的程序虽然可能,却非常棘手。你没法将程序的输出管道连接至网络连接中【17,18】[^iii] 。如果程序直接打开文件进行读取和写入或者将另一个程序作为子进程启动或者打开网络连接那么I/O的布线就取决于程序本身了。它仍然可以被配置例如通过命令行选项但在Shell中对输入和输出进行布线的灵活性就少了。
[^iii]: 除了使用一个单独的工具,如`netcat`或`curl`。 Unix开始试图将所有东西都表示为文件但是BSD套接字API偏离了这个惯例【17】。研究用操作系统Plan 9和Inferno在使用文件方面更加一致它们将TCP连接表示为`/net/tcp`中的文件【18】。
[^iii]: 除了使用一个单独的工具,如`netcat`或`curl`。 Unix起初试图将所有东西都表示为文件但是BSD套接字API偏离了这个惯例【17】。研究用操作系统Plan 9和Inferno在使用文件方面更加一致它们将TCP连接表示为`/net/tcp`中的文件【18】。
@ -188,8 +188,6 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
使Unix工具如此成功的部分原因是它们使查看正在发生的事情变得非常容易
- Unix命令的输入文件通常被视为不可变的。这意味着你可以随意运行命令尝试各种命令行选项而不会损坏输入文件。
- 你可以在任何时候结束管道,将管道输出到`less`,然后查看它是否具有预期的形式。这种检查能力对调试非常有用。
- 你可以将一个流水线阶段的输出写入文件,并将该文件用作下一阶段的输入。这使你可以重新启动后面的阶段,而无需重新运行整个管道。
@ -205,26 +203,26 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
和大多数Unix工具一样运行MapReduce作业通常不会修改输入除了生成输出外没有任何副作用。输出文件以连续的方式一次性写入一旦写入文件不会修改任何现有的文件部分
虽然Unix工具使用`stdin`和`stdout`作为输入和输出但MapReduce作业在分布式文件系统上读写文件。在Hadoop的Map-Reduce实现中该文件系统被称为**HDFSHadoop分布式文件系统**一个Google文件系统GFS的开源实现【19】。
虽然Unix工具使用`stdin`和`stdout`作为输入和输出但MapReduce作业在分布式文件系统上读写文件。在Hadoop的MapReduce实现中该文件系统被称为**HDFSHadoop分布式文件系统**一个Google文件系统GFS的开源实现【19】。
除HDFS外还有各种其他分布式文件系统如GlusterFS和Quantcast File SystemQFS【20】。诸如Amazon S3Azure Blob存储和OpenStack Swift 【21】等对象存储服务在很多方面都是相似的[^iv]。在本章中我们将主要使用HDFS作为示例但是这些原则适用于任何分布式文件系统。
除HDFS外还有各种其他分布式文件系统如GlusterFS和Quantcast File SystemQFS【20】。诸如Amazon S3Azure Blob存储和OpenStack Swift【21】等对象存储服务在很多方面都是相似的[^iv]。在本章中我们将主要使用HDFS作为示例但是这些原则适用于任何分布式文件系统。
[^iv]: 一个不同之处在于对于HDFS可以将计算任务安排在存储特定文件副本的计算机上运行而对象存储通常将存储和计算分开。如果网络带宽是一个瓶颈从本地磁盘读取有性能优势。但是请注意如果使用纠删码则会丢失局部性因为来自多台机器的数据必须进行合并以重建原始文件【20】。
[^iv]: 一个不同之处在于对于HDFS可以将计算任务安排在存储特定文件副本的计算机上运行而对象存储通常将存储和计算分开。如果网络带宽是一个瓶颈从本地磁盘读取有性能优势。但是请注意如果使用纠删码Erasure Coding则会丢失局部性因为来自多台机器的数据必须进行合并以重建原始文件【20】。
与网络连接存储NAS和存储区域网络SAN架构的共享磁盘方法相比HDFS基于**无共享**原则(参见[第二部分前言](part-ii.md))。共享磁盘存储由集中式存储设备实现,通常使用定制硬件和专用网络基础设施(如光纤通道)。而另一方面,无共享方法不需要特殊的硬件,只需要通过传统数据中心网络连接的计算机。
与网络连接存储NAS和存储区域网络SAN架构的共享磁盘方法相比HDFS基于**无共享**原则(参见[第二部分](part-ii.md)的介绍)。共享磁盘存储由集中式存储设备实现,通常使用定制硬件和专用网络基础设施(如光纤通道)。而另一方面,无共享方法不需要特殊的硬件,只需要通过传统数据中心网络连接的计算机。
HDFS包含在每台机器上运行的守护进程,对外暴露网络服务,允许其他节点访问存储在该机器上的文件(假设数据中心中的每台通用计算机都挂载着一些磁盘)。名为**NameNode**的中央服务器会跟踪哪个文件块存储在哪台机器上。因此HDFS在概念上创建了一个大型文件系统可以使用所有运行有守护进程的机器的磁盘。
HDFS在每台机器上运行了一个守护进程,它对外暴露网络服务,允许其他节点访问存储在该机器上的文件(假设数据中心中的每台通用计算机都挂载着一些磁盘)。名为**NameNode**的中央服务器会跟踪哪个文件块存储在哪台机器上。因此HDFS在概念上创建了一个大型文件系统可以使用所有运行有守护进程的机器的磁盘。
为了容忍机器和磁盘故障,文件块被复制到多台机器上。复制可能意味着多个机器上的相同数据的多个副本,如[第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是一个编程框架你可以使用它编写代码来处理HDFS等分布式文件系统中的大型数据集。理解它的最简单方法是参考“[简单日志分析](#简单日志分析)”中的Web服务器日志分析示例。MapReduce中的数据处理模式与此示例非常相似
1. 读取一组输入文件,并将其分解成**记录records**。在Web服务器日志示例中每条记录都是日志中的一行即`\n`是记录分隔符)。
2. 调用Mapper函数从每条输入记录中提取一对键值。在前面的例子中Mapper函数是`awk '{print $7}'`它提取URL`$7`)作为,并将值留空。
2. 调用Mapper函数从每条输入记录中提取一对键值。在前面的例子中Mapper函数是`awk '{print $7}'`它提取URL`$7`)作为键,并将值留空。
3. 按键排序所有的键值对。在日志的例子中,这由第一个`sort`命令完成。
4. 调用Reducer函数遍历排序后的键值对。如果同一个键出现多次排序使它们在列表中相邻所以很容易组合这些值而不必在内存中保留很多状态。在前面的例子中Reducer是由`uniq -c`命令实现的,该命令使用相同的键来统计相邻记录的数量。
@ -237,7 +235,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
Mapper会在每条输入记录上调用一次其工作是从输入记录中提取键值。对于每个输入它可以生成任意数量的键值对包括None。它不会保留从一个输入记录到下一个记录的任何状态因此每个记录都是独立处理的。
***Reducer***
MapReduce框架拉取由Mapper生成的键值对收集属于同一个键的所有值使用在这组值列表上迭代调用Reducer。 Reducer可以产生输出记录例如相同URL的出现次数
MapReduce框架拉取由Mapper生成的键值对收集属于同一个键的所有值并在这组值上迭代调用Reducer。 Reducer可以产生输出记录例如相同URL的出现次数
在Web服务器日志的例子中我们在第5步中有第二个`sort`命令它按请求数对URL进行排序。在MapReduce中如果你需要第二个排序阶段则可以通过编写第二个MapReduce作业并将第一个作业的输出用作第二个作业的输入来实现它。这样看来Mapper的作用是将数据放入一个适合排序的表单中并且Reducer的作用是处理已排序的数据。
@ -247,7 +245,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
在分布式计算中可以使用标准的Unix工具作为Mapper和Reducer【25】但更常见的是它们被实现为传统编程语言的函数。在Hadoop MapReduce中Mapper和Reducer都是实现特定接口的Java类。在MongoDB和CouchDB中Mapper和Reducer都是JavaScript函数参阅“[MapReduce查询](ch2.md#MapReduce查询)”)。
[图10-1]()显示了Hadoop MapReduce作业中的数据流。其并行化基于分区参见[第6章](ch6.md)作业的输入通常是HDFS中的一个目录输入目录中的每个文件或文件块都被认为是一个单独的分区可以单独处理map任务[图10-1](img/fig10-1.png)中的m1m2和m3标记
[图10-1](img/fig10-1.png)显示了Hadoop MapReduce作业中的数据流。其并行化基于分区参见[第6章](ch6.md)作业的输入通常是HDFS中的一个目录输入目录中的每个文件或文件块都被认为是一个单独的分区可以单独处理map任务[图10-1](img/fig10-1.png)中的m1m2和m3标记
每个输入文件的大小通常是数百兆字节。 MapReduce调度器图中未显示试图在其中一台存储输入文件副本的机器上运行每个Mapper只要该机器有足够的备用RAM和CPU资源来运行Mapper任务【26】。这个原则被称为**将计算放在数据附近**【27】它节省了通过网络复制输入文件的开销减少网络负载并增加局部性。
@ -257,7 +255,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
在大多数情况下应该在Mapper任务中运行的应用代码在将要运行它的机器上还不存在所以MapReduce框架首先将代码例如Java程序中的JAR文件复制到适当的机器。然后启动Map任务并开始读取输入文件一次将一条记录传入Mapper回调函数。Mapper的输出由键值对组成。
计算的Reduce端也被分区。虽然Map任务的数量由输入文件块的数量决定但Reducer的任务的数量是由作业作者配置的它可以不同于Map任务的数量。为了确保具有相同键的所有键值对最终落在相同的Reducer处框架使用键的散列值来确定哪个Reduce任务应该接收到特定的键值对参见“[按键散列分区](ch6.md#按键散列分区)”))。
计算的Reduce端也被分区。虽然Map任务的数量由输入文件块的数量决定但Reducer的任务的数量是由作业作者配置的它可以不同于Map任务的数量。为了确保具有相同键的所有键值对最终落在相同的Reducer处框架使用键的散列值来确定哪个Reduce任务应该接收到特定的键值对参见“[根据键的散列分区](ch6.md#根据键的散列分区)”))。
键值对必须进行排序但数据集可能太大无法在单台机器上使用常规排序算法进行排序。相反分类是分阶段进行的。首先每个Map任务都按照Reducer对输出进行分区。每个分区都被写入Mapper程序的本地磁盘使用的技术与我们在“[SSTables与LSM树](ch3.md#SSTables与LSM树)”中讨论的类似。
@ -271,7 +269,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
单个MapReduce作业可以解决的问题范围很有限。以日志分析为例单个MapReduce作业可以确定每个URL的页面浏览次数但无法确定最常见的URL因为这需要第二轮排序。
因此将MapReduce作业链接成为**工作流workflow**中是极为常见的,例如,一个作业的输出成为下一个作业的输入。 Hadoop Map-Reduce框架对工作流没有特殊支持所以这个链是通过目录名隐式实现的第一个作业必须将其输出配置为HDFS中的指定目录第二个作业必须将其输入配置为从同一个目录。从MapReduce框架的角度来看是两个独立的作业。
因此将MapReduce作业链接成为**工作流workflow**中是极为常见的,例如,一个作业的输出成为下一个作业的输入。 Hadoop MapReduce框架对工作流没有特殊支持所以这个链是通过目录名隐式实现的第一个作业必须将其输出配置为HDFS中的指定目录第二个作业必须将其输入配置为从同一个目录。从MapReduce框架的角度来看这是两个独立的作业。
因此被链接的MapReduce作业并没有那么像Unix命令管道它直接将一个进程的输出作为另一个进程的输入仅用一个很小的内存缓冲区。它更像是一系列命令其中每个命令的输出写入临时文件下一个命令从临时文件中读取。这种设计有利也有弊我们将在“[物化中间状态](#物化中间状态)”中讨论。
@ -283,15 +281,15 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
### Reduce侧连接与分组
我们在[第2章](ch2.md)中讨论了数据模型和查询语言的接,但是我们还没有深入探讨连接是如何实现的。现在是我们再次捡起这条线索的时候了。
我们在[第2章](ch2.md)中讨论了数据模型和查询语言的接,但是我们还没有深入探讨连接是如何实现的。现在是我们再次捡起这条线索的时候了。
在许多数据集中,一条记录与另一条记录存在关联是很常见的:关系模型中的**外键**,文档模型中的**文档引用**或图模型中的**边**。当你需要同时访问这一关联的两侧(持有引用的记录与被引用的记录)时,连接就是必须的。正如[第2章](ch2.md)所讨论的,非规范化可以减少对连接的需求,但通常无法将其完全移除[^v]。
[^v]: 我们在本书中讨论的连接通常是等值连接即最常见的连接类型其中记录与其他记录在特定字段例如ID中具有**相同值**相关联。有些数据库支持更通用的连接类型,例如使用小于运算符而不是等号运算符,但是我们没有地方来讲这些东西。
[^v]: 我们在本书中讨论的连接通常是等值连接,即最常见的连接类型,其中记录通过与其他记录在特定字段例如ID中具有**相同值**相关联。有些数据库支持更通用的连接类型,例如使用小于运算符而不是等号运算符,但是我们没有地方来讲这些东西。
在数据库中,如果执行只涉及少量记录的查询,数据库通常会使用**索引**来快速定位感兴趣的记录(参阅[第3章](ch3.md)。如果查询涉及到连接则可能涉及到查找多个索引。然而MapReduce没有索引的概念 —— 至少在通常意义上没有。
当MapReduce作业被赋予一组文件作为输入时它读取所有这些文件的全部内容数据库会将这种操作称为**全表扫描**。如果你只想读取少量的记录,则全表扫描与索引查询相比,代价非常高昂。但是在分析查询中(参阅“[事务处理分析?](ch3.md#事务处理还是分析?)”),通常需要计算大量记录的聚合。在这种情况下,特别是如果能在多台机器上并行处理时,扫描整个输入可能是相当合理的事情。
当MapReduce作业被赋予一组文件作为输入时它读取所有这些文件的全部内容数据库会将这种操作称为**全表扫描**。如果你只想读取少量的记录,则全表扫描与索引查询相比,代价非常高昂。但是在分析查询中(参阅“[事务处理还是分析?](ch3.md#事务处理还是分析?)”),通常需要计算大量记录的聚合。在这种情况下,特别是如果能在多台机器上并行处理时,扫描整个输入可能是相当合理的事情。
当我们在批处理的语境中讨论连接时,我们指的是在数据集中解析某种关联的全量存在。 例如我们假设一个作业是同时处理所有用户的数据,而非仅仅是为某个特定用户查找数据(而这能通过索引更高效地完成)。
@ -303,7 +301,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
**图10-2 用户行为日志与用户档案的连接**
分析任务可能需要将用户活动与用户档相关联例如如果档案包含用户的年龄或出生日期系统就可以确定哪些页面更受哪些年龄段的用户欢迎。然而活动事件仅包含用户ID而没有包含完整的用户档案信息。在每个活动事件中嵌入这些档案信息很可能会非常浪费。因此活动事件需要与用户档案数据库相连接。
分析任务可能需要将用户活动与用户档案信息相关联例如如果档案包含用户的年龄或出生日期系统就可以确定哪些页面更受哪些年龄段的用户欢迎。然而活动事件仅包含用户ID而没有包含完整的用户档案信息。在每个活动事件中嵌入这些档案信息很可能会非常浪费。因此活动事件需要与用户档案数据库相连接。
实现这一连接的最简单方法是逐个遍历活动事件并为每个遇到的用户ID查询用户数据库在远程服务器上。这是可能的但是它的性能可能会非常差处理吞吐量将受限于受数据库服务器的往返时间本地缓存的有效性很大程度上取决于数据的分布并行运行大量查询可能会轻易压垮数据库【35】。
@ -329,11 +327,11 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
在排序合并连接中Mapper和排序过程确保了所有对特定用户ID执行连接操作的必须数据都被放在同一个地方单次调用Reducer的地方。预先排好了所有需要的数据Reducer可以是相当简单的单线程代码能够以高吞吐量和与低内存开销扫过这些记录。
这种架构可以看做Mapper将“消息”发送给Reducer。当一个Mapper发出一个键值对时这个键的作用就像值应该传递到的目标地址。即使键只是一个任意的字符串不是像IP地址和端口号那样的实际的网络地址它表现的就像一个地址所有具有相同键的键值对将被传递到相同的目标一次Reduce的调用
这种架构可以看做Mapper将“消息”发送给Reducer。当一个Mapper发出一个键值对时这个键的作用就像值应该传递到的目标地址。即使键只是一个任意的字符串不是像IP地址和端口号那样的实际的网络地址它表现的就像一个地址所有具有相同键的键值对将被传递到相同的目标一次Reducer的调用)。
使用MapReduce编程模型能将计算的物理网络通信层面从正确的机器获取数据从应用逻辑中剥离出来获取数据后执行处理。这种分离与数据库的典型用法形成了鲜明对比从数据库中获取数据的请求经常出现在应用代码内部【36】。由于MapReduce能够处理所有的网络通信因此它也避免了应用代码去担心部分故障例如另一个节点的崩溃MapReduce在不影响应用逻辑的情况下能透明地重试失败的任务。
使用MapReduce编程模型能将计算的物理网络通信层面从正确的机器获取数据从应用逻辑中剥离出来获取数据后执行处理。这种分离与数据库的典型用法形成了鲜明对比从数据库中获取数据的请求经常出现在应用代码内部【36】。由于MapReduce处理所有的网络通信,因此它也避免了应用代码去担心部分故障例如另一个节点的崩溃MapReduce在不影响应用逻辑的情况下能透明地重试失败的任务。
### GROUP BY
#### 分组
除了连接之外“把相关数据放在一起”的另一种常见模式是按某个键对记录分组如SQL中的GROUP BY子句。所有带有相同键的记录构成一个组而下一步往往是在每个组内进行某种聚合操作例如
@ -343,21 +341,21 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
使用MapReduce实现这种分组操作的最简单方法是设置Mapper以便它们生成的键值对使用所需的分组键。然后分区和排序过程将所有具有相同分区键的记录导向同一个Reducer。因此在MapReduce之上实现分组和连接看上去非常相似。
分组的另一个常见用途是整理特定用户会话的所有活动事件,以找出用户进行的一系列操作(称为**会话化sessionization**【37】。例如可以使用这种分析来确定显示新版网站的用户是否比那些显示旧版本A/B测试的用户更有购买欲,或者计算某个营销活动是否值得。
分组的另一个常见用途是整理特定用户会话的所有活动事件,以找出用户进行的一系列操作(称为**会话化sessionization**【37】。例如可以使用这种分析来确定显示新版网站的用户是否比那些显示旧版本的用户更有购买欲A/B测试,或者计算某个营销活动是否值得。
如果你有多个Web服务器处理用户请求则特定用户的活动事件很可能分散在各个不同的服务器的日志文件中。你可以通过使用会话cookie用户ID或类似的标识符作为分组键以将特定用户的所有活动事件放在一起来实现会话化与此同时不同用户的事件仍然散在不同的分区中。
如果你有多个Web服务器处理用户请求则特定用户的活动事件很可能分散在各个不同的服务器的日志文件中。你可以通过使用会话cookie用户ID或类似的标识符作为分组键以将特定用户的所有活动事件放在一起来实现会话化与此同时不同用户的事件仍然散在不同的分区中。
#### 处理偏斜
如果存在与单个键关联的大量数据,则“将具有相同键的所有记录放到相同的位置”这种模式就被破坏了。例如在社交网络中,大多数用户可能会与几百人有连接,但少数名人可能有数百万的追随者。这种不成比例的活动数据库记录被称为**关键对象linchpin object**【38】或**热键hot key**。
在单个Reducer中收集与某个名流相关的所有活动(例如他们发布内容的回复)可能导致严重的倾斜(也称为**热点hot spot**)—— 也就是说一个Reducer必须比其他Reducer处理更多的记录参见“[负载偏斜与热点消除](ch6.md#负载偏斜与热点消除)“。由于MapReduce作业只有在所有Mapper和Reducer都完成时才完成所有后续作业必须等待最慢的Reducer才能启动。
在单个Reducer中收集与某个名人相关的所有活动(例如他们发布内容的回复)可能导致严重的**偏斜**(也称为**热点hot spot**)—— 也就是说一个Reducer必须比其他Reducer处理更多的记录参见“[负载偏斜与热点消除](ch6.md#负载偏斜与热点消除)“。由于MapReduce作业只有在所有Mapper和Reducer都完成时才完成所有后续作业必须等待最慢的Reducer才能启动。
如果连接的输入存在热可以使用一些算法进行补偿。例如Pig中的**斜连接skewed join**方法首先运行一个抽样作业来确定哪些键是热键【39】。连接实际执行时Mapper会将热键的关联记录**随机**相对于传统MapReduce基于键散列的确定性方法发送到几个Reducer之一。对于另外一侧的连接输入与热键相关的记录需要被复制到所有处理该键的Reducer上【40】。
如果连接的输入存在热键可以使用一些算法进行补偿。例如Pig中的**斜连接skewed join**方法首先运行一个抽样作业Sampling Job来确定哪些键是热键【39】。连接实际执行时Mapper会将热键的关联记录**随机**相对于传统MapReduce基于键散列的确定性方法发送到几个Reducer之一。对于另外一侧的连接输入与热键相关的记录需要被复制到**所有**处理该键的Reducer上【40】。
这种技术将处理热键的工作分散到多个Reducer上这样可以使其更好地并行化代价是需要将连接另一侧的输入记录复制到多个Reducer上。 Crunch中的**分片连接sharded join**方法与之类似,但需要显式指定热键而不是使用样作业。这种技术也非常类似于我们在“[负载偏斜与热点消除](ch6.md#负载偏斜与热点消除)”中讨论的技术,使用随机化来缓解分区数据库中的热点。
这种技术将处理热键的工作分散到多个Reducer上这样可以使其更好地并行化代价是需要将连接另一侧的输入记录复制到多个Reducer上。 Crunch中的**分片连接sharded join**方法与之类似,但需要显式指定热键而不是使用样作业。这种技术也非常类似于我们在“[负载偏斜与热点消除](ch6.md#负载偏斜与热点消除)”中讨论的技术,使用随机化来缓解分区数据库中的热点。
Hive的偏斜连接优化采取了另一种方法。它需要在表格元数据中显式指定热键并将与这些键相关的记录单独存放与其它文件分开。当在该表上执行连接时对于热键它会使用Map端连接参阅[下一节](#Map端连接))。
Hive的偏斜连接优化采取了另一种方法。它需要在表格元数据中显式指定热键并将与这些键相关的记录单独存放与其它文件分开。当在该表上执行连接时对于热键它会使用Map端连接参阅下一节
当按照热键进行分组并聚合时可以将分组分两个阶段进行。第一个MapReduce阶段将记录发送到随机Reducer以便每个Reducer只对热键的子集执行分组为每个键输出一个更紧凑的中间聚合结果。然后第二个MapReduce作业将所有来自第一阶段Reducer的中间聚合结果合并为每个键一个值。
@ -365,17 +363,17 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
### Map侧连接
上一节描述的连接算法在Reducer中执行实际的连接逻辑因此被称为Reduce连接。Mapper扮演着预处理输入数据的角色从每个输入记录中提取键值将键值对分配给Reducer分区并按键排序。
上一节描述的连接算法在Reducer中执行实际的连接逻辑因此被称为Reduce连接。Mapper扮演着预处理输入数据的角色从每个输入记录中提取键值将键值对分配给Reducer分区并按键排序。
Reduce方法的优点是不需要对输入数据做任何假设无论其属性和结构如何Mapper都可以对其预处理以备连接。然而不利的一面是排序复制至Reducer以及合并Reducer输入所有这些操作可能开销巨大。当数据通过MapReduce 阶段时数据可能需要落盘好几次取决于可用的内存缓冲区【37】。
Reduce方法的优点是不需要对输入数据做任何假设无论其属性和结构如何Mapper都可以对其预处理以备连接。然而不利的一面是排序复制至Reducer以及合并Reducer输入所有这些操作可能开销巨大。当数据通过MapReduce 阶段时数据可能需要落盘好几次取决于可用的内存缓冲区【37】。
另一方面,如果你**能**对输入数据作出某些假设则通过使用所谓的Map端连接来加快连接速度是可行的。这种方法使用了一个阉掉Reduce与排序的MapReduce作业每个Mapper只是简单地从分布式文件系统中读取一个输入文件块然后将输出文件写入文件系统仅此而已。
另一方面,如果你**能**对输入数据作出某些假设则通过使用所谓的Map侧连接来加快连接速度是可行的。这种方法使用了一个裁减掉Reducer与排序的MapReduce作业每个Mapper只是简单地从分布式文件系统中读取一个输入文件块然后将输出文件写入文件系统仅此而已。
#### 广播散列连接
适用于执行Map端连接的最简单场景是大数据集与小数据集连接的情况。要点在于小数据集需要足够小以便可以将其全部加载到每个Mapper的内存中。
例如,假设在[图10-2](img/fig10-2.png)的情况下用户数据库小到足以放进内存中。在这种情况下当Mapper启动时它可以首先将用户数据库从分布式文件系统读取到内存中的散列中。完成此操作后Map程序可以扫描用户活动事件并简单地在散列表中查找每个事件的用户ID[^vi]。
例如,假设在[图10-2](img/fig10-2.png)的情况下用户数据库小到足以放进内存中。在这种情况下当Mapper启动时它可以首先将用户数据库从分布式文件系统读取到内存中的散列表中。完成此操作后Mapper可以扫描用户活动事件并简单地在散列表中查找每个事件的用户ID[^vi]。
[^vi]: 这个例子假定散列表中的每个键只有一个条目这对用户数据库用户ID唯一标识一个用户可能是正确的。通常哈希表可能需要包含具有相同键的多个条目而连接运算符将对每个键输出所有的匹配。
@ -383,29 +381,29 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
这种简单有效的算法被称为**广播散列连接broadcast hash join****广播**一词反映了这样一个事实每个连接较大输入端分区的Mapper都会将较小输入端数据集整个读入内存中所以较小输入实际上“广播”到较大数据的所有分区上**散列**一词反映了它使用一个散列表。 Pig名为“**复制链接replicated join**”Hive“**MapJoin**”Cascading和Crunch支持这种连接。它也被诸如Impala的数据仓库查询引擎使用【41】。
除了将连接较小输入加载到内存散列表中另一种方法是将较小输入存储在本地磁盘上的只读索引中【42】。索引中经常使用的部分将保留在操作系统的页面缓存中因而这种方法可以提供与内存散列表几乎一样快的随机查找性能但实际上并不需要数据集能放入内存中。
除了将较小的连接输入加载到内存散列表中另一种方法是将较小输入存储在本地磁盘上的只读索引中【42】。索引中经常使用的部分将保留在操作系统的页面缓存中因而这种方法可以提供与内存散列表几乎一样快的随机查找性能但实际上并不需要数据集能放入内存中。
#### 分区散列连接
如果Map连接的输入以相同的方式进行分区,则散列连接方法可以独立应用于每个分区。在[图10-2](img/fig10-2.png)的情况中你可以根据用户ID的最后一位十进制数字来对活动事件和用户数据库进行分区因此连接两侧各有10个分区。例如Mapper3首先将所有具有以3结尾的ID的用户加载到散列表中然后扫描ID为3的每个用户的所有活动事件。
如果Map连接的输入以相同的方式进行分区,则散列连接方法可以独立应用于每个分区。在[图10-2](img/fig10-2.png)的情况中你可以根据用户ID的最后一位十进制数字来对活动事件和用户数据库进行分区因此连接两侧各有10个分区。例如Mapper3首先将所有具有以3结尾的ID的用户加载到散列表中然后扫描ID为3的每个用户的所有活动事件。
如果分区正确无误可以确定的是所有你可能需要连接的记录都落在同一个编号的分区中。因此每个Mapper只需要从输入两端各读取一个分区就足够了。好处是每个Mapper都可以在内存散列表中少放点数据。
这种方法只有当连接两端输入有相同的分区数且两侧的记录都是使用相同的键与相同的哈希函数做分区时才适用。如果输入是由之前执行过这种分组的MapReduce作业生成的那么这可能是一个合理的假设。
分区散列连接在Hive中称为**Map桶连接bucketed map joins【37】**。
分区散列连接在Hive中称为**Map桶连接bucketed map joins【37】**。
#### Map侧合并连接
如果输入数据集不仅以相同的方式进行分区,而且还基于相同的键进行**排序**则可适用另一种Map端联接的变体。在这种情况下输入是否小到能放入内存并不重要因为这时候Mapper同样可以执行归并操作通常由Reducer执行的归并操作按键递增的顺序依次读取两个输入文件将具有相同键的记录配对。
如果输入数据集不仅以相同的方式进行分区,而且还基于相同的键进行**排序**则可适用另一种Map侧连接的变体。在这种情况下输入是否小到能放入内存并不重要因为这时候Mapper同样可以执行归并操作通常由Reducer执行的归并操作按键递增的顺序依次读取两个输入文件将具有相同键的记录配对。
如果能进行Map合并连接这通常意味着前一个MapReduce作业可能一开始就已经把输入数据做了分区并进行了排序。原则上这个连接就可以在前一个作业的Reduce阶段进行。但使用独立的仅Map作业有时也是合适的例如分好区且排好序的中间数据集可能还会用于其他目的。
如果能进行Map合并连接这通常意味着前一个MapReduce作业可能一开始就已经把输入数据做了分区并进行了排序。原则上这个连接就可以在前一个作业的Reduce阶段进行。但使用独立的仅Map作业有时也是合适的例如分好区且排好序的中间数据集可能还会用于其他目的。
#### MapReduce工作流与Map侧连接
当下游作业使用MapReduce连接的输出时选择Map端连接或Reduce端连接会影响输出的结构。Reduce端连接的输出是按照**连接键**进行分区和排序的而Map端连接的输出则按照与较大输入相同的方式进行分区和排序因为无论是使用分区连接还是广播连接连接较大输入端的每个文件块都会启动一个Map任务
当下游作业使用MapReduce连接的输出时选择Map侧连接或Reduce侧连接会影响输出的结构。Reduce侧连接的输出是按照**连接键**进行分区和排序的而Map端连接的输出则按照与较大输入相同的方式进行分区和排序因为无论是使用分区连接还是广播连接连接较大输入端的每个文件块都会启动一个Map任务
如前所述Map连接也对输入数据集的大小,有序性和分区方式做出了更多假设。在优化连接策略时,了解分布式文件系统中数据集的物理布局变得非常重要:仅仅知道编码格式和数据存储目录的名称是不够的;你还必须知道数据是按哪些键做的分区和排序,以及分区的数量。
如前所述Map连接也对输入数据集的大小,有序性和分区方式做出了更多假设。在优化连接策略时,了解分布式文件系统中数据集的物理布局变得非常重要:仅仅知道编码格式和数据存储目录的名称是不够的;你还必须知道数据是按哪些键做的分区和排序,以及分区的数量。
在Hadoop生态系统中这种关于数据集分区的元数据通常在HCatalog和Hive Metastore中维护【37】。
@ -415,17 +413,17 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
我们已经说了很多用于实现MapReduce工作流的算法但却忽略了一个重要的问题这些处理完成之后的最终结果是什么我们最开始为什么要跑这些作业
在数据库查询的场景中我们将事务处理OLTP与分析两种目的区分开来参阅“[事务处理或分析?](ch3.md#事务处理或分析?)”。我们看到OLTP查询通常根据键查找少量记录使用索引并将其呈现给用户比如在网页上。另一方面分析查询通常会扫描大量记录执行分组与聚合输出通常有着报告的形式显示某个指标随时间变化的图表或按照某种排位取前10项或一些数字细化为子类。这种报告的消费者通常是需要做出商业决策的分析师或经理。
在数据库查询的场景中我们将事务处理OLTP与分析两种目的区分开来参阅“[事务处理还是分析?](ch3.md#事务处理还是分析?)”。我们看到OLTP查询通常根据键查找少量记录使用索引并将其呈现给用户比如在网页上。另一方面分析查询通常会扫描大量记录执行分组与聚合输出通常有着报告的形式显示某个指标随时间变化的图表或按照某种排位取前10项一些数字细化为子类。这种报告的消费者通常是需要做出商业决策的分析师或经理。
批处理放哪里合适它不属于事务处理也不是分析。它和分析比较接近因为批处理通常会扫过输入数据集的绝大部分。然而MapReduce作业工作流与用于分析目的的SQL查询是不同的参阅“[Hadoop与分布式数据库的对比](#Hadoop与分布式数据库的对比)”)。批处理过程的输出通常不是报表,而是一些其他类型的结构。
#### 建立搜索索引
Google最初使用MapReduce是为其搜索引擎建立索引用了由5到10个MapReduce作业组成的工作流实现【1】。虽然Google后来也不仅仅是为这个目的而使用MapReduce 【43】但如果从构建搜索索引的角度来看更能帮助理解MapReduce。 直至今日Hadoop MapReduce仍然是为Lucene/Solr构建索引的好方法【44】
Google最初使用MapReduce是为其搜索引擎建立索引其实现为由5到10个MapReduce作业组成的工作流【1】。虽然Google后来也不仅仅是为这个目的而使用MapReduce 【43】但如果从构建搜索索引的角度来看更能帮助理解MapReduce。 直至今日Hadoop MapReduce仍然是为Lucene/Solr构建索引的好方法【44】
我们在“[全文搜索和模糊索引](ch3.md#全文搜索和模糊索引)”中简要地了解了Lucene这样的全文搜索索引是如何工作的它是一个文件关键词字典你可以在其中高效地查找特定关键字并找到包含该关键字的所有文档ID列表文章列表。这是一种非常简化的看法 —— 实际上,搜索索引需要各种额外数据,以便根据相关性对搜索结果进行排名,纠正拼写错误,解析同义词等等 —— 但这个原则是成立的。
如果需要对一组固定文档执行全文搜索则批处理是一种构建索引的高效方法Mapper根据需要对文档集合进行分区每个Reducer构建该分区的索引并将索引文件写入分布式文件系统。构建这样的文档分区索引参阅“[分区和二级索引](ch6.md#分区和二级索引)”)并行处理效果拔群。
如果需要对一组固定文档执行全文搜索则批处理是一种构建索引的高效方法Mapper根据需要对文档集合进行分区每个Reducer构建该分区的索引并将索引文件写入分布式文件系统。构建这样的文档分区索引参阅“[分区与次级索引](ch6.md#分区与次级索引)”)并行处理效果拔群。
由于按关键字查询搜索索引是只读操作,因而这些索引文件一旦创建就是不可变的。
@ -441,15 +439,15 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
这些数据库需要被处理用户请求的Web应用所查询而它们通常是独立于Hadoop基础设施的。那么批处理过程的输出如何回到Web应用可以查询的数据库中呢
最直接的选择可能是直接在Mapper或Reducer中使用你最爱数据库的客户端库并从批处理作业直接写入数据库服务器一次写入一条记录。它能工作假设你的防火墙规则允许从你的Hadoop环境直接访问你的生产数据库但这并不是一个好主意出于以下几个原因
最直接的选择可能是直接在Mapper或Reducer中使用你最爱数据库的客户端库并从批处理作业直接写入数据库服务器一次写入一条记录。它能工作假设你的防火墙规则允许从你的Hadoop环境直接访问你的生产数据库但这并不是一个好主意出于以下几个原因
- 正如前面在连接的上下文中讨论的那样,为每条记录发起一个网络请求,要比批处理任务的正常吞吐量慢几个数量级。即使客户端库支持批处理,性能也可能很差。
- 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树更可靠)”)。
将数据加载到Voldemort时服务器将继续用旧数据文件服务请求同时将新数据文件从分布式文件系统复制到服务器的本地磁盘。一旦复制完成服务器会自动将查询切换到新文件。如果在这个过程中出现任何问题它可以轻易回滚至旧文件因为它们仍然存在而且不可变【46】。
@ -462,10 +460,10 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
- 如果在代码中引入了一个错误,而输出错误或损坏了,则可以简单地回滚到代码的先前版本,然后重新运行该作业,输出将重新被纠正。或者,甚至更简单,你可以将旧的输出保存在不同的目录中,然后切换回原来的目录。具有读写事务的数据库没有这个属性:如果你部署了错误的代码,将错误的数据写入数据库,那么回滚代码将无法修复数据库中的数据。 (能够从错误代码中恢复的概念被称为**人类容错human fault tolerance**【50】
- 由于回滚很容易,比起在错误意味着不可挽回的伤害的环境,功能开发进展能快很多。这种**最小化不可逆性minimizing irreversibility**的原则有利于敏捷软件开发【51】。
- 如果Map或Reduce任务失败MapReduce框架将自动重新调度并在同样的输入上再次运行它。如果失败是由代码中的错误造成的那么它会不断崩溃并最终导致作业在几次尝试之后失败。但是如果故障是由于临时问题导致的那么故障就会被容忍。因为输入不可变这种自动重试是安全的而失败任务的输出会被MapReduce框架丢弃。
- 同一组文件可用作各种不同作业的输入,包括计算指标的监控作业可以评估作业的输出是否具有预期的性质(例如,将其与前一次运行的输出进行比较并测量差异) 。
- 与Unix工具类似MapReduce作业将逻辑与布线配置输入和输出目录分离这使得关注点分离可以重用代码一个团队可以实现一个专注做好一件事的作业;而其他团队可以决定何时何地运行这项作业。
- 同一组文件可用作各种不同作业的输入,包括计算指标的监控作业并且评估作业的输出是否具有预期的性质(例如,将其与前一次运行的输出进行比较并测量差异) 。
- 与Unix工具类似MapReduce作业将逻辑与布线配置输入和输出目录分离这使得关注点分离可以重用代码一个团队可以专注实现一个做好一件事的作业;而其他团队可以决定何时何地运行这项作业。
在这些领域在Unix上表现良好的设计原则似乎也适用于Hadoop但Unix和Hadoop在某些方面也有所不同。例如因为大多数Unix工具都假设输入输出是无类型文本文件所以它们必须做大量的输入解析工作本章开头的日志分析示例使用`{print $7}`来提取URL。在Hadoop上可以通过使用更结构化的文件格式消除一些低价值的语法转换比如Avro参阅“[Avro](ch4.md#Avro)”和Parquet参阅“[列存储](ch3.md#列存储)”经常使用因为它们提供了基于模式的高效编码并允许模式随时间推移而演进见第4章
在这些领域在Unix上表现良好的设计原则似乎也适用于Hadoop但Unix和Hadoop在某些方面也有所不同。例如因为大多数Unix工具都假设输入输出是无类型文本文件所以它们必须做大量的输入解析工作本章开头的日志分析示例使用`{print $7}`来提取URL。在Hadoop上可以通过使用更结构化的文件格式消除一些低价值的语法转换比如Avro参阅“[Avro](ch4.md#Avro)”和Parquet参阅“[列存储](ch3.md#列存储)”)经常使用,因为它们提供了基于模式的高效编码,并允许模式随时间推移而演进(见[第4章](ch4.md))。
### Hadoop与分布式数据库的对比
@ -483,23 +481,23 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
在纯粹主义者看来,这种仔细的建模和导入似乎是可取的,因为这意味着数据库的用户有更高质量的数据来处理。然而实践经验表明,简单地使数据快速可用 —— 即使它很古怪,难以使用,使用原始格式 —— 也通常要比事先决定理想数据模型要更有价值【54】。
这个想法与数据仓库类似(参阅“[数据仓库](ch3.md#数据仓库)”):将大型组织的各个部分的数据集中在一起是很有价值的,因为它可以跨越以前相分离的数据集进行连接。 MPP数据库所要求的谨慎模式设计拖慢了集中式数据收集速度以原始形式收集数据稍后再操心模式的设计能使数据收集速度加快有时被称为“**数据湖data lake**”或“**企业数据中心enterprise data hub**”【55】
这个想法与数据仓库类似(参阅“[数据仓库](ch3.md#数据仓库)”):将大型组织的各个部分的数据集中在一起是很有价值的,因为它可以跨越以前相分离的数据集进行连接。 MPP数据库所要求的谨慎模式设计拖慢了集中式数据收集速度以原始形式收集数据稍后再操心模式的设计能使数据收集速度加快有时被称为“**数据湖data lake**”或“**企业数据中心enterprise data hub**”【55】
不加区分的数据转储转移了解释数据的负担:数据集的生产者不再需要强制将其转化为标准格式,数据的解释成为消费者的问题(**读时模式**方法【56】参阅“[文档模型中的架构灵活性](ch2.md#文档模型中的架构灵活性)”)。如果生产者和消费者是不同优先级的不同团队,这可能是一种优势。甚至可能不存在一个理想的数据模型,对于不同目的有不同的合适视角。以原始形式简单地转储数据,可以允许多种这样的转换。这种方法被称为**寿司原则sushi principle**“原始数据更好”【57】。
不加区分的数据转储转移了解释数据的负担:数据集的生产者不再需要强制将其转化为标准格式,数据的解释成为消费者的问题(**读时模式**方法【56】参阅“[文档模型中的模式灵活性](ch2.md#文档模型中的模式灵活性)”)。如果生产者和消费者是不同优先级的不同团队,这可能是一种优势。甚至可能不存在一个理想的数据模型,对于不同目的有不同的合适视角。以原始形式简单地转储数据,可以允许多种这样的转换。这种方法被称为**寿司原则sushi principle**“原始数据更好”【57】。
因此Hadoop经常被用于实现ETL过程参阅“[数据仓库](ch3.md#数据仓库)”事务处理系统中的数据以某种原始形式转储到分布式文件系统中然后编写MapReduce作业来清理数据将其转换为关系形式并将其导入MPP数据仓库以进行分析。数据建模仍然在进行但它在一个单独的步骤中进行与数据收集相解耦。这种解耦是可行的因为分布式文件系统支持以任何格式编码的数据。
#### 处理模型多样性
#### 处理模型多样性
MPP数据库是单体的紧密集成的软件负责磁盘上的存储布局查询计划调度和执行。由于这些组件都可以针对数据库的特定需求进行调整和优化因此整个系统可以在其设计针对的查询类型上取得非常好的性能。而且SQL查询语言允许以优雅的语法表达查询而无需编写代码使业务分析师用来做商业分析的可视化工具例如Tableau能够访问
MPP数据库是单体的紧密集成的软件负责磁盘上的存储布局查询计划调度和执行。由于这些组件都可以针对数据库的特定需求进行调整和优化因此整个系统可以在其设计针对的查询类型上取得非常好的性能。而且SQL查询语言允许以优雅的语法表达查询而无需编写代码可以在业务分析师使用的可视化工具例如Tableau中访问到
另一方面并非所有类型的处理都可以合理地表达为SQL查询。例如如果要构建机器学习和推荐系统或者使用相关性排名模型的全文搜索索引或者执行图像分析则很可能需要更一般的数据处理模型。这些类型的处理通常是特别针对特定应用的例如机器学习的特征工程机器翻译的自然语言模型欺诈预测的风险评估函数因此它们不可避免地需要编写代码而不仅仅是查询。
MapReduce使工程师能够轻松地在大型数据集上运行自己的代码。如果你有HDFS和MapReduce那么你**可以**在它之上建立一个SQL查询执行引擎事实上这正是Hive项目所做的【31】。但是你也可以编写许多其他形式的批处理这些批处理不必非要用SQL查询表示。
随后人们发现MapReduce对于某些类型的处理而言局限性很大表现很差因此在Hadoop之上其他各种处理模型也被开发出来我们将在“[MapReduce之后](#后MapReduce时代)”中看到其中一些)。有两种处理模型SQL和MapReduce还不够需要更多不同的模型而且由于Hadoop平台的开放性实施一整套方法是可行的而这在单体MPP数据库的范畴内是不可能的【58】。
随后人们发现MapReduce对于某些类型的处理而言局限性很大表现很差因此在Hadoop之上其他各种处理模型也被开发出来我们将在“[MapReduce之后](#MapReduce之后)”中看到其中一些)。只有两种处理模型SQL和MapReduce还不够需要更多不同的模型而且由于Hadoop平台的开放性实施一整套方法是可行的而这在单体MPP数据库的范畴内是不可能的【58】。
至关重要的是这些不同的处理模型都可以在共享的单个机器集群上运行所有这些机器都可以访问分布式文件系统上的相同文件。在Hadoop方中,不需要将数据导入到几个不同的专用系统中进行不同类型的处理:系统足够灵活,可以支持同一个集内不同的工作负载。不需要移动数据,使得从数据中挖掘价值变得容易得多,也使采用新的处理模型容易的多。
至关重要的是这些不同的处理模型都可以在共享的单个机器集群上运行所有这些机器都可以访问分布式文件系统上的相同文件。在Hadoop方中,不需要将数据导入到几个不同的专用系统中进行不同类型的处理:系统足够灵活,可以支持同一个集内不同的工作负载。不需要移动数据,使得从数据中挖掘价值变得容易得多,也使采用新的处理模型容易的多。
Hadoop生态系统包括随机访问的OLTP数据库如HBase参阅“[SSTables和LSM树](ch3.md#SSTables和LSM树)”和MPP风格的分析型数据库如Impala 【41】。 HBase与Impala都不使用MapReduce但都使用HDFS进行存储。它们是迥异的数据访问与处理方法但是它们可以共存并被集成到同一个系统中。
@ -519,11 +517,11 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
这种架构允许非生产(低优先级)计算资源被**过量使用overcommitted**因为系统知道必要时它可以回收资源。与分离生产和非生产任务的系统相比过量使用资源可以更好地利用机器并提高效率。但由于MapReduce作业以低优先级运行它们随时都有被抢占的风险因为优先级较高的进程可能需要其资源。在高优先级进程拿走所需资源后批量作业能有效地“捡面包屑”利用剩下的任何计算资源。
在谷歌运行一个小时的MapReduce任务有大约有5的风险被终止为了给更高优先级的进程挪地方。这一概率比硬件问题机器重启或其他原因的概率高了一个数量级【59】。按照这种抢占率如果一个作业有100个任务每个任务运行10分钟那么至少有一个任务在完成之前被终止的风险大于50
在谷歌运行一个小时的MapReduce任务有大约有5的风险被终止为了给更高优先级的进程挪地方。这一概率比硬件问题机器重启或其他原因的概率高了一个数量级【59】。按照这种抢占率如果一个作业有100个任务每个任务运行10分钟那么至少有一个任务在完成之前被终止的风险大于50
这就是MapReduce被设计为容忍频繁意外任务终止的原因不是因为硬件很不可靠而是因为任意终止进程的自由有利于提高计算集群中的资源利用率。
在开源的集群调度器中,抢占的使用较少。 YARN的CapacityScheduler支持抢占以平衡不同队列的资源分配【58】但在编写本文时YARNMesos或Kubernetes不支持通用优先级抢占【60】。在任务不经常被终止的环境中MapReduce的这一设计决策就没有多少意义了。在下一节中我们将研究一些与MapReduce设计决策相异的替代方案。
在开源的集群调度器中,抢占的使用较少。 YARN的CapacityScheduler支持抢占以平衡不同队列的资源分配【58】但在编写本文时YARNMesos或Kubernetes不支持通用优先级抢占【60】。在任务不经常被终止的环境中MapReduce的这一设计决策就没有多少意义了。在下一节中我们将研究一些与MapReduce设计决策相异的替代方案。
@ -548,21 +546,21 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
但在很多情况下,你知道一个作业的输出只能用作另一个作业的输入,这些作业由同一个团队维护。在这种情况下,分布式文件系统上的文件只是简单的**中间状态intermediate state**一种将数据从一个作业传递到下一个作业的方式。在一个用于构建推荐系统的由50或100个MapReduce作业组成的复杂工作流中存在着很多这样的中间状态【29】。
将这个中间状态写入文件的过程称为**物化materialization**。 (在“[聚合:数据立方体和物化视图](ch2.md#聚合:数据立方体和物化视图)”中已经在物化视图的背景中遇到过这个术语。它意味着对某个操作的结果立即求值并写出来,而不是在请求时按需计算)
将这个中间状态写入文件的过程称为**物化materialization**。 (在“[聚合:数据立方体和物化视图](ch3.md#聚合:数据立方体和物化视图)”中已经在物化视图的背景中遇到过这个术语。它意味着对某个操作的结果立即求值并写出来,而不是在请求时按需计算)
作为对照本章开头的日志分析示例使用Unix管道将一个命令的输出与另一个命令的输入连接起来。管道并没有完全物化中间状态而是只使用一个小的内存缓冲区将输出增量地**流stream**向输入。
与Unix管道相比MapReduce完全物化中间状态的方法存在不足之处
- MapReduce作业只有在前驱作业生成其输入中的所有任务都完成时才能启动而由Unix管道连接的进程会同时启动输出一旦生成就会被消费。不同机器上的数据斜或负载不均意味着一个作业往往会有一些掉队的任务,比其他任务要慢得多才能完成。必须等待至前驱作业的所有任务完成,拖慢了整个工作流程的执行。
- MapReduce作业只有在前驱作业生成其输入中的所有任务都完成时才能启动而由Unix管道连接的进程会同时启动输出一旦生成就会被消费。不同机器上的数据斜或负载不均意味着一个作业往往会有一些掉队的任务,比其他任务要慢得多才能完成。必须等待至前驱作业的所有任务完成,拖慢了整个工作流程的执行。
- Mapper通常是多余的它们仅仅是读取刚刚由Reducer写入的同样文件为下一个阶段的分区和排序做准备。在许多情况下Mapper代码可能是前驱Reducer的一部分如果Reducer和Mapper的输出有着相同的分区与排序方式那么Reducer就可以直接串在一起而不用与Mapper相互交织。
- 将中间状态存储在分布式文件系统中意味着这些文件被复制到多个节点,这些临时数据这么搞就比较过分了。
- 将中间状态存储在分布式文件系统中意味着这些文件被复制到多个节点,这些临时数据这么搞就比较过分了。
#### 数据流引擎
了解决MapReduce的这些问题几种用于分布式批处理的新执行引擎被开发出来其中最著名的是Spark 【61,62】Tez 【63,64】和Flink 【65,66】。它们的设计方式有很多区别但有一个共同点把整个工作流作为单个作业来处理而不是把它分解为独立的子作业。
了解决MapReduce的这些问题几种用于分布式批处理的新执行引擎被开发出来其中最著名的是Spark 【61,62】Tez 【63,64】和Flink 【65,66】。它们的设计方式有很多区别但有一个共同点把整个工作流作为单个作业来处理而不是把它分解为独立的子作业。
由于它们将工作流显式建模为 数据从几个处理阶段穿过,所以这些系统被称为**数据流引擎dataflow engines**。像MapReduce一样它们在一条线上通过反复调用用户定义的函数来一次处理一条记录它们通过输入分区来并行化载荷它们通过网络将一个函数的输出复制到另一个函数的输入。
由于它们将工作流显式建模为数据从几个处理阶段穿过,所以这些系统被称为**数据流引擎dataflow engines**。像MapReduce一样它们在一条线上通过反复调用用户定义的函数来一次处理一条记录它们通过输入分区来并行化载荷它们通过网络将一个函数的输出复制到另一个函数的输入。
与MapReduce不同这些功能不需要严格扮演交织的Map与Reduce的角色而是可以以更灵活的方式进行组合。我们称这些函数为**算子operators**,数据流引擎提供了几种不同的选项来将一个算子的输出连接到另一个算子的输入:
@ -570,7 +568,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
- 另一种可能是接受多个输入,并以相同的方式进行分区,但跳过排序。当记录的分区重要但顺序无关紧要时,这省去了分区散列连接的工作,因为构建散列表还是会把顺序随机打乱。
- 对于广播散列连接,可以将一个算子的输出,发送到连接算子的所有分区。
这种类型的处理引擎是基于像Dryad 【67】和Nephele 【68】这样的研究系统与MapReduce模型相比它有几个优点
这种类型的处理引擎是基于像Dryad 【67】和Nephele 【68】这样的研究系统与MapReduce模型相比它有几个优点
- 排序等昂贵的工作只需要在实际需要的地方执行而不是默认地在每个Map和Reduce阶段之间出现。
- 没有不必要的Map任务因为Mapper所做的工作通常可以合并到前面的Reduce算子中因为Mapper不会更改数据集的分区
@ -579,7 +577,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
- 算子可以在输入就绪后立即开始执行;后续阶段无需等待前驱阶段整个完成后再开始。
- 与MapReduce为每个任务启动一个新的JVM相比现有Java虚拟机JVM进程可以重用来运行新算子从而减少启动开销。
你可以使用数据流引擎执行与MapReduce工作流同样的计算而且由于此处所述的优化通常执行速度要明显快得多。既然算子是Map和Reduce的泛化那么相同的处理代码就可以在任一执行引擎上运行PigHive或Cascading中实现的工作流可以无需修改代码可以通过修改配置简单地从MapReduce切换到Tez或Spark【64】。
你可以使用数据流引擎执行与MapReduce工作流同样的计算而且由于此处所述的优化通常执行速度要明显快得多。既然算子是Map和Reduce的泛化那么相同的处理代码就可以在任一执行引擎上运行PigHive或Cascading中实现的工作流可以无需修改代码可以通过修改配置简单地从MapReduce切换到Tez或Spark【64】。
Tez是一个相当薄的库它依赖于YARN shuffle服务来实现节点间数据的实际复制【58】而Spark和Flink则是包含了独立网络通信层调度器及用户向API的大型框架。我们将简要讨论这些高级API。
@ -589,7 +587,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
SparkFlink和Tez避免将中间状态写入HDFS因此它们采取了不同的方法来容错如果一台机器发生故障并且该机器上的中间状态丢失则它会从其他仍然可用的数据重新计算在可行的情况下是先前的中间状态要么就只能是原始输入数据通常在HDFS上
为了实现这种重新计算,框架必须跟踪一个给定的数据是如何计算的 —— 使用了哪些输入分区?应用了哪些算子? Spark使用**弹性分布式数据集RDD**的抽象来跟踪数据的谱系【61】而Flink对算子状态存档允许恢复运行在执行过程中遇到错误的算子【66】。
为了实现这种重新计算,框架必须跟踪一个给定的数据是如何计算的 —— 使用了哪些输入分区?应用了哪些算子? Spark使用**弹性分布式数据集RDDResilient Distributed Dataset**的抽象来跟踪数据的谱系【61】而Flink对算子状态存档允许恢复运行在执行过程中遇到错误的算子【66】。
在重新计算数据时,重要的是要知道计算是否是**确定性的**:也就是说,给定相同的输入数据,算子是否始终产生相同的输出?如果一些丢失的数据已经发送给下游算子,这个问题就很重要。如果算子重新启动,重新计算的数据与原有的丢失数据不一致,下游算子很难解决新旧数据之间的矛盾。对于不确定性算子来说,解决方案通常是杀死下游算子,然后再重跑新数据。
@ -609,11 +607,11 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
在“[图数据模型](ch2.md#图数据模型)”中,我们讨论了使用图来建模数据,并使用图查询语言来遍历图中的边与点。[第2章](ch2.md)的讨论集中在OLTP风格的应用场景快速执行查询来查找少量符合特定条件的顶点。
批处理上下文中的图也很有趣其目标是在整个图上执行某种离线处理或分析。这种需求经常出现在机器学习应用如推荐引擎或排序系统中。例如最着名的图形分析算法之一是PageRank 【69】它试图根据链接到某个网页的其他网页来估计该网页的流行度。它作为配方的一部分用于确定网络搜索引擎呈现结果的顺序
批处理上下文中的图也很有趣其目标是在整个图上执行某种离线处理或分析。这种需求经常出现在机器学习应用如推荐引擎或排序系统中。例如最着名的图形分析算法之一是PageRank 【69】它试图根据链接到某个网页的其他网页来估计该网页的流行度。它作为配方的一部分用于确定网络搜索引擎呈现结果的顺序
> 像SparkFlink和Tez这样的数据流引擎参见“[中间状态的物化](#中间状态的物化)”)通常将算子作为**有向无环图DAG**的一部分安排在作业中。这与图处理不一样:在数据流引擎中,**从一个算子到另一个算子的数据流**被构造成一个图,而数据本身通常由关系型元组构成。在图处理中,数据本身具有图的形式。又一个不幸的命名混乱!
> 像SparkFlink和Tez这样的数据流引擎参见“[物化中间状态](#物化中间状态)”)通常将算子作为**有向无环图DAG**的一部分安排在作业中。这与图处理不一样:在数据流引擎中,**从一个算子到另一个算子的数据流**被构造成一个图,而数据本身通常由关系型元组构成。在图处理中,数据本身具有图的形式。又一个不幸的命名混乱!
许多图算法是通过一次遍历一条边来表示的,将一个顶点与近邻的顶点连接起来,以传播一些信息,并不断重复,直到满足一些条件为止 —— 例如,直到没有更多的边要跟进,或直到一些指标收敛。我们在[图2-6](img/fig2-6.png)中看到一个例子,它通过重复跟进标明地点归属关系的边,生成了数据库中北美包含的所有地点列表(这种算法被称为**闭包传递transitive closure**)。
许多图算法是通过一次遍历一条边来表示的,将一个顶点与近邻的顶点连接起来,以传播一些信息,并不断重复,直到满足一些条件为止 —— 例如,直到没有更多的边要跟进,或直到一些指标收敛。我们在[图2-6](img/fig2-6.png)中看到一个例子,它通过重复跟进标明地点归属关系的边,生成了数据库中北美包含的所有地点列表(这种算法被称为**传递闭包transitive closure**)。
可以在分布式文件系统中存储图包含顶点和边的列表的文件但是这种“重复至完成”的想法不能用普通的MapReduce来表示因为它只扫过一趟数据。这种算法因此经常以**迭代**的风格实现:
@ -625,19 +623,19 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
#### Pregel处理模型
针对图批处理的优化 —— **批量同步并行BSP**计算模型【70】已经开始流行起来。其中Apache Giraph 【37】Spark的GraphX API和Flink的Gelly API 【71】实现了它。它也被称为**Pregel**模型因为Google的Pregel论文推广了这种处理图的方法【72】。
针对图批处理的优化 —— **批量同步并行BSPBulk Synchronous Parallel**计算模型【70】已经开始流行起来。其中Apache Giraph 【37】Spark的GraphX API和Flink的Gelly API 【71】实现了它。它也被称为**Pregel**模型因为Google的Pregel论文推广了这种处理图的方法【72】。
回想一下在MapReduce中Mapper在概念上向Reducer的特定调用“发送消息”因为框架将所有具有相同键的Mapper输出集中在一起。 Pregel背后有一个类似的想法一个顶点可以向另一个顶点“发送消息”通常这些消息是沿着图的边发送的。
在每次迭代中,为每个顶点调用一个函数,将所有发送给它的消息传递给它 —— 就像调用Reducer一样。与MapReduce的不同之处在于在Pregel模型中顶点在一次迭代到下一次迭代的过程中会记住它的状态所以这个函数只需要处理新的传入消息。如果图的某个部分没有被发送消息那里就不需要做任何工作。
这与Actor模型有些相似参阅“[分布式的Actor框架](ch4.md#分布式的Actor框架)”),除了顶点状态和顶点之间的消息具有容错性和耐久性,且通信以固定的方式进行在每次迭代中框架递送上次迭代中发送的所有消息。Actor通常没有这样的时保证。
这与Actor模型有些相似参阅“[分布式的Actor框架](ch4.md#分布式的Actor框架)”),除了顶点状态和顶点之间的消息具有容错性和持久性,且通信以固定的回合进行在每次迭代中框架递送上次迭代中发送的所有消息。Actor通常没有这样的时保证。
#### 容错
顶点只能通过消息传递进行通信而不是直接相互查询的事实有助于提高Pregel作业的性能因为消息可以成批处理且等待通信的次数也减少了。唯一的等待是在迭代之间由于Pregel模型保证所有在一轮迭代中发送的消息都在下轮迭代中送达所以在下一轮迭代开始前先前的迭代必须完全完成而所有的消息必须在网络上完成复制。
即使底层网络可能丢失重复或任意延迟消息(参阅“[不可靠的网络](ch8.md#不可靠的网络)”Pregel的实现能保证在后续迭代中消息在其目标顶点恰好处理一次。像MapReduce一样框架能从故障中透明地恢复以简化在Pregel上实现算法的编程模型。
即使底层网络可能丢失重复或任意延迟消息(参阅“[不可靠的网络](ch8.md#不可靠的网络)”Pregel的实现能保证在后续迭代中消息在其目标顶点恰好处理一次。像MapReduce一样框架能从故障中透明地恢复以简化在Pregel上实现算法的编程模型。
这种容错是通过在迭代结束时定期存档所有顶点的状态来实现的即将其全部状态写入持久化存储。如果某个节点发生故障并且其内存中的状态丢失则最简单的解决方法是将整个图计算回滚到上一个存档点然后重启计算。如果算法是确定性的且消息记录在日志中那么也可以选择性地只恢复丢失的分区就像之前讨论过的数据流引擎【72】。
@ -655,7 +653,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
### 高级API和语言
自MapReduce开始流行的这几年以来分布式批处理的执行引擎已经很成熟了。到目前为止基础设施已经足够强大能够存储和处理超过10,000台机器集上的数PB的数据。由于在这种规模下物理执行批处理的问题已经被认为或多或少解决了所以关注点已经转向其他领域改进编程模型提高处理效率扩大这些技术可以解决的问题集。
自MapReduce开始流行的这几年以来分布式批处理的执行引擎已经很成熟了。到目前为止基础设施已经足够强大能够存储和处理超过10,000台机器集上的数PB的数据。由于在这种规模下物理执行批处理的问题已经被认为或多或少解决了所以关注点已经转向其他领域改进编程模型提高处理效率扩大这些技术可以解决的问题集。
如前所述HivePigCascading和Crunch等高级语言和API变得越来越流行因为手写MapReduce作业实在是个苦力活。随着Tez的出现这些高级语言还有一个额外好处可以迁移到新的数据流执行引擎而无需重写作业代码。 Spark和Flink也有它们自己的高级数据流API通常是从FlumeJava中获取的灵感【34】。
@ -671,21 +669,21 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
连接算法的选择可以对批处理作业的性能产生巨大影响,而无需理解和记住本章中讨论的各种连接算法。如果连接是以**声明式declarative**的方式指定的,那这就这是可行的:应用只是简单地说明哪些连接是必需的,查询优化器决定如何最好地执行连接。我们以前在“[数据查询语言](ch2.md#数据查询语言)”中见过这个想法。
但MapReduce及其后继者数据流在其他方面与SQL的完全声明式查询模型有很大区别。 MapReduce是围绕着回调函数的概念建立的对于每条记录或者一组记录调用一个用户定义的函数Mapper或Reducer并且该函数可以自由地调用任意代码来决定输出什么。这种方法的优点是可以基于大量已有库的生态系统创作解析,自然语言分析,图像分析以及运行数值算法或统计算法等。
但MapReduce及其数据流后继者在其他方面与SQL的完全声明式查询模型有很大区别。 MapReduce是围绕着回调函数的概念建立的对于每条记录或者一组记录调用一个用户定义的函数Mapper或Reducer并且该函数可以自由地调用任意代码来决定输出什么。这种方法的优点是可以基于大量已有库的生态系统创作解析、自然语言分析、图像分析以及运行数值或统计算法等。
自由运行任意代码长期以来都是传统MapReduce批处理系统与MPP数据库的区别所在参见“[比较Hadoop和分布式数据库](#比较Hadoop和分布式数据库)”一节。虽然数据库具有编写用户定义函数的功能但是它们通常使用起来很麻烦而且与大多数编程语言中广泛使用的程序包管理器和依赖管理系统兼容不佳例如Java的Maven Javascript的npm以及Ruby的gems
自由运行任意代码长期以来都是传统MapReduce批处理系统与MPP数据库的区别所在参见“[Hadoop与分布式数据库的对比](#Hadoop与分布式数据库的对比)”一节。虽然数据库具有编写用户定义函数的功能但是它们通常使用起来很麻烦而且与大多数编程语言中广泛使用的程序包管理器和依赖管理系统兼容不佳例如Java的Maven、Javascript的npm以及Ruby的gems
然而数据流引擎已经发现,支持除连接之外的更多**声明式特性**还有其他的优势。例如如果一个回调函数只包含一个简单的过滤条件或者只是从一条记录中选择了一些字段那么在为每条记录调用函数时会有相当大的额外CPU开销。如果以声明方式表示这些简单的过滤和映射操作那么查询优化器可以利用面向列的存储布局参阅“[列存储](ch3.md#列存储)”),只从磁盘读取所需的列。 HiveSpark DataFrames和Impala还使用了向量化执行参阅“[内存带宽和向量处理](ch3.md#内存带宽和向量处理)”在对CPU缓存友好的内部循环中迭代数据避免函数调用。Spark生成JVM字节码【79】Impala使用LLVM为这些内部循环生成本机代码【41】。
然而数据流引擎已经发现,支持除连接之外的更多**声明式特性**还有其他的优势。例如如果一个回调函数只包含一个简单的过滤条件或者只是从一条记录中选择了一些字段那么在为每条记录调用函数时会有相当大的额外CPU开销。如果以声明方式表示这些简单的过滤和映射操作那么查询优化器可以利用面向列的存储布局参阅“[列存储](ch3.md#列存储)”),只从磁盘读取所需的列。 HiveSpark DataFrames和Impala还使用了向量化执行参阅“[内存带宽和向量处理](ch3.md#内存带宽和向量处理)”在对CPU缓存友好的内部循环中迭代数据避免函数调用。Spark生成JVM字节码【79】Impala使用LLVM为这些内部循环生成本机代码【41】。
通过在高级API中引入声明式的部分并使查询优化器可以在执行期间利用这些来做优化批处理框架看起来越来越像MPP数据库了并且能实现可与之媲美的性能。同时通过拥有运行任意代码和以任意格式读取数据的可伸缩性,它们保持了灵活性的优势。
通过在高级API中引入声明式的部分并使查询优化器可以在执行期间利用这些来做优化批处理框架看起来越来越像MPP数据库了并且能实现可与之媲美的性能。同时通过拥有运行任意代码和以任意格式读取数据的可扩展性,它们保持了灵活性的优势。
#### 专业化的不同领域
尽管能够运行任意代码的可伸缩性是很有用的,但是也有很多常见的例子,不断重复着标准的处理模式。因而这些模式值得拥有自己的可重用通用构建模块实现传统上MPP数据库满足了商业智能分析和业务报表的需求但这只是许多使用批处理的领域之一。
尽管能够运行任意代码的可扩展性是很有用的,但是也有很多常见的例子,不断重复着标准的处理模式。因而这些模式值得拥有自己的可重用通用构建模块实现传统上MPP数据库满足了商业智能分析和业务报表的需求但这只是许多使用批处理的领域之一。
另一个越来越重要的领域是统计和数值算法它们是机器学习应用所需要的例如分类器和推荐系统。可重用的实现正在出现例如Mahout在MapReduceSpark和Flink之上实现了用于机器学习的各种算法而MADlib在关系型MPP数据库Apache HAWQ中实现了类似的功能【54】。
另一个越来越重要的领域是统计和数值算法它们是机器学习应用所需要的例如分类器和推荐系统。可重用的实现正在出现例如Mahout在MapReduceSpark和Flink之上实现了用于机器学习的各种算法而MADlib在关系型MPP数据库Apache HAWQ中实现了类似的功能【54】。
空间算法也是有用的,例如**最近邻搜索k-nearest neghbors, kNN**【80】它在一些多维空间中搜索与给定项最近的项目 —— 这是一种相似性搜索。近似搜索对于基因组分析算法也很重要它们需要找到相似但不相同的字符串【81】。
空间算法也是有用的,例如**k近邻搜索k-nearest neighbors, kNN**【80】它在一些多维空间中搜索与给定项最近的项目 —— 这是一种相似性搜索。近似搜索对于基因组分析算法也很重要它们需要找到相似但不相同的字符串【81】。
批处理引擎正被用于分布式执行日益广泛的各领域算法。随着批处理系统获得各种内置功能以及高级声明式算子且随着MPP数据库变得更加灵活和易于编程两者开始看起来相似了最终它们都只是存储和处理数据的系统。
@ -693,7 +691,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
## 本章小结
在本章中我们探索了批处理的主题。我们首先看到了诸如awkgrep和sort之类的Unix工具然后我们看到了这些工具的设计理念是如何应用到MapReduce和更近的数据流引擎中的。一些设计原则包括输入是不可变的输出是为了作为另一个仍未知的程序的输入而复杂的问题是通过编写“做好一件事”的小工具来解决的。
在本章中我们探索了批处理的主题。我们首先看到了诸如awkgrep和sort之类的Unix工具然后我们看到了这些工具的设计理念是如何应用到MapReduce和更近的数据流引擎中的。一些设计原则包括输入是不可变的输出是为了作为另一个仍未知的程序的输入而复杂的问题是通过编写“做好一件事”的小工具来解决的。
在Unix世界中允许程序与程序组合的统一接口是文件与管道在MapReduce中该接口是一个分布式文件系统。我们看到数据流引擎添加了自己的管道式数据传输机制以避免将中间状态物化至分布式文件系统但作业的初始输入和最终输出通常仍是HDFS。
@ -701,7 +699,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
***分区***
在MapReduce中Mapper根据输入文件块进行分区。Mapper的输出被重新分区,排序,并合并到可配置数量的Reducer分区中。这一过程的目的是把所有的**相关**数据(例如带有相同键的所有记录)都放在同一个地方。
在MapReduce中Mapper根据输入文件块进行分区。Mapper的输出被重新分区、排序并合并到可配置数量的Reducer分区中。这一过程的目的是把所有的**相关**数据(例如带有相同键的所有记录)都放在同一个地方。
后MapReduce时代的数据流引擎若非必要会尽量避免排序但它们也采取了大致类似的分区方法。
@ -715,7 +713,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
***排序合并连接***
每个参与连接的输入都通过一个提取连接键的Mapper。通过分区排序和合并具有相同键的所有记录最终都会进入相同的Reducer调用。这个函数能输出连接好的记录。
每个参与连接的输入都通过一个提取连接键的Mapper。通过分区排序和合并具有相同键的所有记录最终都会进入相同的Reducer调用。这个函数能输出连接好的记录。
***广播散列连接***
@ -727,7 +725,7 @@ top5.each{|count, url| puts "#{count} #{url}" } # 5
分布式批处理引擎有一个刻意限制的编程模型回调函数比如Mapper和Reducer被假定是无状态的而且除了指定的输出外必须没有任何外部可见的副作用。这一限制允许框架在其抽象下隐藏一些困难的分布式系统问题当遇到崩溃和网络问题时任务可以安全地重试任何失败任务的输出都被丢弃。如果某个分区的多个任务成功则其中只有一个能使其输出实际可见。
得益于这个框架,你在批处理作业中的代码无需操心实现容错机制:框架可以保证作业的最终输出与没有发生错误的情况相同,也许不得不重试各种任务。在线服务处理用户请求,并将写入数据库作为处理请求的副作用,比起在线服务,批处理提供的这种可靠性语义要强得多。
得益于这个框架,你在批处理作业中的代码无需操心实现容错机制:框架可以保证作业的最终输出与没有发生错误的情况相同,虽然实际上也许不得不重试各种任务。比起在线服务一边处理用户请求一边将写入数据库作为处理请求的副作用,批处理提供的这种可靠性语义要强得多。
批处理作业的显著特点是,它读取一些输入数据并产生一些输出数据,但不修改输入—— 换句话说,输出是从输入衍生出的。最关键的是,输入数据是**有界的bounded**:它有一个已知的,固定的大小(例如,它包含一些时间点的日志文件或数据库内容的快照)。因为它是有界的,一个作业知道自己什么时候完成了整个输入的读取,所以一个工作在做完后,最终总是会完成的。

2
ch4.md
View File

@ -496,7 +496,7 @@ Actor模型是单个进程中并发的编程模型。逻辑被封装在actor中
三个流行的分布式actor框架处理消息编码如下
* 默认情况下Akka使用Java的内置序列化不提供前向或后向兼容性。 但是你可以用类似Prototol Buffers的东西替代它从而获得滚动升级的能力【50】。
* Orleans 默认使用不支持滚动升级部署的自定义数据编码格式; 要部署新版本的应用程序,您需要设置一个新的集,将流量从旧集迁移到新集,然后关闭旧集【51,52】。 像Akka一样可以使用自定义序列化插件。
* Orleans 默认使用不支持滚动升级部署的自定义数据编码格式; 要部署新版本的应用程序,您需要设置一个新的集,将流量从旧集迁移到新集,然后关闭旧集【51,52】。 像Akka一样可以使用自定义序列化插件。
* 在Erlang OTP中对记录模式进行更改是非常困难的尽管系统具有许多为高可用性设计的功能。 滚动升级是可能的但需要仔细计划【53】。 一个新的实验性的`maps`数据类型2014年在Erlang R17中引入的类似于JSON的结构可能使得这个数据类型在未来更容易【54】。

2
ch5.md
View File

@ -581,7 +581,7 @@
然而法定人数如迄今为止所描述的并不像它们可能的那样具有容错性。网络中断可以很容易地将客户端从大量的数据库节点上切断。虽然这些节点是活着的而其他客户端可能能够连接到它们但是从数据库节点切断的客户端来看它们也可能已经死亡。在这种情况下剩余的可用节点可能会少于w或r因此客户端不再能达到法定人数。
在一个大型的集中节点数量明显多于n个网络中断期间客户端可能仍能连接到一些数据库节点但又不足以组成一个特定值的法定人数。在这种情况下数据库设计人员需要权衡一下
在一个大型的集节点数量明显多于n个网络中断期间客户端可能仍能连接到一些数据库节点但又不足以组成一个特定值的法定人数。在这种情况下数据库设计人员需要权衡一下
* 对于所有无法达到w或r节点法定人数的请求是否返回错误是更好的
* 或者我们是否应该接受写入然后将它们写入一些可达的节点但不在这些值通常所存在的n个节点上

4
ch6.md
View File

@ -206,7 +206,7 @@
![](img/fig6-6.png)
**图6-6 将新节点添加到每个节点具有多个分区的数据库集。**
**图6-6 将新节点添加到每个节点具有多个分区的数据库集。**
原则上您甚至可以解决集群中的硬件不匹配问题通过为更强大的节点分配更多的分区可以强制这些节点承载更多的负载。在Riak 【15】Elasticsearch 【24】Couchbase 【10】和Voldemort 【25】中使用了这种再平衡的方法。
@ -278,7 +278,7 @@
例如LinkedIn的Espresso使用Helix 【31】进行集群管理依靠ZooKeeper实现了如[图6-8](img/fig6-8.png)所示的路由层。 HBaseSolrCloud和Kafka也使用ZooKeeper来跟踪分区分配。 MongoDB具有类似的体系结构但它依赖于自己的**配置服务器config server** 实现和mongos守护进程作为路由层。
Cassandra和Riak采取不同的方法他们在节点之间使用**流言协议gossip protocol** 来传播集状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点([图6-7](img/fig6-7.png)中的方法1。这个模型在数据库节点中增加了更多的复杂性但是避免了对像ZooKeeper这样的外部协调服务的依赖。
Cassandra和Riak采取不同的方法他们在节点之间使用**流言协议gossip protocol** 来传播集状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点([图6-7](img/fig6-7.png)中的方法1。这个模型在数据库节点中增加了更多的复杂性但是避免了对像ZooKeeper这样的外部协调服务的依赖。
Couchbase不会自动重新平衡这简化了设计。通常情况下它配置了一个名为moxi的路由层它会从集群节点了解路由变化【32】。

2
ch8.md
View File

@ -59,7 +59,7 @@
在本书中,我们将重点放在实现互联网服务的系统上,这些系统通常与超级计算机看起来有很大不同:
* 许多与互联网有关的应用程序都是**在线online**的,因为它们需要能够随时以低延迟服务用户。使服务不可用(例如,停止集以进行修复)是不可接受的。相比之下,像天气模拟这样的离线(批处理)工作可以停止并重新启动,影响相当小。
* 许多与互联网有关的应用程序都是**在线online**的,因为它们需要能够随时以低延迟服务用户。使服务不可用(例如,停止集以进行修复)是不可接受的。相比之下,像天气模拟这样的离线(批处理)工作可以停止并重新启动,影响相当小。
* 超级计算机通常由专用硬件构建而成,每个节点相当可靠,节点通过共享内存和**远程直接内存访问RDMA**进行通信。另一方面,云服务中的节点是由商用机器构建而成的,由于规模经济,可以以较低的成本提供相同的性能,而且具有较高的故障率。

View File

@ -1,4 +1,4 @@
# 設計資料密集型應用 - 中文翻譯
# 設計資料密集型應用 - 中文翻譯
- 作者: [Martin Kleppmann](https://martin.kleppmann.com)
- 原名:[《Designing Data-Intensive Applications》](http://shop.oreilly.com/product/0636920032175.do)
@ -8,7 +8,7 @@
> 使用 [Typora](https://www.typora.io)、[Gitbook](https://vonng.gitbooks.io/ddia-cn/content/)[Github Pages](https://vonng.github.io/ddia)以獲取最佳閱讀體驗。
>
>
> 本地:您可在專案根目錄中執行`make`,並透過瀏覽器閱讀([線上預覽](http://ddia.vonng.com/#/))。
## 譯序
@ -35,7 +35,7 @@
---------
> 計算是一種流行文化,流行文化鄙視歷史。 流行文化關乎個體身份和參與感,但與合作無關。流行文化活在當下,也與過去和未來無關。 我認為大部分(為了錢)編寫程式碼的人就是這樣的, 他們不知道自己的文化來自哪裡。
> 計算是一種流行文化,流行文化鄙視歷史。 流行文化關乎個體身份和參與感,但與合作無關。流行文化活在當下,也與過去和未來無關。 我認為大部分(為了錢)編寫程式碼的人就是這樣的, 他們不知道自己的文化來自哪裡。
>
> ——阿蘭·凱接受Dobb博士的雜誌採訪時2012年
@ -141,7 +141,7 @@
0. 全文校訂 by [@yingang](https://github.com/yingang)
1. [序言初翻修正](https://github.com/Vonng/ddia/commit/afb5edab55c62ed23474149f229677e3b42dfc2c) by [@seagullbird](https://github.com/Vonng/ddia/commits?author=seagullbird)
2. [第一章語法標點校正](https://github.com/Vonng/ddia/commit/973b12cd8f8fcdf4852f1eb1649ddd9d187e3644) by [@nevertiree](https://github.com/Vonng/ddia/commits?author=nevertiree)
3. [第六章部分校正](https://github.com/Vonng/ddia/commit/d4eb0852c0ec1e93c8aacc496c80b915bb1e6d48) 與[第10章的初翻](https://github.com/Vonng/ddia/commit/9de8dbd1bfe6fbb03b3bf6c1a1aa2291aed2490e) by @[MuAlex](https://github.com/Vonng/ddia/commits?author=MuAlex)
3. [第六章部分校正](https://github.com/Vonng/ddia/commit/d4eb0852c0ec1e93c8aacc496c80b915bb1e6d48) 與[第10章的初翻](https://github.com/Vonng/ddia/commit/9de8dbd1bfe6fbb03b3bf6c1a1aa2291aed2490e) by @[MuAlex](https://github.com/Vonng/ddia/commits?author=MuAlex)
4. [第一部分](part-i.md)前言,[ch2](ch2.md)校正 by [@jiajiadebug](https://github.com/Vonng/ddia/commits?author=jiajiadebug)
5. [詞彙表](glossary.md)、[後記]()關於野豬的部分 by @[Chowss](https://github.com/Vonng/ddia/commits?author=Chowss)
6. [繁體中文](https://github.com/Vonng/ddia/pulls)版本與轉換指令碼 by [@afunTW](https://github.com/afunTW)
@ -245,4 +245,4 @@
## 協議
[CC-BY 4.0](LICENSE)
[CC-BY 4.0](LICENSE)

View File

@ -1,45 +1,22 @@
# Summary
* [簡介](README.md)
* [序言](preface.md)
* [第一部分:資料系統的基石](part-i.md)
* [第一章:可靠性、可伸縮性、可維護性](ch1.md)
* [第二章:資料模型與查詢語言](ch2.md)
* [第三章:儲存與檢索](ch3.md)
* [第四章:編碼與演化](ch4.md)
* [第二部分:分散式資料](part-ii.md)
* [第五章:複製](ch5.md)
* [第六章:分割槽](ch6.md)
* [第七章:事務](ch7.md)
* [第八章:分散式系統的麻煩](ch8.md)
* [第九章:一致性與共識](ch9.md)
* [第三部分:衍生資料](part-iii.md)
* [第十章:批處理](ch10.md)
* [第十一章:流處理](ch11.md)
* [第十二章:資料系統的未來](ch12.md)
* [術語表](glossary.md)
* [後記](colophon.md)

View File

@ -1,11 +0,0 @@
# 設計資料密集型應用 <small></small>
> <b>Designing Data Intensive Applications</b>

View File

@ -1,7 +1,3 @@
- 語言版本
- [簡體中文](/)
- [繁體中文](/zh-tw/)

View File

@ -1,35 +1,18 @@
- [序言](preface.md)
- [第一部分:資料系統的基石](part-i.md)
- [第一章:可靠性、可伸縮性、可維護性](ch1.md)
- [第二章:資料模型與查詢語言](ch2.md)
- [第三章:儲存與檢索](ch3.md)
- [第四章:編碼與演化](ch4.md)
- [第二部分:分散式資料](part-ii.md)
- [第五章:複製](ch5.md)
- [第六章:分割槽](ch6.md)
- [第七章:事務](ch7.md)
- [第八章:分散式系統的麻煩](ch8.md)
- [第九章:一致性與共識](ch9.md)
- [第三部分:衍生資料](part-iii.md)
- [第十章:批處理](ch10.md)
- [第十一章:流處理](ch11.md)
- [第十二章:資料系統的未來](ch12.md)
- [術語表](glossary.md)
- [後記](colophon.md)

View File

@ -1,923 +1,462 @@
# 第一章:可靠性,可伸縮性,可維護性
![](../img/ch1.png)
> 網際網路做得太棒了,以至於大多數人將它看作像太平洋這樣的自然資源,而不是什麼人工產物。上一次出現這種大規模且無差錯的技術, 你還記得是什麼時候嗎?
>
> ——阿蘭·凱在接受Dobb博士雜誌採訪時說2012年
-----------------------
[TOC]
現今很多應用程式都是 **資料密集型data-intensive** 的,而非 **計算密集型compute-intensive** 的。因此CPU很少成為這類應用的瓶頸更大的問題通常來自資料量、資料複雜性、以及資料的變更速度。
資料密集型應用通常由標準組件構建而成,標準組件提供了很多通用的功能;例如,許多應用程式都需要:
- 儲存資料,以便自己或其他應用程式之後能再次找到 ***資料庫database***
- 記住開銷昂貴操作的結果,加快讀取速度(***快取cache***
- 允許使用者按關鍵字搜尋資料,或以各種方式對資料進行過濾(***搜尋索引search indexes***
- 向其他程序傳送訊息,進行非同步處理(***流處理stream processing***
- 定期處理累積的大批次資料(***批處理batch processing***
如果這些功能聽上去平淡無奇,那是因為這些 **資料系統data system** 是非常成功的抽象:我們一直不假思索地使用它們並習以為常。絕大多數工程師不會幻想從零開始編寫儲存引擎,因為在開發應用時,資料庫已經是足夠完美的工具了。
但現實沒有這麼簡單。不同的應用有著不同的需求,因而資料庫系統也是百花齊放,有著各式各樣的特性。實現快取有很多種手段,建立搜尋索引也有好幾種方法,諸如此類。因此在開發應用前,我們依然有必要先弄清楚最適合手頭工作的工具和方法。而且當單個工具解決不了你的問題時,組合使用這些工具可能還是有些難度的。
本書將是一趟關於資料系統原理、實踐與應用的旅程,並講述了設計資料密集型應用的方法。我們將探索不同工具之間的共性與特性,以及各自的實現原理。
本章將從我們所要實現的基礎目標開始:可靠、可伸縮、可維護的資料系統。我們將澄清這些詞語的含義,概述考量這些目標的方法。並回顧一些後續章節所需的基礎知識。在接下來的章節中我們將抽絲剝繭,研究設計資料密集型應用時可能遇到的設計決策。
## 關於資料系統的思考
我們通常認為,資料庫、訊息佇列、快取等工具分屬於幾個差異顯著的類別。雖然資料庫和訊息隊列表面上有一些相似性——它們都會儲存一段時間的資料——但它們有迥然不同的訪問模式,這意味著迥異的效能特徵和實現手段。
那我們為什麼要把這些東西放在 **資料系統data system** 的總稱之下混為一談呢?
近些年來出現了許多新的資料儲存工具與資料處理工具。它們針對不同應用場景進行最佳化因此不再適合生硬地歸入傳統類別【1】。類別之間的界限變得越來越模糊例如資料儲存可以被當成訊息佇列用Redis訊息佇列則帶有類似資料庫的持久保證Apache Kafka
其次,越來越多的應用程式有著各種嚴格而廣泛的要求,單個工具不足以滿足所有的資料處理和儲存需求。取而代之的是,總體工作被拆分成一系列能被單個工具高效完成的任務,並透過應用程式碼將它們縫合起來。
例如如果將快取應用管理的快取層Memcached或同類產品和全文搜尋全文搜尋伺服器例如Elasticsearch或Solr功能從主資料庫剝離出來那麼使快取/索引與主資料庫保持同步通常是應用程式碼的責任。[圖1-1](../img/fig1-1.png) 給出了這種架構可能的樣子(細節將在後面的章節中詳細介紹)。
![](../img/fig1-1.png)
**圖1-1 一個可能的組合使用多個元件的資料系統架構**
當你將多個工具組合在一起提供服務時,服務的介面或**應用程式程式設計介面API, Application Programming Interface** 通常向客戶端隱藏這些實現細節。現在,你基本上已經使用較小的通用元件建立了一個全新的、專用的資料系統。這個新的複合資料系統可能會提供特定的保證,例如:快取在寫入時會作廢或更新,以便外部客戶端獲取一致的結果。現在你不僅是應用程式開發人員,還是資料系統設計人員了。
設計資料系統或服務時可能會遇到很多棘手的問題例如當系統出問題時如何確保資料的正確性和完整性當部分系統退化降級時如何為客戶提供始終如一的良好效能當負載增加時如何擴容應對什麼樣的API才是好的API
影響資料系統設計的因素很多,包括參與人員的技能和經驗、歷史遺留問題、系統路徑依賴、交付時限、公司的風險容忍度、監管約束等,這些因素都需要具體問題具體分析。
本書著重討論三個在大多數軟體系統中都很重要的問題:
***可靠性Reliability***
系統在**困境adversity**(硬體故障、軟體故障、人為錯誤)中仍可正常工作(正確完成功能,並能達到期望的效能水準)。
***可伸縮性Scalability***
有合理的辦法應對系統的增長(資料量、流量、複雜性)(參閱“[可伸縮性](#可伸縮性)”)
***可維護性Maintainability***
許多不同的人(工程師、運維)在不同的生命週期,都能高效地在系統上工作(使系統保持現有行為,並適應新的應用場景)。(參閱”[可維護性](#可維護性)“)
人們經常追求這些詞彙,卻沒有清楚理解它們到底意味著什麼。為了工程的嚴謹性,本章的剩餘部分將探討可靠性、可伸縮性、可維護性的含義。為實現這些目標而使用的各種技術,架構和演算法將在後續的章節中研究。
## 可靠性
人們對於一個東西是否可靠,都有一個直觀的想法。人們對可靠軟體的典型期望包括:
* 應用程式表現出使用者所期望的功能。
* 允許使用者犯錯,允許使用者以出乎意料的方式使用軟體。
* 在預期的負載和資料量下,效能滿足要求。
* 系統能防止未經授權的訪問和濫用。
如果所有這些在一起意味著“正確工作”,那麼可以把可靠性粗略理解為“即使出現問題,也能繼續正確工作”。
造成錯誤的原因叫做**故障fault**,能預料並應對故障的系統特性可稱為**容錯fault-tolerant**或**韌性resilient**。“**容錯**”一詞可能會產生誤導,因為它暗示著系統可以容忍所有可能的錯誤,但在實際中這是不可能的。比方說,如果整個地球(及其上的所有伺服器)都被黑洞吞噬了,想要容忍這種錯誤,需要把網路託管到太空中——這種預算能不能批准就祝你好運了。所以在討論容錯時,只有談論特定型別的錯誤才有意義。
注意**故障fault**不同於**失效failure**【2】。**故障**通常定義為系統的一部分狀態偏離其標準,而**失效**則是系統作為一個整體停止向用戶提供服務。故障的概率不可能降到零,因此最好設計容錯機制以防因**故障**而導致**失效**。本書中我們將介紹幾種用不可靠的部件構建可靠系統的技術。
反直覺的是,在這類容錯系統中,透過故意觸發來**提高**故障率是有意義的例如在沒有警告的情況下隨機地殺死單個程序。許多高危漏洞實際上是由糟糕的錯誤處理導致的【3】因此我們可以透過故意引發故障來確保容錯機制不斷執行並接受考驗從而提高故障自然發生時系統能正確處理的信心。Netflix公司的*Chaos Monkey*【4】就是這種方法的一個例子。
儘管比起**阻止錯誤prevent error**,我們通常更傾向於**容忍錯誤**。但也有**預防勝於治療**的情況(比如不存在治療方法時)。安全問題就屬於這種情況。例如,如果攻擊者破壞了系統,並獲取了敏感資料,這種事是撤銷不了的。但本書主要討論的是可以恢復的故障種類,正如下面幾節所述。
### 硬體故障
當想到系統失效的原因時,**硬體故障hardware faults**總會第一個進入腦海。硬碟崩潰、記憶體出錯、機房斷電、有人拔錯網線……任何與大型資料中心打過交道的人都會告訴你:一旦你擁有很多機器,這些事情**總**會發生!
據報道稱,硬碟的 **平均無故障時間MTTF mean time to failure** 約為10到50年【5】【6】。因此從數學期望上講在擁有10000個磁碟的儲存叢集上平均每天會有1個磁碟出故障。
為了減少系統的故障率第一反應通常都是增加單個硬體的冗餘度例如磁碟可以組建RAID伺服器可能有雙路電源和熱插拔CPU資料中心可能有電池和柴油發電機作為後備電源某個元件掛掉時冗餘元件可以立刻接管。這種方法雖然不能完全防止由硬體問題導致的系統失效但它簡單易懂通常也足以讓機器不間斷執行很多年。
直到最近,硬體冗餘對於大多數應用來說已經足夠了,它使單臺機器完全失效變得相當罕見。只要你能快速地把備份恢復到新機器上,故障停機時間對大多數應用而言都算不上災難性的。只有少量高可用性至關重要的應用才會要求有多套硬體冗餘。
但是隨著資料量和應用計算需求的增加,越來越多的應用開始大量使用機器,這會相應地增加硬體故障率。此外在一些雲平臺(**如亞馬遜網路服務AWS, Amazon Web Services**虛擬機器例項不可用卻沒有任何警告也是很常見的【7】因為雲平臺的設計就是優先考慮**靈活性flexibility**和**彈性elasticity**[^i],而不是單機可靠性。
如果在硬體冗餘的基礎上進一步引入軟體容錯機制,那麼系統在容忍整個(單臺)機器故障的道路上就更進一步了。這樣的系統也有運維上的便利,例如:如果需要重啟機器(例如應用作業系統安全補丁),單伺服器系統就需要計劃停機。而允許機器失效的系統則可以一次修復一個節點,無需整個系統停機。
[^i]: 在[應對負載的方法](#應對負載的方法)一節定義
### 軟體錯誤
我們通常認為硬體故障是隨機的、相互獨立的:一臺機器的磁碟失效並不意味著另一臺機器的磁碟也會失效。大量硬體元件不可能同時發生故障,除非它們存在比較弱的相關性(同樣的原因導致關聯性錯誤,例如伺服器機架的溫度)。
另一類錯誤是內部的**系統性錯誤systematic error**【7】。這類錯誤難以預料而且因為是跨節點相關的所以比起不相關的硬體故障往往可能造成更多的**系統失效**【5】。例子包括
* 接受特定的錯誤輸入便導致所有應用伺服器例項崩潰的BUG。例如2012年6月30日的閏秒由於Linux核心中的一個錯誤許多應用同時掛掉了。
* 失控程序會用盡一些共享資源包括CPU時間、記憶體、磁碟空間或網路頻寬。
* 系統依賴的服務變慢,沒有響應,或者開始返回錯誤的響應。
* 級聯故障一個元件中的小故障觸發另一個元件中的故障進而觸發更多的故障【10】。
導致這類軟體故障的BUG通常會潛伏很長時間直到被異常情況觸發為止。這種情況意味著軟體對其環境做出了某種假設——雖然這種假設通常來說是正確的但由於某種原因最後不再成立了【11】。
雖然軟體中的系統性故障沒有速效藥,但我們還是有很多小辦法,例如:仔細考慮系統中的假設和互動;徹底的測試;程序隔離;允許程序崩潰並重啟;測量、監控並分析生產環境中的系統行為。如果系統能夠提供一些保證(例如在一個訊息佇列中,進入與發出的訊息數量相等),那麼系統就可以在執行時不斷自檢,並在出現**差異discrepancy** 時報警【12】。
### 人為錯誤
設計並構建了軟體系統的工程師是人類維持系統執行的運維也是人類。即使他們懷有最大的善意人類也是不可靠的。舉個例子一項關於大型網際網路服務的研究發現運維配置錯誤是導致服務中斷的首要原因而硬體故障伺服器或網路僅導致了10-25的服務中斷【13】。
儘管人類不可靠,但怎麼做才能讓系統變得可靠?最好的系統會組合使用以下幾種辦法:
* 以最小化犯錯機會的方式設計系統。例如精心設計的抽象、API和管理後臺使做對事情更容易搞砸事情更困難。但如果介面限制太多人們就會忽略它們的好處而想辦法繞開。很難正確把握這種微妙的平衡。
* 將人們最容易犯錯的地方與可能導致失效的地方**解耦decouple**。特別是提供一個功能齊全的非生產環境**沙箱sandbox**,使人們可以在不影響真實使用者的情況下,使用真實資料安全地探索和實驗。
* 在各個層次進行徹底的測試【3】從單元測試、全系統整合測試到手動測試。自動化測試易於理解已經被廣泛使用特別適合用來覆蓋正常情況中少見的**邊緣場景corner case**。
* 允許從人為錯誤中簡單快速地恢復,以最大限度地減少失效情況帶來的影響。 例如,快速回滾配置變更,分批發布新程式碼(以便任何意外錯誤隻影響一小部分使用者),並提供資料重算工具(以備舊的計算出錯)。
* 配置詳細和明確的監控,比如效能指標和錯誤率。 在其他工程學科中這指的是**遙測telemetry**。 (一旦火箭離開了地面,遙測技術對於跟蹤發生的事情和理解失敗是至關重要的。)監控可以向我們發出預警訊號,並允許我們檢查是否有任何地方違反了假設和約束。當出現問題時,指標資料對於問題診斷是非常寶貴的。
* 良好的管理實踐與充分的培訓——一個複雜而重要的方面,但超出了本書的範圍。
### 可靠性有多重要?
可靠性不僅僅是針對核電站和空中交通管制軟體而言,我們也期望更多平凡的應用能可靠地執行。商務應用中的錯誤會導致生產力損失(也許資料報告不完整還會有法律風險),而電商網站的中斷則可能會導致收入和聲譽的巨大損失。
即使在“非關鍵”應用中我們也對使用者負有責任。試想一位家長把所有的照片和孩子的影片儲存在你的照片應用裡【15】。如果資料庫突然損壞他們會感覺如何他們可能會知道如何從備份恢復嗎
在某些情況下,我們可能會選擇犧牲可靠性來降低開發成本(例如為未經證實的市場開發產品原型)或運營成本(例如利潤率極低的服務),但我們偷工減料時,應該清楚意識到自己在做什麼。
## 可伸縮性
系統今天能可靠執行,並不意味未來也能可靠執行。服務 **降級degradation** 的一個常見原因是負載增加,例如:系統負載已經從一萬個併發使用者增長到十萬個併發使用者,或者從一百萬增長到一千萬。也許現在處理的資料量級要比過去大得多。
**可伸縮性Scalability** 是用來描述系統應對負載增長能力的術語。但是請注意這不是貼在系統上的一維標籤說“X可伸縮”或“Y不可伸縮”是沒有任何意義的。相反討論可伸縮性意味著考慮諸如“如果系統以特定方式增長有什麼選項可以應對增長”和“如何增加計算資源來處理額外的負載”等問題。
### 描述負載
在討論增長問題(如果負載加倍會發生什麼?)前,首先要能簡要描述系統的當前負載。負載可以用一些稱為 **負載引數load parameters** 的數字來描述。引數的最佳選擇取決於系統架構它可能是每秒向Web伺服器發出的請求、資料庫中的讀寫比率、聊天室中同時活躍的使用者數量、快取命中率或其他東西。除此之外也許平均情況對你很重要也許你的瓶頸是少數極端場景。
為了使這個概念更加具體我們以推特在2012年11月釋出的資料【16】為例。推特的兩個主要業務是
***釋出推文***
使用者可以向其粉絲髮布新訊息(平均 4.6k請求/秒,峰值超過 12k請求/秒)。
***主頁時間線***
使用者可以查閱他們關注的人釋出的推文300k請求/秒)。
處理每秒12,000次寫入發推文的速率峰值還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量而是來自**扇出fan-out**——每個使用者關注了很多人,也被很多人關注。
[^ii]: 扇出:從電子工程學中借用的術語,它描述了輸入連線到另一個門輸出的邏輯閘數量。 輸出需要提供足夠的電流來驅動所有連線的輸入。 在事務處理系統中,我們使用它來描述為了服務一個傳入請求而需要執行其他服務的請求數量。
大體上講,這一對操作有兩種實現方式。
1. 釋出推文時,只需將新推文插入全域性推文集合即可。當一個使用者請求自己的主頁時間線時,首先查詢他關注的所有人,查詢這些被關注使用者釋出的推文並按時間順序合併。在如[圖1-2](../img/fig1-2.png)所示的關係型資料庫中,可以編寫這樣的查詢:
```sql
SELECT tweets.*, users.*
FROM tweets
JOIN users ON tweets.sender_id = users.id
JOIN follows ON follows.followee_id = users.id
WHERE follows.follower_id = current_user
```
![](../img/fig1-2.png)
**圖1-2 推特主頁時間線的關係型模式簡單實現**
2. 為每個使用者的主頁時間線維護一個快取,就像每個使用者的推文收件箱([圖1-3](../img/fig1-3.png))。 當一個使用者釋出推文時,查詢所有關注該使用者的人,並將新的推文插入到每個主頁時間線快取中。 因此讀取主頁時間線的請求開銷很小,因為結果已經提前計算好了。
![](../img/fig1-3.png)
**圖1-3 用於分發推特至關注者的資料流水線2012年11月的負載引數【16】**
推特的第一個版本使用了方法1但系統很難跟上主頁時間線查詢的負載。所以公司轉向了方法2方法2的效果更好因為發推頻率比查詢主頁時間線的頻率幾乎低了兩個數量級所以在這種情況下最好在寫入時做更多的工作而在讀取時做更少的工作。
然而方法2的缺點是發推現在需要大量的額外工作。平均來說一條推文會發往約75個關注者所以每秒4.6k的發推寫入變成了對主頁時間線快取每秒345k的寫入。但這個平均值隱藏了使用者粉絲數差異巨大這一現實一些使用者有超過3000萬的粉絲這意味著一條推文就可能會導致主頁時間線快取的3000萬次寫入及時完成這種操作是一個巨大的挑戰 —— 推特嘗試在5秒內向粉絲髮送推文。
在推特的例子中,每個使用者粉絲數的分佈(可能按這些使用者的發推頻率來加權)是探討可伸縮性的一個關鍵負載引數,因為它決定了扇出負載。你的應用程式可能具有非常不同的特徵,但可以採用相似的原則來考慮它的負載。
推特軼事的最終轉折現在已經穩健地實現了方法2推特逐步轉向了兩種方法的混合。大多數使用者發的推文會被扇出寫入其粉絲主頁時間線快取中。但是少數擁有海量粉絲的使用者即名流會被排除在外。當用戶讀取主頁時間線時分別地獲取出該使用者所關注的每位名流的推文再與使用者的主頁時間線快取合併如方法1所示。這種混合方法能始終如一地提供良好效能。在[第12章](ch12.md)中我們將重新討論這個例子,這在覆蓋更多技術層面之後。
### 描述效能
一旦系統的負載被描述好,就可以研究當負載增加會發生什麼。我們可以從兩種角度來看:
* 增加負載引數並保持系統資源CPU、記憶體、網路頻寬等不變時系統性能將受到什麼影響
* 增加負載引數並希望保持效能不變時,需要增加多少系統資源?
這兩個問題都需要效能資料,所以讓我們簡單地看一下如何描述系統性能。
對於Hadoop這樣的批處理系統通常關心的是**吞吐量throughput**,即每秒可以處理的記錄數量,或者在特定規模資料集上執行作業的總時間[^iii]。對於線上系統,通常更重要的是服務的**響應時間response time**,即客戶端傳送請求到接收響應之間的時間。
[^iii]: 理想情況下,批次作業的執行時間是資料集的大小除以吞吐量。 在實踐中由於資料傾斜(資料不是均勻分佈在每個工作程序中),需要等待最慢的任務完成,所以執行時間往往更長。
> #### 延遲和響應時間
>
> **延遲latency****響應時間response time** 經常用作同義詞,但實際上它們並不一樣。響應時間是客戶所看到的,除了實際處理請求的時間( **服務時間service time** )之外,還包括網路延遲和排隊延遲。延遲是某個請求等待處理的**持續時長**,在此期間它處於 **休眠latent** 狀態並等待服務【17】。
即使不斷重複傳送同樣的請求,每次得到的響應時間也都會略有不同。現實世界的系統會處理各式各樣的請求,響應時間可能會有很大差異。因此我們需要將響應時間視為一個可以測量的數值**分佈distribution**,而不是單個數值。
在[圖1-4](../img/fig1-4.png)中每個灰條代表一次對服務的請求其高度表示請求花費了多長時間。大多數請求是相當快的但偶爾會出現需要更長的時間的異常值。這也許是因為緩慢的請求實質上開銷更大例如它們可能會處理更多的資料。但即使你認為所有請求都花費相同時間的情況下隨機的附加延遲也會導致結果變化例如上下文切換到後臺程序網路資料包丟失與TCP重傳垃圾收集暫停強制從磁碟讀取的頁面錯誤伺服器機架中的震動【18】還有很多其他原因。
![](../img/fig1-4.png)
**圖1-4 展示了一個服務100次請求響應時間的均值與百分位數**
通常報表都會展示服務的平均響應時間。 (嚴格來講“平均”一詞並不指代任何特定公式,但實際上它通常被理解為**算術平均值arithmetic mean**:給定 n 個值,加起來除以 n )。然而如果你想知道“**典型typical**”響應時間,那麼平均值並不是一個非常好的指標,因為它不能告訴你有多少使用者實際上經歷了這個延遲。
通常使用**百分位點percentiles** 會更好。如果將響應時間列表按最快到最慢排序,那麼**中位數median** 就在正中間舉個例子如果你的響應時間中位數是200毫秒這意味著一半請求的返回時間少於200毫秒另一半比這個要長。
如果想知道典型場景下使用者需要等待多長時間那麼中位數是一個好的度量標準一半使用者請求的響應時間少於響應時間的中位數另一半服務時間比中位數長。中位數也被稱為第50百分位點有時縮寫為p50。注意中位數是關於單個請求的如果使用者同時發出幾個請求在一個會話過程中或者由於一個頁面中包含了多個資源則至少一個請求比中位數慢的概率遠大於50
為了弄清異常值有多糟糕可以看看更高的百分位點例如第95、99和99.9百分位點縮寫為p95p99和p999。它們意味著9599或99.9的請求響應時間要比該閾值快例如如果第95百分位點響應時間是1.5秒則意味著100個請求中的95個響應時間快於1.5秒而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。
響應時間的高百分位點(也稱為**尾部延遲tail latencies**非常重要因為它們直接影響使用者的服務體驗。例如亞馬遜在描述內部服務的響應時間要求時以99.9百分位點為準,即使它隻影響一千個請求中的一個。這是因為請求響應最慢的客戶往往也是資料最多的客戶,也可以說是最有價值的客戶 —— 因為他們掏錢了【19】。保證網站響應迅速對於保持客戶的滿意度非常重要亞馬遜觀察到響應時間增加100毫秒銷售量就減少1【20】而另一些報告說慢 1 秒鐘會讓客戶滿意度指標減少16%【2122】。
另一方面最佳化第99.99百分位點(一萬個請求中最慢的一個)被認為太昂貴了,不能為亞馬遜的目標帶來足夠好處。減小高百分位點處的響應時間相當困難,因為它很容易受到隨機事件的影響,這超出了控制範圍,而且效益也很小。
百分位點通常用於**服務級別目標SLO, service level objectives**和**服務級別協議SLA, service level agreements**,即定義服務預期效能和可用性的合同。 SLA可能會宣告如果服務響應時間的中位數小於200毫秒且99.9百分位點低於1秒則認為服務工作正常如果響應時間更長就認為服務不達標。這些指標為客戶設定了期望值並允許客戶在SLA未達標的情況下要求退款。
**排隊延遲queueing delay** 通常佔了高百分位點處響應時間的很大一部分。由於伺服器只能並行處理少量的事務如受其CPU核數的限制所以只要有少量緩慢的請求就能阻礙後續請求的處理這種效應有時被稱為 **頭部阻塞head-of-line blocking** 。即使後續請求在伺服器上處理的非常迅速,由於需要等待先前請求完成,客戶端最終看到的是緩慢的總體響應時間。因為存在這種效應,測量客戶端的響應時間非常重要。
為測試系統的可伸縮性而人為產生負載時產生負載的客戶端要獨立於響應時間不斷髮送請求。如果客戶端在傳送下一個請求之前等待先前的請求完成這種行為會產生人為排隊的效果使得測試時的佇列比現實情況更短使測量結果產生偏差【23】。
> #### 實踐中的百分位點
>
> 在多重呼叫的後端服務裡,高百分位數變得特別重要。即使並行呼叫,終端使用者請求仍然需要等待最慢的並行呼叫完成。如[圖1-5](../img/fig1-5.png)所示只需要一個緩慢的呼叫就可以使整個終端使用者請求變慢。即使只有一小部分後端呼叫速度較慢如果終端使用者請求需要多個後端呼叫則獲得較慢呼叫的機會也會增加因此較高比例的終端使用者請求速度會變慢效果稱為尾部延遲放大【24】
>
> 如果您想將響應時間百分點新增到您的服務的監視儀表板則需要持續有效地計算它們。例如您可能希望在最近10分鐘內保持請求響應時間的滾動視窗。每一分鐘您都會計算出該視窗中的中值和各種百分數並將這些度量值繪製在圖上。
>
> 簡單的實現是在時間視窗內儲存所有請求的響應時間列表並且每分鐘對列表進行排序。如果對你來說效率太低那麼有一些演算法能夠以最小的CPU和記憶體成本如前向衰減【25】t-digest【26】或HdrHistogram 【27】來計算百分位數的近似值。請注意平均百分比例如減少時間解析度或合併來自多臺機器的資料在數學上沒有意義 - 聚合響應時間資料的正確方法是新增直方圖【28】。
![](../img/fig1-5.png)
**圖1-5 當一個請求需要多個後端請求時,單個後端慢請求就會拖慢整個終端使用者的請求**
### 應對負載的方法
現在我們已經討論了用於描述負載的引數和用於衡量效能的指標。可以開始認真討論可伸縮性了:當負載引數增加時,如何保持良好的效能?
適應某個級別負載的架構不太可能應付10倍於此的負載。如果你正在開發一個快速增長的服務那麼每次負載發生數量級的增長時你可能都需要重新考慮架構——或者更頻繁。
人們經常討論**縱向伸縮scaling up****垂直伸縮vertical scaling**,轉向更強大的機器)和**橫向伸縮scaling out** **水平伸縮horizontal scaling**,將負載分佈到多臺小機器上)之間的對立。跨多臺機器分配負載也稱為“**無共享shared-nothing**”架構。可以在單臺機器上執行的系統通常更簡單,但高階機器可能非常貴,所以非常密集的負載通常無法避免地需要橫向伸縮。現實世界中的優秀架構需要將這兩種方法務實地結合,因為使用幾臺足夠強大的機器可能比使用大量的小型虛擬機器更簡單也更便宜。
有些系統是 **彈性elastic** 的,這意味著可以在檢測到負載增加時自動增加計算資源,而其他系統則是手動伸縮(人工分析容量並決定向系統新增更多的機器)。如果負載**極難預測highly unpredictable**,則彈性系統可能很有用,但手動伸縮系統更簡單,並且意外操作可能會更少(參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”)。
跨多臺機器部署 **無狀態服務stateless services** 非常簡單,但將帶狀態的資料系統從單節點變為分散式配置則可能引入許多額外複雜度。出於這個原因,常識告訴我們應該將資料庫放在單個節點上(縱向伸縮),直到伸縮成本或可用性需求迫使其改為分散式。
隨著分散式系統的工具和抽象越來越好,至少對於某些型別的應用而言,這種常識可能會改變。可以預見分散式資料系統將成為未來的預設設定,即使對不處理大量資料或流量的場景也如此。本書的其餘部分將介紹多種分散式資料系統,不僅討論它們在可伸縮性方面的表現,還包括易用性和可維護性。
大規模的系統架構通常是應用特定的—— 沒有一招鮮吃遍天的通用可伸縮架構(不正式的叫法:**萬金油magic scaling sauce** )。應用的問題可能是讀取量、寫入量、要儲存的資料量、資料的複雜度、響應時間要求、訪問模式或者所有問題的大雜燴。
舉個例子用於處理每秒十萬個請求每個大小為1 kB的系統與用於處理每分鐘3個請求每個大小為2GB的系統看上去會非常不一樣儘管兩個系統有同樣的資料吞吐量。
一個良好適配應用的可伸縮架構,是圍繞著**假設assumption** 建立的:哪些操作是常見的?哪些操作是罕見的?這就是所謂負載引數。如果假設最終是錯誤的,那麼為伸縮所做的工程投入就白費了,最糟糕的是適得其反。在早期創業公司或非正式產品中,通常支援產品快速迭代的能力,要比可伸縮至未來的假想負載要重要的多。
儘管這些架構是應用程式特定的,但可伸縮的架構通常也是從通用的積木塊搭建而成的,並以常見的模式排列。在本書中,我們將討論這些構件和模式。
## 可維護性
眾所周知,軟體的大部分開銷並不在最初的開發階段,而是在持續的維護階段,包括修復漏洞、保持系統正常執行、調查失效、適配新的平臺、為新的場景進行修改、償還技術債、新增新的功能等等。
不幸的是,許多從事軟體系統行業的人不喜歡維護所謂的**遺留legacy** 系統,——也許因為涉及修復其他人的錯誤、和過時的平臺打交道,或者系統被迫使用於一些份外工作。每一個遺留系統都以自己的方式讓人不爽,所以很難給出一個通用的建議來和它們打交道。
但是我們可以,也應該以這樣一種方式來設計軟體:在設計之初就儘量考慮儘可能減少維護期間的痛苦,從而避免自己的軟體系統變成遺留系統。為此,我們將特別關注軟體系統的三個設計原則:
***可操作性Operability***
便於運維團隊保持系統平穩執行。
***簡單性Simplicity***
從系統中消除儘可能多的**複雜度complexity**,使新工程師也能輕鬆理解系統。(注意這和使用者介面的簡單性不一樣。)
***可演化性evolability***
使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為**可伸縮性extensibility****可修改性modifiability**或**可塑性plasticity**。
和之前提到的可靠性、可伸縮性一樣,實現這些目標也沒有簡單的解決方案。不過我們會試著想象具有可操作性,簡單性和可演化性的系統會是什麼樣子。
### 可操作性:人生苦短,關愛運維
有人認為,“良好的運維經常可以繞開垃圾(或不完整)軟體的侷限性,而再好的軟體攤上垃圾運維也沒法可靠執行”。儘管運維的某些方面可以,而且應該是自動化的,但在最初建立正確運作的自動化機制仍然取決於人。
運維團隊對於保持軟體系統順利執行至關重要。一個優秀運維團隊的典型職責如下或者更多【29】
* 監控系統的執行狀況,並在服務狀態不佳時快速恢復服務
* 跟蹤問題的原因,例如系統故障或效能下降
* 及時更新軟體和平臺,比如安全補丁
* 瞭解系統間的相互作用,以便在異常變更造成損失前進行規避。
* 預測未來的問題,並在問題出現之前加以解決(例如,容量規劃)
* 建立部署,配置、管理方面的良好實踐,編寫相應工具
* 執行復雜的維護任務,例如將應用程式從一個平臺遷移到另一個平臺
* 當配置變更時,維持系統的安全性
* 定義工作流程,使運維操作可預測,並保持生產環境穩定。
* 鐵打的營盤流水的兵,維持組織對系統的瞭解。
良好的可操作性意味著更輕鬆的日常工作,進而運維團隊能專注於高價值的事情。資料系統可以透過各種方式使日常任務更輕鬆:
* 透過良好的監控,提供對系統內部狀態和執行時行為的**可見性visibility**
* 為自動化提供良好支援,將系統與標準化工具相整合
* 避免依賴單臺機器(在整個系統繼續不間斷執行的情況下允許機器停機維護)
* 提供良好的文件和易於理解的操作模型“如果做X會發生Y”
* 提供良好的預設行為,但需要時也允許管理員自由覆蓋預設值
* 有條件時進行自我修復,但需要時也允許管理員手動控制系統狀態
* 行為可預測,最大限度減少意外
### 簡單性:管理複雜度
小型軟體專案可以使用簡單討喜的、富表現力的程式碼,但隨著專案越來越大,程式碼往往變得非常複雜,難以理解。這種複雜度拖慢了所有系統相關人員,進一步增加了維護成本。一個陷入複雜泥潭的軟體專案有時被描述為 **爛泥潭a big ball of mud** 【30】。
**複雜度complexity** 有各種可能的症狀例如狀態空間激增、模組間緊密耦合、糾結的依賴關係、不一致的命名和術語、解決效能問題的Hack、需要繞開的特例等等現在已經有很多關於這個話題的討論【31,32,33】。
因為複雜度導致維護困難時,預算和時間安排通常會超支。在複雜的軟體中進行變更,引入錯誤的風險也更大:當開發人員難以理解系統時,隱藏的假設、無意的後果和意外的互動就更容易被忽略。相反,降低複雜度能極大地提高軟體的可維護性,因此簡單性應該是構建系統的一個關鍵目標。
簡化系統並不一定意味著減少功能;它也可以意味著消除**額外的accidental** 的複雜度。 Moseley和Marks【32】把 **額外複雜度** 定義為:由具體實現中湧現,而非(從使用者視角看,系統所解決的)問題本身固有的複雜度。
用於消除**額外複雜度** 的最好工具之一是**抽象abstraction**。一個好的抽象可以將大量實現細節隱藏在一個乾淨,簡單易懂的外觀下面。一個好的抽象也可以廣泛用於各類不同應用。比起重複造很多輪子,重用抽象不僅更有效率,而且有助於開發高質量的軟體。抽象元件的質量改進將使所有使用它的應用受益。
例如高階程式語言是一種抽象隱藏了機器碼、CPU暫存器和系統呼叫。 SQL也是一種抽象隱藏了複雜的磁碟/記憶體資料結構、來自其他客戶端的併發請求、崩潰後的不一致性。當然在用高階語言程式設計時,我們仍然用到了機器碼;只不過沒有**直接directly** 使用罷了,正是因為程式語言的抽象,我們才不必去考慮這些實現細節。
抽象可以幫助我們將系統的複雜度控制在可管理的水平,不過,找到好的抽象是非常困難的。在分散式系統領域雖然有許多好的演算法,但我們並不清楚它們應該打包成什麼樣抽象。
本書將緊盯那些允許我們將大型系統的部分提取為定義明確的、可重用的元件的優秀抽象。
### 可演化性:擁抱變化
系統的需求永遠不變,基本是不可能的。更可能的情況是,它們處於常態的變化中,例如:你瞭解了新的事實、出現意想不到的應用場景、業務優先順序發生變化、使用者要求新功能、新平臺取代舊平臺、法律或監管要求發生變化、系統增長迫使架構變化等。
在組織流程方面, **敏捷agile** 工作模式為適應變化提供了一個框架。敏捷社群還開發了對在頻繁變化的環境中開發軟體很有幫助的技術工具和模式,如 **測試驅動開發TDD, test-driven development****重構refactoring**
這些敏捷技術的大部分討論都集中在相當小的規模同一個應用中的幾個程式碼檔案。本書將探索在更大資料系統層面上提高敏捷性的方法可能由幾個不同的應用或服務組成。例如為了將裝配主頁時間線的方法從方法1變為方法2你會如何“重構”推特的架構
修改資料系統並使其適應不斷變化需求的容易程度,是與**簡單性**和**抽象性**密切相關的:簡單易懂的系統通常比複雜系統更容易修改。但由於這是一個非常重要的概念,我們將用一個不同的詞來指代資料系統層面的敏捷性: **可演化性evolvability** 【34】。
## 本章小結
本章探討了一些關於資料密集型應用的基本思考方式。這些原則將指導我們閱讀本書的其餘部分,那裡將會深入技術細節。
一個應用必須滿足各種需求才稱得上有用。有一些**功能需求functional requirements**(它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求nonfunctional **(通用屬性,例如安全性,可靠性,合規性,可伸縮性,相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。
**可靠性Reliability** 意味著即使發生故障系統也能正常工作。故障可能發生在硬體通常是隨機的和不相關的軟體通常是系統性的Bug很難處理和人類不可避免地時不時出錯**容錯技術** 可以對終端使用者隱藏某些型別的故障。
**可伸縮性Scalability** 意味著即使在負載增加的情況下也有保持效能的策略。為了討論可伸縮性,我們首先需要定量描述負載和效能的方法。我們簡要了解了推特主頁時間線的例子,介紹描述負載的方法,並將響應時間百分位點作為衡量效能的一種方式。在可伸縮的系統中可以新增 **處理容量processing capacity** 以在高負載下保持可靠。
**可維護性Maintainability** 有許多方面,但實質上是關於工程師和運維團隊的生活質量的。良好的抽象可以幫助降低複雜度,並使系統易於修改和適應新的應用場景。良好的可操作性意味著對系統的健康狀態具有良好的可見性,並擁有有效的管理手段。
不幸的是,使應用可靠、可伸縮或可維護並不容易。但是某些模式和技術會不斷重新出現在不同的應用中。在接下來的幾章中,我們將看到一些資料系統的例子,並分析它們如何實現這些目標。
在本書後面的[第三部分](part-iii.md)中,我們將看到一種模式:幾個元件協同工作以構成一個完整的系統(如[圖1-1](../img/fig1-1.png)中的例子)
## 參考文獻
1. Michael Stonebraker and Uğur Çetintemel: “['One Size Fits All': An Idea Whose Time Has Come and Gone](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.68.9136&rep=rep1&type=pdf),” at *21st International Conference on Data Engineering* (ICDE), April 2005.
1. Walter L. Heimerdinger and Charles B. Weinstock: “[A Conceptual Framework for System Fault Tolerance](http://www.sei.cmu.edu/reports/92tr033.pdf),” Technical Report CMU/SEI-92-TR-033, Software Engineering Institute, Carnegie Mellon University, October 1992.
2. Ding Yuan, Yu Luo, Xin Zhuang, et al.: “[Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf),” at *11th USENIX Symposium on Operating Systems Design and Implementation* (OSDI), October 2014.
3. Yury Izrailevsky and Ariel Tseitlin: “[The Netflix Simian Army](http://techblog.netflix.com/2011/07/netflix-simian-army.html),” *techblog.netflix.com*, July 19, 2011.
4. Daniel Ford, François Labelle, Florentina I. Popovici, et al.: “[Availability in Globally Distributed Storage Systems](http://research.google.com/pubs/archive/36737.pdf),” at *9th USENIX Symposium on Operating Systems Design and Implementation* (OSDI),
October 2010.
5. Brian Beach: “[Hard Drive Reliability Update Sep 2014](https://www.backblaze.com/blog/hard-drive-reliability-update-september-2014/),” *backblaze.com*, September 23, 2014.
6. Laurie Voss: “[AWS: The Good, the Bad and the Ugly](https://web.archive.org/web/20160429075023/http://blog.awe.sm/2012/12/18/aws-the-good-the-bad-and-the-ugly/),” *blog.awe.sm*, December 18, 2012.
7. Haryadi S. Gunawi, Mingzhe Hao, Tanakorn Leesatapornwongsa, et al.: “[What Bugs Live in the Cloud?](http://ucare.cs.uchicago.edu/pdf/socc14-cbs.pdf),” at *5th ACM Symposium on Cloud Computing* (SoCC), November 2014. [doi:10.1145/2670979.2670986](http://dx.doi.org/10.1145/2670979.2670986)
8. Nelson Minar: “[Leap Second Crashes Half the Internet](http://www.somebits.com/weblog/tech/bad/leap-second-2012.html),” *somebits.com*, July 3, 2012.
9. Amazon Web Services: “[Summary of the Amazon EC2 and Amazon RDS Service Disruption in the US East Region](http://aws.amazon.com/message/65648/),” *aws.amazon.com*, April 29, 2011.
10. Richard I. Cook: “[How Complex Systems Fail](http://web.mit.edu/2.75/resources/random/How%20Complex%20Systems%20Fail.pdf),” Cognitive Technologies Laboratory, April 2000.
11. Jay Kreps: “[Getting Real About Distributed System Reliability](http://blog.empathybox.com/post/19574936361/getting-real-about-distributed-system-reliability),” *blog.empathybox.com*, March 19, 2012.
12. David Oppenheimer, Archana Ganapathi, and David A. Patterson: “[Why Do Internet Services Fail, and What Can Be Done About It?](http://static.usenix.org/legacy/events/usits03/tech/full_papers/oppenheimer/oppenheimer.pdf),” at *4th USENIX Symposium on Internet Technologies and Systems* (USITS), March 2003.
13. Nathan Marz: “[Principles of Software Engineering, Part 1](http://nathanmarz.com/blog/principles-of-software-engineering-part-1.html),” *nathanmarz.com*, April 2, 2013.
14. Michael Jurewitz:“[The Human Impact of Bugs](http://jury.me/blog/2013/3/14/the-human-impact-of-bugs),” *jury.me*, March 15, 2013.
15. Raffi Krikorian: “[Timelines at Scale](http://www.infoq.com/presentations/Twitter-Timeline-Scalability),” at *QCon San Francisco*, November 2012.
16. Martin Fowler: *Patterns of Enterprise Application Architecture*. Addison Wesley, 2002. ISBN: 978-0-321-12742-6
17. Kelly Sommers: “[After all that run around, what caused 500ms disk latency even when we replaced physical server?](https://twitter.com/kellabyte/status/532930540777635840)” *twitter.com*, November 13, 2014.
18. Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, et al.: “[Dynamo: Amazon's Highly Available Key-Value Store](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf),” at *21st ACM Symposium on Operating Systems Principles* (SOSP), October 2007.
19. Greg Linden: “[Make Data Useful](http://glinden.blogspot.co.uk/2006/12/slides-from-my-talk-at-stanford.html),” slides from presentation at Stanford University Data Mining class (CS345), December 2006.
20. Tammy Everts: “[The Real Cost of Slow Time vs Downtime](http://www.webperformancetoday.com/2014/11/12/real-cost-slow-time-vs-downtime-slides/),” *webperformancetoday.com*, November 12, 2014.
21. Jake Brutlag:“[Speed Matters for Google Web Search](http://googleresearch.blogspot.co.uk/2009/06/speed-matters.html),” *googleresearch.blogspot.co.uk*, June 22, 2009.
22. Tyler Treat: “[Everything You Know About Latency Is Wrong](http://bravenewgeek.com/everything-you-know-about-latency-is-wrong/),” *bravenewgeek.com*, December 12, 2015.
23. Jeffrey Dean and Luiz André Barroso: “[The Tail at Scale](http://cacm.acm.org/magazines/2013/2/160173-the-tail-at-scale/fulltext),” *Communications of the ACM*, volume 56, number 2, pages 7480, February 2013. [doi:10.1145/2408776.2408794](http://dx.doi.org/10.1145/2408776.2408794)
24. Graham Cormode, Vladislav Shkapenyuk, Divesh Srivastava, and Bojian Xu: “[Forward Decay: A Practical Time Decay Model for Streaming Systems](http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf),” at *25th IEEE International Conference on Data Engineering* (ICDE), March 2009.
25. Ted Dunning and Otmar Ertl: “[Computing Extremely Accurate Quantiles Using t-Digests](https://github.com/tdunning/t-digest),” *github.com*, March 2014.
26. Gil Tene: “[HdrHistogram](http://www.hdrhistogram.org/),” *hdrhistogram.org*.
27. Baron Schwartz: “[Why Percentiles Dont Work the Way You Think](https://www.vividcortex.com/blog/why-percentiles-dont-work-the-way-you-think),” *vividcortex.com*, December 7, 2015.
28. James Hamilton: “[On Designing and Deploying Internet-Scale Services](https://www.usenix.org/legacy/events/lisa07/tech/full_papers/hamilton/hamilton.pdf),” at *21st Large Installation
System Administration Conference* (LISA), November 2007.
29. Brian Foote and Joseph Yoder: “[Big Ball of Mud](http://www.laputan.org/pub/foote/mud.pdf),” at *4th Conference on Pattern Languages of Programs* (PLoP), September 1997.
30. Frederick P Brooks: “No Silver Bullet Essence and Accident in Software Engineering,” in *The Mythical Man-Month*, Anniversary edition, Addison-Wesley, 1995. ISBN: 978-0-201-83595-3
31. Ben Moseley and Peter Marks: “[Out of the Tar Pit](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.93.8928),” at *BCS Software Practice Advancement* (SPA), 2006.
32. Rich Hickey: “[Simple Made Easy](http://www.infoq.com/presentations/Simple-Made-Easy),” at *Strange Loop*, September 2011.
33. Hongyu Pei Breivold, Ivica Crnkovic, and Peter J. Eriksson: “[Analyzing Software Evolvability](http://www.mrtc.mdh.se/publications/1478.pdf),” at *32nd Annual IEEE International Computer Software and Applications Conference* (COMPSAC), July 2008. [doi:10.1109/COMPSAC.2008.50](http://dx.doi.org/10.1109/COMPSAC.2008.50)
------
| 上一章 | 目錄 | 下一章 |
| ----------------------------------- | ------------------------------- | ------------------------------------ |
| [第一部分:資料系統基礎](part-i.md) | [設計資料密集型應用](README.md) | [第二章:資料模型與查詢語言](ch2.md) |
| [第一部分:資料系統基礎](part-i.md) | [設計資料密集型應用](README.md) | [第二章:資料模型與查詢語言](ch2.md) |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,819 +1,409 @@
# 第六章:分割槽
# 第六章:分割槽
![](../img/ch6.png)
> 我們必須跳出電腦指令序列的窠臼。 敘述定義、描述元資料、梳理關係,而不是編寫過程。
>
> —— Grace Murray Hopper未來的計算機及其管理1962
>
-------------
[TOC]
在[第5章](ch5.md)中,我們討論了複製——即資料在不同節點上的副本,對於非常大的資料集,或非常高的吞吐量,僅僅進行復制是不夠的:我們需要將資料進行**分割槽partitions**,也稱為**分片sharding**[^i]。
[^i]: 正如本章所討論的,分割槽是一種有意將大型資料庫分解成小型資料庫的方式。它與 **網路分割槽network partitions, netsplits** 無關,這是節點之間網路故障的一種。我們將在[第8章](ch8.md)討論這些錯誤。
> #### 術語澄清
>
> 上文中的**分割槽(partition)**在MongoDBElasticsearch和Solr Cloud中被稱為**分片(shard)**在HBase中稱之為**區域(Region)**Bigtable中則是 **表塊tablet**Cassandra和Riak中是**虛節點vnode)**Couchbase中叫做**虛桶(vBucket)**。但是**分割槽(partitioning)** 是最約定俗成的叫法。
>
通常情況下,每條資料(每條記錄,每行或每個文件)屬於且僅屬於一個分割槽。有很多方法可以實現這一點,本章將進行深入討論。實際上,每個分割槽都是自己的小型資料庫,儘管資料庫可能支援同時進行多個分割槽的操作。
分割槽主要是為了**可伸縮性**。不同的分割槽可以放在不共享叢集中的不同節點上(參閱[第二部分](part-ii.md)關於[無共享架構](part-ii.md#無共享架構)的定義)。因此,大資料集可以分佈在多個磁碟上,並且查詢負載可以分佈在多個處理器上。
對於在單個分割槽上執行的查詢,每個節點可以獨立執行對自己的查詢,因此可以透過新增更多的節點來擴大查詢吞吐量。大型,複雜的查詢可能會跨越多個節點並行處理,儘管這也帶來了新的困難。
分割槽資料庫在20世紀80年代由Teradata和NonStop SQL【1】等產品率先推出最近因為NoSQL資料庫和基於Hadoop的資料倉庫重新被關注。有些系統是為事務性工作設計的有些系統則用於分析參閱“[事務處理還是分析](ch3.md#事務處理還是分析)”):這種差異會影響系統的運作方式,但是分割槽的基本原理均適用於這兩種工作方式。
在本章中,我們將首先介紹分割大型資料集的不同方法,並觀察索引如何與分割槽配合。然後我們將討論[分割槽再平衡rebalancing](#分割槽再平衡),如果想要新增或刪除叢集中的節點,則必須進行再平衡。最後,我們將概述資料庫如何將請求路由到正確的分割槽並執行查詢。
## 分割槽與複製
分割槽通常與複製結合使用,使得每個分割槽的副本儲存在多個節點上。 這意味著,即使每條記錄屬於一個分割槽,它仍然可以儲存在多個不同的節點上以獲得容錯能力。
一個節點可能儲存多個分割槽。 如果使用主從複製模型,則分割槽和複製的組合如[圖6-1](../img/fig6-1.png)所示。 每個分割槽領導者(主)被分配給一個節點,追隨者(從)被分配給其他節點。 每個節點可能是某些分割槽的領導者,同時是其他分割槽的追隨者。
我們在[第5章](ch5.md)討論的關於資料庫複製的所有內容同樣適用於分割槽的複製。 大多數情況下,分割槽方案的選擇與複製方案的選擇是獨立的,為簡單起見,本章中將忽略複製。
![](../img/fig6-1.png)
**圖6-1 組合使用複製和分割槽:每個節點充當某些分割槽的領導者,其他分割槽充當追隨者。**
## 鍵值資料的分割槽
假設你有大量資料並且想要分割槽,如何決定在哪些節點上儲存哪些記錄呢?
分割槽目標是將資料和查詢負載均勻分佈在各個節點上。如果每個節點公平分享資料和負載那麼理論上10個節點應該能夠處理10倍的資料量和10倍的單個節點的讀寫吞吐量暫時忽略複製
如果分割槽是不公平的,一些分割槽比其他分割槽有更多的資料或查詢,我們稱之為**偏斜skew**。資料偏斜的存在使分割槽效率下降很多。在極端的情況下所有的負載可能壓在一個分割槽上其餘9個節點空閒的瓶頸落在這一個繁忙的節點上。不均衡導致的高負載的分割槽被稱為**熱點hot spot**。
避免熱點最簡單的方法是將記錄隨機分配給節點。這將在所有節點上平均分配資料,但是它有一個很大的缺點:當你試圖讀取一個特定的值時,你無法知道它在哪個節點上,所以你必須並行地查詢所有的節點。
我們可以做得更好。現在假設您有一個簡單的鍵值資料模型,其中您總是透過其主鍵訪問記錄。例如,在一本老式的紙質百科全書中,你可以透過標題來查詢一個條目;由於所有條目按字母順序排序,因此您可以快速找到您要查詢的條目。
### 根據鍵的範圍分割槽
一種分割槽的方法是為每個分割槽指定一塊連續的鍵範圍(從最小值到最大值),如紙質百科全書的卷([圖6-2](../img/fig6-2.png))。如果知道範圍之間的邊界,則可以輕鬆確定哪個分割槽包含某個值。如果您還知道分割槽所在的節點,那麼可以直接向相應的節點發出請求(對於百科全書而言,就像從書架上選取正確的書籍)。
![](../img/fig6-2.png)
**圖6-2 印刷版百科全書按照關鍵字範圍進行分割槽**
鍵的範圍不一定均勻分佈,因為資料也很可能不均勻分佈。例如在[圖6-2](../img/fig6-2.png)中第1捲包含以A和B開頭的單詞但第12卷則包含以TUVXY和Z開頭的單詞。只是簡單的規定每個捲包含兩個字母會導致一些卷比其他卷大。為了均勻分配資料分割槽邊界需要依據資料調整。
分割槽邊界可以由管理員手動選擇,也可以由資料庫自動選擇(我們會在“[分割槽再平衡](#分割槽再平衡)”中更詳細地討論分割槽邊界的選擇)。 Bigtable使用了這種分割槽策略以及其開源等價物HBase 【2, 3】RethinkDB和2.4版本之前的MongoDB 【4】。
在每個分割槽中,我們可以按照一定的順序儲存鍵(參見“[SSTables和LSM樹](ch3.md#SSTables和LSM樹)”)。好處是進行範圍掃描非常簡單,您可以將鍵作為聯合索引來處理,以便在一次查詢中獲取多個相關記錄(參閱“[多列索引](ch3.md#多列索引)”)。例如,假設我們有一個程式來儲存感測器網路的資料,其中主鍵是測量的時間戳(年月日時分秒)。範圍掃描在這種情況下非常有用,因為我們可以輕鬆獲取某個月份的所有資料。
然而Key Range分割槽的缺點是某些特定的訪問模式會導致熱點。 如果主鍵是時間戳,則分割槽對應於時間範圍,例如,給每天分配一個分割槽。 不幸的是由於我們在測量發生時將資料從感測器寫入資料庫因此所有寫入操作都會轉到同一個分割槽即今天的分割槽這樣分割槽可能會因寫入而過載而其他分割槽則處於空閒狀態【5】。
為了避免感測器資料庫中的這個問題,需要使用除了時間戳以外的其他東西作為主鍵的第一個部分。 例如,可以在每個時間戳前新增感測器名稱,這樣會首先按感測器名稱,然後按時間進行分割槽。 假設有多個感測器同時執行,寫入負載將最終均勻分佈在不同分割槽上。 現在,當想要在一個時間範圍內獲取多個感測器的值時,您需要為每個感測器名稱執行一個單獨的範圍查詢。
### 根據鍵的雜湊分割槽
由於偏斜和熱點的風險,許多分散式資料儲存使用雜湊函式來確定給定鍵的分割槽。
一個好的雜湊函式可以將偏斜的資料均勻分佈。假設你有一個32位雜湊函式,無論何時給定一個新的字串輸入它將返回一個0到$2^{32}$ -1之間的“隨機”數。即使輸入的字串非常相似它們的雜湊也會均勻分佈在這個數字範圍內。
出於分割槽的目的雜湊函式不需要多麼強壯的加密演算法例如Cassandra和MongoDB使用MD5Voldemort使用Fowler-Noll-Vo函式。許多程式語言都有內建的簡單雜湊函式它們用於雜湊表但是它們可能不適合分割槽例如在Java的`Object.hashCode()`和Ruby的`Object#hash`同一個鍵可能在不同的程序中有不同的雜湊值【6】。
一旦你有一個合適的鍵雜湊函式,你可以為每個分割槽分配一個雜湊範圍(而不是鍵的範圍),每個透過雜湊雜湊落在分割槽範圍內的鍵將被儲存在該分割槽中。如[圖6-3](../img/fig6-3.png)所示。
![](../img/fig6-3.png)
**圖6-3 按雜湊鍵分割槽**
這種技術擅長在分割槽之間公平地分配鍵。分割槽邊界可以是均勻間隔的,也可以是偽隨機選擇的(在這種情況下,該技術有時也被稱為**一致性雜湊consistent hashing**)。
> #### 一致性雜湊
>
> 一致性雜湊由Karger等人定義。【7】 用於跨網際網路級別的快取系統例如CDN中是一種能均勻分配負載的方法。它使用隨機選擇的 **分割槽邊界partition boundaries** 來避免中央控制或分散式共識的需要。 請注意,這裡的一致性與複製一致性(請參閱[第5章](ch5.md)或ACID一致性參閱[第7章](ch7.md)無關而只是描述了一種重新平衡reblancing的特定方法。
>
> 正如我們將在“[分割槽再平衡](#分割槽再平衡)”中所看到的,這種特殊的方法對於資料庫實際上並不是很好,所以在實際中很少使用(某些資料庫的文件仍然會使用一致性雜湊的說法,但是它往往是不準確的)。 因為有可能產生混淆,所以最好避免使用一致性雜湊這個術語,而只是把它稱為**雜湊分割槽hash partitioning**。
不幸的是透過使用鍵雜湊進行分割槽我們失去了鍵範圍分割槽的一個很好的屬性高效執行範圍查詢的能力。曾經相鄰的鍵現在分散在所有分割槽中所以它們之間的順序就丟失了。在MongoDB中如果您使用了基於雜湊的分割槽模式則任何範圍查詢都必須傳送到所有分割槽【4】。Riak 【9】Couchbase 【10】或Voldemort不支援主鍵上的範圍查詢。
Cassandra採取了折衷的策略【11, 12, 13】。 Cassandra中的表可以使用由多個列組成的複合主鍵來宣告。鍵中只有第一列會作為雜湊的依據而其他列則被用作Casssandra的SSTables中排序資料的連線索引。儘管查詢無法在複合主鍵的第一列中按範圍掃表但如果第一列已經指定了固定值則可以對該鍵的其他列執行有效的範圍掃描。
組合索引方法為一對多關係提供了一個優雅的資料模型。例如,在社交媒體網站上,一個使用者可能會發布很多更新。如果更新的主鍵被選擇為`(user_id, update_timestamp)`,那麼您可以有效地檢索特定使用者在某個時間間隔內按時間戳排序的所有更新。不同的使用者可以儲存在不同的分割槽上,對於每個使用者,更新按時間戳順序儲存在單個分割槽上。
### 負載偏斜與熱點消除
如前所述,雜湊分割槽可以幫助減少熱點。但是,它不能完全避免它們:在極端情況下,所有的讀寫操作都是針對同一個鍵的,所有的請求都會被路由到同一個分割槽。
這種場景也許並不常見但並非聞所未聞例如在社交媒體網站上一個擁有數百萬追隨者的名人使用者在做某事時可能會引發一場風暴【14】。這個事件可能導致同一個鍵的大量寫入鍵可能是名人的使用者ID或者人們正在評論的動作的ID。雜湊策略不起作用因為兩個相同ID的雜湊值仍然是相同的。
如今大多數資料系統無法自動補償這種高度偏斜的負載因此應用程式有責任減少偏斜。例如如果一個主鍵被認為是非常火爆的一個簡單的方法是在主鍵的開始或結尾新增一個隨機數。只要一個兩位數的十進位制隨機數就可以將主鍵分散為100種不同的主鍵,從而儲存在不同的分割槽中。
然而將主鍵進行分割之後任何讀取都必須要做額外的工作因為他們必須從所有100個主鍵分佈中讀取資料並將其合併。此技術還需要額外的記錄只需要對少量熱點附加隨機數對於寫入吞吐量低的絕大多數主鍵來說是不必要的開銷。因此您還需要一些方法來跟蹤哪些鍵需要被分割。
也許在將來,資料系統將能夠自動檢測和補償偏斜的工作負載;但現在,您需要自己來權衡。
## 分割槽與次級索引
到目前為止,我們討論的分割槽方案依賴於鍵值資料模型。如果只通過主鍵訪問記錄,我們可以從該鍵確定分割槽,並使用它來將讀寫請求路由到負責該鍵的分割槽。
如果涉及次級索引,情況會變得更加複雜(參考“[其他索引結構](ch3.md#其他索引結構)”。輔助索引通常並不能唯一地標識記錄而是一種搜尋記錄中出現特定值的方式查詢使用者123的所有操作查詢包含詞語`hogwash`的所有文章,查詢所有顏色為紅色的車輛等等。
次級索引是關係型資料庫的基礎並且在文件資料庫中也很普遍。許多鍵值儲存如HBase和Volde-mort為了減少實現的複雜度而放棄了次級索引但是一些如Riak已經開始新增它們因為它們對於資料模型實在是太有用了。並且次級索引也是Solr和Elasticsearch等搜尋伺服器的基石。
次級索引的問題是它們不能整齊地對映到分割槽。有兩種用二級索引對資料庫進行分割槽的方法:**基於文件的分割槽document-based**和**基於關鍵詞term-based的分割槽**。
### 基於文件的二級索引進行分割槽
假設你正在經營一個銷售二手車的網站(如[圖6-4](../img/fig6-4.png)所示)。 每個列表都有一個唯一的ID——稱之為文件ID——並且用文件ID對資料庫進行分割槽例如分割槽0中的ID 0到499分割槽1中的ID 500到999等
你想讓使用者搜尋汽車,允許他們透過顏色和廠商過濾,所以需要一個在顏色和廠商上的次級索引(文件資料庫中這些是**欄位field**,關係資料庫中這些是**列column** )。 如果您聲明瞭索引,則資料庫可以自動執行索引[^ii]。例如,無論何時將紅色汽車新增到資料庫,資料庫分割槽都會自動將其新增到索引條目`color:red`的文件ID列表中。
[^ii]: 如果資料庫僅支援鍵值模型則你可能會嘗試在應用程式程式碼中建立從值到文件ID的對映來實現輔助索引。 如果沿著這條路線走下去,請萬分小心,確保您的索引與底層資料保持一致。 競爭條件和間歇性寫入失敗(其中一些更改已儲存,但其他更改未儲存)很容易導致資料不同步 - 參見“[多物件事務的需求](ch7.md#多物件事務的需求)”。
![](../img/fig6-4.png)
**圖6-4 基於文件的二級索引進行分割槽**
在這種索引方法中每個分割槽是完全獨立的每個分割槽維護自己的二級索引僅覆蓋該分割槽中的文件。它不關心儲存在其他分割槽的資料。無論何時您需要寫入資料庫新增刪除或更新文件只需處理包含您正在編寫的文件ID的分割槽即可。出於這個原因**文件分割槽索引**也被稱為**本地索引local index**(而不是將在下一節中描述的**全域性索引global index**)。
但是從文件分割槽索引中讀取需要注意除非您對文件ID做了特別的處理否則沒有理由將所有具有特定顏色或特定品牌的汽車放在同一個分割槽中。在[圖6-4](../img/fig6-4.png)中紅色汽車出現在分割槽0和分割槽1中。因此如果要搜尋紅色汽車則需要將查詢傳送到所有分割槽併合並所有返回的結果。
這種查詢分割槽資料庫的方法有時被稱為**分散/聚集scatter/gather**,並且可能會使二級索引上的讀取查詢相當昂貴。即使並行查詢分割槽,分散/聚集也容易導致尾部延遲放大(參閱“[實踐中的百分位點](ch1.md#實踐中的百分位點)”。然而它被廣泛使用MongoDBRiak 【15】Cassandra 【16】Elasticsearch 【17】SolrCloud 【18】和VoltDB 【19】都使用文件分割槽二級索引。大多數資料庫供應商建議您構建一個能從單個分割槽提供二級索引查詢的分割槽方案但這並不總是可行尤其是當在單個查詢中使用多個二級索引時例如同時需要按顏色和製造商查詢
### 基於關鍵詞(Term)的二級索引進行分割槽
我們可以構建一個覆蓋所有分割槽資料的**全域性索引**,而不是給每個分割槽建立自己的次級索引(本地索引)。但是,我們不能只把這個索引儲存在一個節點上,因為它可能會成為瓶頸,違背了分割槽的目的。全域性索引也必須進行分割槽,但可以採用與主鍵不同的分割槽方式。
[圖6-5](../img/fig6-5.png)描述了這可能是什麼樣子:來自所有分割槽的紅色汽車在紅色索引中,並且索引是分割槽的,首字母從`a`到`r`的顏色在分割槽0中`s`到`z`的在分割槽1。汽車製造商的索引也與之類似分割槽邊界在`f`和`h`之間)。
![](../img/fig6-5.png)
**圖6-5 基於關鍵詞對二級索引進行分割槽**
我們將這種索引稱為**關鍵詞分割槽term-partitioned**,因為我們尋找的關鍵詞決定了索引的分割槽方式。例如,一個關鍵詞可能是:`color:red`。**關鍵詞(Term)** 這個名稱來源於全文搜尋索引(一種特殊的次級索引),指文件中出現的所有單詞。
和之前一樣,我們可以透過**關鍵詞**本身或者它的雜湊進行索引分割槽。根據關鍵詞本身來分割槽對於範圍掃描非常有用(例如對於數值類的屬性,像汽車的報價),而對關鍵詞的雜湊分割槽提供了負載均衡的能力。
關鍵詞分割槽的全域性索引優於文件分割槽索引的地方點是它可以使讀取更有效率:不需要**分散/收集**所有分割槽,客戶端只需要向包含關鍵詞的分割槽發出請求。全域性索引的缺點在於寫入速度較慢且較為複雜,因為寫入單個文件現在可能會影響索引的多個分割槽(文件中的每個關鍵詞可能位於不同的分割槽或者不同的節點上) 。
理想情況下,索引總是最新的,寫入資料庫的每個文件都會立即反映在索引中。但是,在關鍵詞分割槽索引中,這需要跨分割槽的分散式事務,並不是所有資料庫都支援(請參閱[第7章](ch7.md)和[第9章](ch9.md))。
在實踐中,對全域性二級索引的更新通常是**非同步**的也就是說如果在寫入之後不久讀取索引剛才所做的更改可能尚未反映在索引中。例如Amazon DynamoDB聲稱在正常情況下其全域性次級索引會在不到一秒的時間內更新但在基礎架構出現故障的情況下可能會有延遲【20】。
全域性關鍵詞分割槽索引的其他用途包括Riak的搜尋功能【21】和Oracle資料倉庫它允許您在本地和全域性索引之間進行選擇【22】。我們將在[第12章](ch12.md)中繼續關鍵詞分割槽二級索引實現的話題。
## 分割槽再平衡
隨著時間的推移,資料庫會有各種變化:
* 查詢吞吐量增加所以您想要新增更多的CPU來處理負載。
* 資料集大小增加所以您想新增更多的磁碟和RAM來儲存它。
* 機器出現故障,其他機器需要接管故障機器的責任。
所有這些更改都需要資料和請求從一個節點移動到另一個節點。 將負載從叢集中的一個節點向另一個節點移動的過程稱為**再平衡rebalancing**。
無論使用哪種分割槽方案,再平衡通常都要滿足一些最低要求:
* 再平衡之後,負載(資料儲存,讀取和寫入請求)應該在叢集中的節點之間公平地共享。
* 再平衡發生時,資料庫應該繼續接受讀取和寫入。
* 節點之間只移動必須的資料以便快速再平衡並減少網路和磁碟I/O負載。
### 再平衡策略
有幾種不同的分割槽分配方法【23】,讓我們依次簡要討論一下。
#### 反面教材hash mod N
我們在前面說過([圖6-3](../img/fig6-3.png)),最好將可能的雜湊分成不同的範圍,並將每個範圍分配給一個分割槽(例如,如果$0≤hash(key)<b_0$則將鍵分配給分割槽0如果$b_0 hash(key) <b_1$則分配給分割槽1
也許你想知道為什麼我們不使用 ***取模mod***(許多程式語言中的%運算子)。例如,`hash(key) mod 10`會返回一個介於0和9之間的數字如果我們將雜湊寫為十進位制數雜湊模10將是最後一個數字。如果我們有10個節點編號為0到9這似乎是將每個鍵分配給一個節點的簡單方法。
模N$mod N$方法的問題是如果節點數量N發生變化大多數金鑰將需要從一個節點移動到另一個節點。例如假設$hash(key)=123456$。如果最初有10個節點那麼這個鍵一開始放在節點6上因為$123456\ mod\ 10 = 6$。當您增長到11個節點時金鑰需要移動到節點3$123456\ mod\ 11 = 3$當您增長到12個節點時需要移動到節點0$123456\ mod\ 12 = 0$)。這種頻繁的舉動使得重新平衡過於昂貴。
我們需要一種只移動必需資料的方法。
#### 固定數量的分割槽
幸運的是有一個相當簡單的解決方案建立比節點更多的分割槽併為每個節點分配多個分割槽。例如執行在10個節點的叢集上的資料庫可能會從一開始就被拆分為1,000個分割槽因此大約有100個分割槽被分配給每個節點。
現在,如果一個節點被新增到叢集中,新節點可以從當前每個節點中**竊取**一些分割槽,直到分割槽再次公平分配。這個過程如[圖6-6](../img/fig6-6.png)所示。如果從叢集中刪除一個節點,則會發生相反的情況。
只有分割槽在節點之間的移動。分割槽的數量不會改變,鍵所指定的分割槽也不會改變。唯一改變的是分割槽所在的節點。這種變更並不是即時的 — 在網路上傳輸大量的資料需要一些時間 — 所以在傳輸過程中,原有分割槽仍然會接受讀寫操作。
![](../img/fig6-6.png)
**圖6-6 將新節點新增到每個節點具有多個分割槽的資料庫群集。**
**圖6-6 將新節點新增到每個節點具有多個分割槽的資料庫叢集。**
原則上您甚至可以解決叢集中的硬體不匹配問題透過為更強大的節點分配更多的分割槽可以強制這些節點承載更多的負載。在Riak 【15】Elasticsearch 【24】Couchbase 【10】和Voldemort 【25】中使用了這種再平衡的方法。
在這種配置中,分割槽的數量通常在資料庫第一次建立時確定,之後不會改變。雖然原則上可以分割和合並分割槽(請參閱下一節),但固定數量的分割槽在操作上更簡單,因此許多固定分割槽資料庫選擇不實施分割槽分割。因此,一開始配置的分割槽數就是您可以擁有的最大節點數量,所以您需要選擇足夠多的分割槽以適應未來的增長。但是,每個分割槽也有管理開銷,所以選擇太大的數字會適得其反。
如果資料集的總大小難以預估(例如,可能它開始很小,但隨著時間的推移會變得更大),選擇正確的分割槽數是困難的。由於每個分割槽包含了總資料量固定比率的資料,因此每個分割槽的大小與叢集中的資料總量成比例增長。如果分割槽非常大,再平衡和從節點故障恢復變得昂貴。但是,如果分割槽太小,則會產生太多的開銷。當分割槽大小“恰到好處”的時候才能獲得很好的效能,如果分割槽數量固定,但資料量變動很大,則難以達到最佳效能。
#### 動態分割槽
對於使用鍵範圍分割槽的資料庫(參閱“[根據鍵的範圍分割槽](#根據鍵的範圍分割槽)”),具有固定邊界的固定數量的分割槽將非常不便:如果出現邊界錯誤,則可能會導致一個分割槽中的所有資料或者其他分割槽中的所有資料為空。手動重新配置分割槽邊界將非常繁瑣。
出於這個原因按鍵的範圍進行分割槽的資料庫如HBase和RethinkDB會動態建立分割槽。當分割槽增長到超過配置的大小時在HBase上預設值是10GB會被分成兩個分割槽每個分割槽約佔一半的資料【26】。與之相反如果大量資料被刪除並且分割槽縮小到某個閾值以下則可以將其與相鄰分割槽合併。此過程與B樹頂層發生的過程類似參閱“[B樹](ch3.md#B樹)”)。
每個分割槽分配給一個節點每個節點可以處理多個分割槽就像固定數量的分割槽一樣。大型分割槽拆分後可以將其中的一半轉移到另一個節點以平衡負載。在HBase中分割槽檔案的傳輸透過HDFS底層使用的分散式檔案系統來實現【3】。
動態分割槽的一個優點是分割槽數量適應總資料量。如果只有少量的資料少量的分割槽就足夠了所以開銷很小如果有大量的資料每個分割槽的大小被限制在一個可配置的最大值【23】。
需要注意的是一個空的資料庫從一個分割槽開始因為沒有關於在哪裡繪製分割槽邊界的先驗資訊。資料集開始時很小直到達到第一個分割槽的分割點所有寫入操作都必須由單個節點處理而其他節點則處於空閒狀態。為了解決這個問題HBase和MongoDB允許在一個空的資料庫上配置一組初始分割槽這被稱為**預分割pre-splitting**。在鍵範圍分割槽的情況中預分割需要提前知道鍵是如何進行分配的【4,26】。
動態分割槽不僅適用於資料的範圍分割槽而且也適用於雜湊分割槽。從版本2.4開始MongoDB同時支援範圍和雜湊分割槽並且都支援動態分割分割槽。
#### 按節點比例分割槽
透過動態分割槽,分割槽的數量與資料集的大小成正比,因為拆分和合並過程將每個分割槽的大小保持在固定的最小值和最大值之間。另一方面,對於固定數量的分割槽,每個分割槽的大小與資料集的大小成正比。在這兩種情況下,分割槽的數量都與節點的數量無關。
Cassandra和Ketama使用的第三種方法是使分割槽數與節點數成正比——換句話說每個節點具有固定數量的分割槽【23,27,28】。在這種情況下每個分割槽的大小與資料集大小成比例地增長而節點數量保持不變但是當增加節點數時分割槽將再次變小。由於較大的資料量通常需要較大數量的節點進行儲存因此這種方法也使每個分割槽的大小較為穩定。
當一個新節點加入叢集時它隨機選擇固定數量的現有分割槽進行拆分然後佔有這些拆分分割槽中每個分割槽的一半同時將每個分割槽的另一半留在原地。隨機化可能會產生不公平的分割但是平均在更大數量的分割槽上時在Cassandra中預設情況下每個節點有256個分割槽新節點最終從現有節點獲得公平的負載份額。 Cassandra 3.0引入了另一種再平衡的演算法來避免不公平的分割【29】。
隨機選擇分割槽邊界要求使用基於雜湊的分割槽可以從雜湊函式產生的數字範圍中挑選邊界。實際上這種方法最符合一致性雜湊的原始定義【7】參閱“[一致性雜湊](#一致性雜湊)”。最新的雜湊函式可以在較低元資料開銷的情況下達到類似的效果【8】。
### 運維:手動還是自動再平衡
關於再平衡有一個重要問題:自動還是手動進行?
在全自動重新平衡系統自動決定何時將分割槽從一個節點移動到另一個節點無須人工干預和完全手動分割槽指派給節點由管理員明確配置僅在管理員明確重新配置時才會更改之間有一個權衡。例如CouchbaseRiak和Voldemort會自動生成建議的分割槽分配但需要管理員提交才能生效。
全自動重新平衡可以很方便,因為正常維護的操作工作較少。但是,這可能是不可預測的。再平衡是一個昂貴的操作,因為它需要重新路由請求並將大量資料從一個節點移動到另一個節點。如果沒有做好,這個過程可能會使網路或節點負載過重,降低其他請求的效能。
這種自動化與自動故障檢測相結合可能十分危險。例如,假設一個節點過載,並且對請求的響應暫時很慢。其他節點得出結論:過載的節點已經死亡,並自動重新平衡叢集,使負載離開它。這會對已經超負荷的節點,其他節點和網路造成額外的負載,從而使情況變得更糟,並可能導致級聯失敗。
出於這個原因,再平衡的過程中有人参與是一件好事。這比完全自動的過程慢,但可以幫助防止運維意外。
## 請求路由
現在我們已經將資料集分割到多個機器上執行的多個節點上。但是仍然存在一個懸而未決的問題當客戶想要發出請求時如何知道要連線哪個節點隨著分割槽重新平衡分割槽對節點的分配也發生變化。為了回答這個問題需要有人知曉這些變化如果我想讀或寫鍵“foo”需要連線哪個IP地址和埠號
這個問題可以概括為 **服務發現(service discovery)** 它不僅限於資料庫。任何可透過網路訪問的軟體都有這個問題特別是如果它的目標是高可用性在多臺機器上執行冗餘配置。許多公司已經編寫了自己的內部服務發現工具其中許多已經作為開源釋出【30】。
概括來說這個問題有幾種不同的方案如圖6-7所示:
1. 允許客戶聯絡任何節點(例如,透過**迴圈策略的負載均衡Round-Robin Load Balancer**)。如果該節點恰巧擁有請求的分割槽,則它可以直接處理該請求;否則,它將請求轉發到適當的節點,接收回復並傳遞給客戶端。
2. 首先將所有來自客戶端的請求傳送到路由層,它決定了應該處理請求的節點,並相應地轉發。此路由層本身不處理任何請求;它僅負責分割槽的負載均衡。
3. 要求客戶端知道分割槽和節點的分配。在這種情況下,客戶端可以直接連線到適當的節點,而不需要任何中介。
以上所有情況中的關鍵問題是:作出路由決策的元件(可能是節點之一,還是路由層或客戶端)如何瞭解分割槽-節點之間的分配關係變化?
![](../img/fig6-7.png)
**圖6-7 將請求路由到正確節點的三種不同方式。**
這是一個具有挑戰性的問題,因為重要的是所有參與者都同意 - 否則請求將被髮送到錯誤的節點,得不到正確的處理。 在分散式系統中有達成共識的協議,但很難正確地實現(見[第9章](ch9.md))。
許多分散式資料系統都依賴於一個獨立的協調服務比如ZooKeeper來跟蹤叢集元資料如[圖6-8](../img/fig6-8.png)所示。 每個節點在ZooKeeper中註冊自己ZooKeeper維護分割槽到節點的可靠對映。 其他參與者如路由層或分割槽感知客戶端可以在ZooKeeper中訂閱此資訊。 只要分割槽分配發生了改變或者叢集中新增或刪除了一個節點ZooKeeper就會通知路由層使路由資訊保持最新狀態。
![](../img/fig6-8.png)
**圖6-8 使用ZooKeeper跟蹤分割槽分配給節點。**
例如LinkedIn的Espresso使用Helix 【31】進行叢集管理依靠ZooKeeper實現瞭如[圖6-8](../img/fig6-8.png)所示的路由層。 HBaseSolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。 MongoDB具有類似的體系結構但它依賴於自己的**配置伺服器config server** 實現和mongos守護程序作為路由層。
Cassandra和Riak採取不同的方法他們在節點之間使用**流言協議gossip protocol** 來傳播群集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖6-7](../img/fig6-7.png)中的方法1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像ZooKeeper這樣的外部協調服務的依賴。
Cassandra和Riak採取不同的方法他們在節點之間使用**流言協議gossip protocol** 來傳播叢集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖6-7](../img/fig6-7.png)中的方法1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像ZooKeeper這樣的外部協調服務的依賴。
Couchbase不會自動重新平衡這簡化了設計。通常情況下它配置了一個名為moxi的路由層它會從叢集節點了解路由變化【32】。
當使用路由層或向隨機節點發送請求時客戶端仍然需要找到要連線的IP地址。這些地址並不像分割槽的節點分佈變化的那麼快所以使用DNS通常就足夠了。
### 執行並行查詢
到目前為止,我們只關注讀取或寫入單個鍵的非常簡單的查詢(加上基於文件分割槽的二級索引場景下的分散/聚集查詢。這也是大多數NoSQL分散式資料儲存所支援的訪問層級。
然而,通常用於分析的**大規模並行處理MPP, Massively parallel processing** 關係型資料庫產品在其支援的查詢型別方面要複雜得多。一個典型的資料倉庫查詢包含多個連線,過濾,分組和聚合操作。 MPP查詢最佳化器將這個複雜的查詢分解成許多執行階段和分割槽其中許多可以在資料庫叢集的不同節點上並行執行。涉及掃描大規模資料集的查詢特別受益於這種並行執行。
資料倉庫查詢的快速並行執行是一個專門的話題,由於分析有很重要的商業意義,可以帶來很多利益。我們將在[第10章](ch10.md)討論並行查詢執行的一些技巧。有關並行資料庫中使用的技術的更詳細的概述請參閱參考文獻【1,33】。
## 本章小結
在本章中,我們探討了將大資料集劃分成更小的子集的不同方法。資料量非常大的時候,在單臺機器上儲存和處理不再可行,而分割槽則十分必要。分割槽的目標是在多臺機器上均勻分佈資料和查詢負載,避免出現熱點(負載不成比例的節點)。這需要選擇適合於您的資料的分割槽方案,並在將節點新增到叢集或從叢集刪除時進行分割槽再平衡。
我們討論了兩種主要的分割槽方法:
***鍵範圍分割槽***
其中鍵是有序的,並且分割槽擁有從某個最小值到某個最大值的所有鍵。排序的優勢在於可以進行有效的範圍查詢,但是如果應用程式經常訪問相鄰的鍵,則存在熱點的風險。
在這種方法中,當分割槽變得太大時,通常將分割槽分成兩個子分割槽,動態地再平衡分割槽。
***雜湊分割槽***
雜湊函式應用於每個鍵,分割槽擁有一定範圍的雜湊。這種方法破壞了鍵的排序,使得範圍查詢效率低下,但可以更均勻地分配負載。
透過雜湊進行分割槽時,通常先提前建立固定數量的分割槽,為每個節點分配多個分割槽,並在新增或刪除節點時將整個分割槽從一個節點移動到另一個節點。也可以使用動態分割槽。
兩種方法搭配使用也是可行的,例如使用複合主鍵:使用鍵的一部分來標識分割槽,而使用另一部分作為排序順序。
我們還討論了分割槽和二級索引之間的相互作用。次級索引也需要分割槽,有兩種方法:
* 基於文件分割槽(本地索引),其中二級索引儲存在與主鍵和值相同的分割槽中。這意味著只有一個分割槽需要在寫入時更新,但是讀取二級索引需要在所有分割槽之間進行分散/收集。
* 基於關鍵詞分割槽(全域性索引),其中二級索引存在不同的分割槽中。輔助索引中的條目可以包括來自主鍵的所有分割槽的記錄。當文件寫入時,需要更新多個分割槽中的二級索引;但是可以從單個分割槽中進行讀取。
最後,我們討論了將查詢路由到適當的分割槽的技術,從簡單的分割槽負載平衡到複雜的並行查詢執行引擎。
按照設計,多數情況下每個分割槽是獨立執行的 — 這就是分割槽資料庫可以伸縮到多臺機器的原因。但是,需要寫入多個分割槽的操作結果可能難以預料:例如,如果寫入一個分割槽成功,但另一個分割槽失敗,會發生什麼情況?我們將在下面的章節中討論這個問題。
參考文獻
--------------------
1. David J. DeWitt and Jim N. Gray: “[Parallel Database Systems: The Future of High Performance Database Systems](),”
*Communications of the ACM*, volume 35, number 6, pages 8598, June 1992. [doi:10.1145/129888.129894](http://dx.doi.org/10.1145/129888.129894)
2. Lars George: “[HBase vs. BigTable Comparison](http://www.larsgeorge.com/2009/11/hbase-vs-bigtable-comparison.html),” *larsgeorge.com*, November 2009.
3. “[The Apache HBase Reference Guide](https://hbase.apache.org/book/book.html),” Apache Software Foundation, *hbase.apache.org*, 2014.
4. MongoDB, Inc.: “[New Hash-Based Sharding Feature in MongoDB 2.4](http://blog.mongodb.org/post/47633823714/new-hash-based-sharding-feature-in-mongodb-24),” *blog.mongodb.org*, April 10, 2013.
5. Ikai Lan: “[App Engine Datastore Tip: Monotonically Increasing Values Are Bad](http://ikaisays.com/2011/01/25/app-engine-datastore-tip-monotonically-increasing-values-are-bad/),” *ikaisays.com*,
January 25, 2011.
6. Martin Kleppmann: “[Java's hashCode Is Not Safe for Distributed Systems](http://martin.kleppmann.com/2012/06/18/java-hashcode-unsafe-for-distributed-systems.html),” *martin.kleppmann.com*, June 18, 2012.
7. David Karger, Eric Lehman, Tom Leighton, et al.: “[Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web](http://www.akamai.com/dl/technical_publications/ConsistenHashingandRandomTreesDistributedCachingprotocolsforrelievingHotSpotsontheworldwideweb.pdf),” at *29th Annual ACM Symposium on Theory of Computing* (STOC), pages 654663, 1997. [doi:10.1145/258533.258660](http://dx.doi.org/10.1145/258533.258660)
8. John Lamping and Eric Veach: “[A Fast, Minimal Memory, Consistent Hash Algorithm](http://arxiv.org/pdf/1406.2294v1.pdf),” *arxiv.org*, June 2014.
9. Eric Redmond: “[A Little Riak Book](http://littleriakbook.com/),” Version 1.4.0, Basho Technologies, September 2013.
10. “[Couchbase 2.5 Administrator Guide](http://docs.couchbase.com/couchbase-manual-2.5/cb-admin/),” Couchbase, Inc., 2014.
11. Avinash Lakshman and Prashant Malik: “[Cassandra A Decentralized Structured Storage System](http://www.cs.cornell.edu/Projects/ladis2009/papers/Lakshman-ladis2009.PDF),” at *3rd ACM SIGOPS International Workshop on
Large Scale Distributed Systems and Middleware* (LADIS), October 2009.
12. Jonathan Ellis: “[Facebooks Cassandra Paper, Annotated and Compared to Apache Cassandra 2.0](http://www.datastax.com/documentation/articles/cassandra/cassandrathenandnow.html),”
*datastax.com*, September 12, 2013.
13. “[Introduction to Cassandra Query Language](http://www.datastax.com/documentation/cql/3.1/cql/cql_intro_c.html),” DataStax, Inc., 2014.
14. Samuel Axon: “[3% of Twitter's Servers Dedicated to Justin Bieber](http://mashable.com/2010/09/07/justin-bieber-twitter/),” *mashable.com*, September 7, 2010.
15. “[Riak 1.4.8 Docs](http://docs.basho.com/riak/1.4.8/),” Basho Technologies, Inc., 2014.
16. Richard Low: “[The Sweet Spot for Cassandra Secondary Indexing](http://www.wentnet.com/blog/?p=77),” *wentnet.com*, October 21, 2013.
17. Zachary Tong: “[Customizing Your Document Routing](http://www.elasticsearch.org/blog/customizing-your-document-routing/),” *elasticsearch.org*, June 3, 2013.
18. “[Apache Solr Reference Guide](https://cwiki.apache.org/confluence/display/solr/Apache+Solr+Reference+Guide),” Apache Software Foundation, 2014.
19. Andrew Pavlo: “[H-Store Frequently Asked Questions](http://hstore.cs.brown.edu/documentation/faq/),” *hstore.cs.brown.edu*, October 2013.
20. “[Amazon DynamoDB Developer Guide](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/),” Amazon Web Services, Inc., 2014.
21. Rusty Klophaus: “[Difference Between 2I and Search](http://lists.basho.com/pipermail/riak-users_lists.basho.com/2011-October/006220.html),” email to *riak-users* mailing list, *lists.basho.com*, October 25, 2011.
22. Donald K. Burleson: “[Object Partitioning in Oracle](http://www.dba-oracle.com/art_partit.htm),”*dba-oracle.com*, November 8, 2000.
23. Eric Evans: “[Rethinking Topology in Cassandra](http://www.slideshare.net/jericevans/virtual-nodes-rethinking-topology-in-cassandra),” at *ApacheCon Europe*, November 2012.
24. Rafał Kuć: “[Reroute API Explained](http://elasticsearchserverbook.com/reroute-api-explained/),” *elasticsearchserverbook.com*, September 30, 2013.
25. “[Project Voldemort Documentation](http://www.project-voldemort.com/voldemort/),” *project-voldemort.com*.
26. Enis Soztutar: “[Apache HBase Region Splitting and Merging](http://hortonworks.com/blog/apache-hbase-region-splitting-and-merging/),” *hortonworks.com*, February 1, 2013.
27. Brandon Williams: “[Virtual Nodes in Cassandra 1.2](http://www.datastax.com/dev/blog/virtual-nodes-in-cassandra-1-2),” *datastax.com*, December 4, 2012.
28. Richard Jones: “[libketama: Consistent Hashing Library for Memcached Clients](https://www.metabrew.com/article/libketama-consistent-hashing-algo-memcached-clients),” *metabrew.com*, April 10, 2007.
29. Branimir Lambov: “[New Token Allocation Algorithm in Cassandra 3.0](http://www.datastax.com/dev/blog/token-allocation-algorithm),” *datastax.com*, January 28, 2016.
30. Jason Wilder: “[Open-Source Service Discovery](http://jasonwilder.com/blog/2014/02/04/service-discovery-in-the-cloud/),” *jasonwilder.com*, February 2014.
31. Kishore Gopalakrishna, Shi Lu, Zhen Zhang, et al.: “[Untangling Cluster Management with Helix](http://www.socc2012.org/helix_onecol.pdf?attredirects=0),” at *ACM Symposium on Cloud Computing* (SoCC), October 2012.
[doi:10.1145/2391229.2391248](http://dx.doi.org/10.1145/2391229.2391248)
32. “[Moxi 1.8 Manual](http://docs.couchbase.com/moxi-manual-1.8/),” Couchbase, Inc., 2014.
33. Shivnath Babu and Herodotos Herodotou: “[Massively Parallel Databases and MapReduce Systems](http://research.microsoft.com/pubs/206464/db-mr-survey-final.pdf),” *Foundations and Trends in Databases*, volume 5, number 1, pages 1104, November 2013.[doi:10.1561/1900000036](http://dx.doi.org/10.1561/1900000036)
------
| 上一章 | 目錄 | 下一章 |
| :--------------------: | :-----------------------------: | :--------------------: |
| [第五章:複製](ch5.md) | [設計資料密集型應用](README.md) | [第七章:事務](ch7.md) |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,35 @@
# 後記
## 關於作者
**Martin Kleppmann**是英國劍橋大學分散式系統的研究員。此前他曾在網際網路公司擔任過軟體工程師和企業家其中包括LinkedIn和Rapportive負責大規模資料基礎架構。在這個過程中他以艱難的方式學習了一些東西他希望這本書能夠讓你避免重蹈覆轍。
Martin是一位常規會議演講者博主和開源貢獻者。他認為每個人都應該有深刻的技術理念深層次的理解能幫助我們開發出更好的軟體。
![](http://martin.kleppmann.com/2017/03/ddia-poster.jpg)
## 關於譯者
[馮若航](https://vonng.com/about)
PostgreSQL DBA @ TanTan
Alibaba+-Finplus 架構師/全棧工程師 (2015 ~ 2017)
## 後記
《設計資料密集型應用》封面上的動物是**印度野豬Sus scrofa cristatus**,它是在印度、緬甸、尼泊爾、斯里蘭卡和泰國發現的一種野豬的亞種。與歐洲野豬不同,它們有更高的背部鬃毛,沒有體表絨毛,以及更大更直的頭骨。
印度野豬有一頭灰色或黑色的頭髮脊背上有短而硬的毛。雄性有突出的犬齒稱為T用來與對手戰鬥或抵禦掠食者。雄性比雌性大這些物種平均肩高33-35英寸體重200-300磅。他們的天敵包括熊、老虎和各種大型貓科動物。
這些動物夜行且雜食——它們吃各種各樣的東西包括根、昆蟲、腐肉、堅果、漿果和小動物。野豬經常因為破壞農作物的根被人們所熟知他們造成大量的破壞並被農民所敵視。他們每天需要攝入4,000 ~ 4,500卡路里的能量。野豬有發達的嗅覺這有助於尋找地下植物和挖掘動物。然而它們的視力很差。
野豬在人類文化中一直具有重要意義。在印度教傳說中,野豬是毗溼奴神的化身。在古希臘的喪葬紀念碑中,它是一個勇敢失敗者的象徵(與勝利的獅子相反)。由於它的侵略,它被描繪在斯堪的納維亞、日耳曼和盎格魯撒克遜戰士的盔甲和武器上。在中國十二生肖中,它象徵著決心和急躁。
O'Reilly封面上的許多動物都受到威脅這些動物對世界都很重要。要了解有關如何提供幫助的更多資訊請訪問animals.oreilly.com。
封面圖片來自Shaw's Zoology。封面字型是URW Typewriter和Guardian Sans。文字字型是Adobe Minion Pro圖中的字型是Adobe Myriad Pro標題字型是Adobe Myriad Condensed程式碼字型是Dalton Maag的Ubuntu Mono。
封面圖片來自Shaw's Zoology。封面字型是URW Typewriter和Guardian Sans。文字字型是Adobe Minion Pro圖中的字型是Adobe Myriad Pro標題字型是Adobe Myriad Condensed程式碼字型是Dalton Maag的Ubuntu Mono。

View File

@ -1,753 +1,377 @@
# 術語表 【DRAFT】
> 請注意,本術語表中的定義簡短而簡單,旨在傳達核心思想,而不是術語的完整細微之處。 有關更多詳細資訊,請參閱正文中的參考資料。
[TOC]
### 非同步asynchronous
不等待某些事情完成(例如,將資料傳送到網路中的另一個節點),並且不會假設要花多長時間。請參閱[同步複製與非同步複製](ch5.md#同步複製與非同步複製)”,“[同步網路與非同步網路](ch8.md#同步網路與非同步網路)”,以及“[系統模型與現實](ch8.md#系統模型與現實)”。
### 原子atomic
1.在併發操作的上下文中:描述一個在單個時間點看起來生效的操作,所以另一個併發程序永遠不會遇到處於“半完成”狀態的操作。另見隔離。
2.在事務的上下文中:將一些寫入操作分為一組,這組寫入要麼全部提交成功,要麼遇到錯誤時全部回滾。參見“[原子性Atomicity](ch7.md#原子性Atomicity)”和“[原子提交與二階段提交2PC](ch9.md#原子提交與二階段提交2PC)”。
### 背壓backpressure
接收方接收資料速度較慢時,強制降低傳送方的資料傳送速度。也稱為流量控制。請參閱“[訊息系統](ch11.md#訊息系統)”。
### 批處理batch process
一種計算,它將一些固定的(通常是大的)資料集作為輸入,並將其他一些資料作為輸出,而不修改輸入。見[第十章](ch10.md)。
### 邊界bounded
有一些已知的上限或大小。例如,網路延遲情況(請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”)和資料集(請參閱[第11章](ch11.md)的介紹)。
### 拜占庭故障Byzantine fault
表現異常的節點,這種異常可能以任意方式出現,例如向其他節點發送矛盾或惡意訊息。請參閱“[拜占庭故障](ch8.md#拜占庭故障)”。
### 快取cache
一種元件,透過儲存最近使用過的資料,加快未來對相同資料的讀取速度。快取中通常存放部分資料:因此,如果快取中缺少某些資料,則必須從某些底層較慢的資料儲存系統中,獲取完整的資料副本。
### CAP定理CAP theorem
一個被廣泛誤解的理論結果,在實踐中是沒有用的。參見“[CAP定理](ch9.md#CAP定理)”。
### 因果關係causality
事件之間的依賴關係,當一件事發生在另一件事情之前。例如,後面的事件是對早期事件的迴應,或者依賴於更早的事件,或者應該根據先前的事件來理解。請參閱“[“此前發生”的關係和併發](ch5.md#“此前發生”的關係和併發)”和“[順序與因果關係](ch5.md#順序與因果關係)”。
### 共識consensus
分散式計算的一個基本問題,就是讓幾個節點同意某些事情(例如,哪個節點應該是資料庫叢集的領導者)。問題比乍看起來要困難得多。請參閱“[容錯共識](ch9.md#容錯共識)”。
### 資料倉庫data warehouse
一個數據庫其中來自幾個不同的OLTP系統的資料已經被合併和準備用於分析目的。請參閱“[資料倉庫](ch3.md#資料倉庫)”。
### 宣告式declarative
描述某些東西應有的屬性,但不知道如何實現它的確切步驟。在查詢的上下文中,查詢最佳化器採用宣告性查詢並決定如何最好地執行它。請參閱“[資料查詢語言](ch2.md#資料查詢語言)”。
### 非規範化denormalize
為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。非規範化的值是一種預先計算的查詢結果,像物化檢視。請參見“[單物件和多物件操作](ch7.md#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](ch11.md#從同一事件日誌中派生多個檢視)”。
### 衍生資料derived data
一種資料集,根據其他資料透過可重複執行的流程建立。必要時,你可以執行該流程再次建立衍生資料。衍生資料通常用於提高特定資料的讀取速度。常見的衍生資料有索引、快取和物化檢視。參見[第三部分](part-iii.md)的介紹。
### 確定性deterministic
描述一個函式,如果給它相同的輸入,則總是產生相同的輸出。這意味著它不能依賴於隨機數字、時間、網路通訊或其他不可預測的事情。
### 分散式distributed
在由網路連線的多個節點上執行。對於部分節點故障,具有容錯性:系統的一部分發生故障時,其他部分仍可以正常工作,通常情況下,軟體無需瞭解故障相關的確切情況。請參閱“[故障與部分失效](ch8.md#故障與部分失效)”。
### 持久durable
以某種方式儲存資料,即使發生各種故障,也不會丟失資料。請參閱“[永續性Durability](ch7.md#永續性Durability)”。
### ETLExtract-Transform-Load
提取-轉換-載入Extract-Transform-Load。從源資料庫中提取資料將其轉換為更適合分析查詢的形式並將其載入到資料倉庫或批處理系統中的過程。請參閱“[資料倉庫](ch3.md#資料倉庫)”。
### 故障切換failover
在具有單一領導者的系統中,故障切換是將領導角色從一個節點轉移到另一個節點的過程。請參閱“[處理節點宕機](ch5.md#處理節點宕機)”。
### 容錯fault-tolerant
如果出現問題(例如,機器崩潰或網路連線失敗),可以自動恢復。請參閱“[可靠性](ch1.md#可靠性)”。
### 流量控制flow control
見背壓backpressure
### 追隨者follower
一種資料副本,僅處理領導者發出的資料變更,不直接接受來自客戶端的任何寫入。也稱為輔助、僕從、只讀副本或熱備份。請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”。
### 全文檢索full-text search
透過任意關鍵字來搜尋文字,通常具有附加特徵,例如匹配類似的拼寫詞或同義詞。全文索引是一種支援這種查詢的次級索引。請參閱“[全文搜尋和模糊索引](ch3.md#全文搜尋和模糊索引)”。
### 圖graph
一種資料結構,由頂點(可以指向的東西,也稱為節點或實體)和邊(從一個頂點到另一個頂點的連線,也稱為關係或弧)組成。請參閱“[圖資料模型](ch2.md#圖資料模型)”。
一種資料結構,由頂點(可以指向的東西,也稱為節點或實體)和邊(從一個頂點到另一個頂點的連線,也稱為關係或弧)組成。請參閱“[圖資料模型](ch2.md#圖資料模型)”。
### 雜湊hash
將輸入轉換為看起來像隨機數值的函式。相同的輸入會轉換為相同的數值,不同的輸入一般會轉換為不同的數值,也可能轉換為相同數值(也被稱為衝突)。請參閱“[根據鍵的雜湊分割槽](ch6.md#根據鍵的雜湊分割槽)”。
### 冪等idempotent
用於描述一種操作可以安全地重試執行,即執行多次的效果和執行一次的效果相同。請參閱“[冪等性](ch11.md#冪等性)”。
### 索引index
一種資料結構。透過索引,你可以根據特定欄位的值,在所有資料記錄中進行高效檢索。請參閱“[驅動資料庫的資料結構](ch3.md#驅動資料庫的資料結構)”。
### 隔離性isolation
在事務上下文中,用於描述併發執行事務的互相干擾程度。序列執行具有最強的隔離性,不過其它程度的隔離也通常被使用。請參閱“[隔離性Isolation](ch7.md#隔離性Isolation)”。
### 連線join
彙集有共同點的記錄。在一個記錄與另一個記錄有關(外來鍵,文件參考,圖中的邊)的情況下最常用,查詢需要獲取參考所指向的記錄。請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”和“[Reduce側連線與分組](ch10.md#Reduce側連線與分組)”。
### 領導者leader
當資料或服務被複制到多個節點時,由領導者分發已授權變更的資料副本。領導者可以透過某些協議選舉產生,也可以由管理者手動選擇。也被稱為主人。請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”。
### 線性化linearizable
表現為系統中只有一份透過原子操作更新的資料副本。請參閱“[線性一致性](ch9.md#線性一致性)”。
### 區域性性locality
一種效能最佳化方式,如果經常在相同的時間請求一些離散資料,把這些資料放到一個位置。請參閱“[查詢的資料區域性性](ch2.md#查詢的資料區域性性)”。
### 鎖lock
一種保證只有一個執行緒、節點或事務可以訪問的機制,如果其它執行緒、節點或事務想訪問相同元素,則必須等待鎖被釋放。請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”和“[領導者和鎖](ch8.md#領導者和鎖)”。
### 日誌log
日誌是一個只能以追加方式寫入的檔案,用於存放資料。預寫式日誌用於在儲存引擎崩潰時恢復資料(請參閱“[讓B樹更可靠](ch3.md#讓B樹更可靠)”);結構化日誌儲存引擎使用日誌作為它的主要儲存格式(請參閱“[SSTables和LSM樹](ch3.md#SSTables和LSM樹)”);複製型日誌用於把寫入從領導者複製到追隨者(請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”);事件性日誌可以表現為資料流(請參閱“[分割槽日誌](ch11.md#分割槽日誌)”)。
### 物化materialize
急切地計算並寫出結果,而不是在請求時計算。請參閱“[聚合:資料立方體和物化檢視](ch3.md#聚合:資料立方體和物化檢視)”和“[物化中間狀態](ch10.md#物化中間狀態)”。
### 節點node
計算機上執行的一些軟體的例項,透過網路與其他節點通訊以完成某項任務。
### 規範化normalized
以沒有冗餘或重複的方式進行結構化。 在規範化資料庫中,當某些資料發生變化時,您只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。 請參閱“[多對一和多對多的關係](ch2.md#多對一和多對多的關係)”。
### OLAPOnline Analytic Processing
線上分析處理。 透過對大量記錄進行聚合(例如,計數,總和,平均)來表徵的訪問模式。 請參閱“[事務處理還是分析?](ch3.md#事務處理還是分析?)”。
### OLTPOnline Transaction Processing
線上事務處理。 訪問模式的特點是快速查詢,讀取或寫入少量記錄,這些記錄通常透過鍵索引。 請參閱“[事務處理還是分析?](ch3.md#事務處理還是分析?)”。
### 分割槽partitioning
將單機上的大型資料集或計算結果拆分為較小部分,並將其分佈到多臺機器上。 也稱為分片。見[第6章](ch6.md)。
### 百分位點percentile
透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如某個時間段的第95個百分位響應時間是時間t則該時間段中95的請求完成時間小於t5的請求完成時間要比t長。 請參閱“[描述效能](ch1.md#描述效能)”。
### 主鍵primary key
唯一標識記錄的值(通常是數字或字串)。 在許多應用程式中,主鍵由系統在建立記錄時生成(例如,按順序或隨機); 它們通常不由使用者設定。 另請參閱二級索引。
### 法定人數quorum
在操作完成之前,需要對操作進行投票的最少節點數量。 請參閱“[讀寫的法定人數](ch5.md#讀寫的法定人數)”。
### 再平衡rebalance
將資料或服務從一個節點移動到另一個節點以實現負載均衡。 請參閱“[分割槽再平衡](ch6.md#分割槽再平衡)”。
### 複製replication
在幾個節點(副本)上保留相同資料的副本,以便在某些節點無法訪問時,資料仍可訪問。請參閱[第5章](ch5.md)。
### 模式schema
一些資料結構的描述,包括其欄位和資料型別。 可以在資料生命週期的不同點檢查某些資料是否符合模式(請參閱“[文件模型中的模式靈活性](ch2.md#文件模型中的模式靈活性)”),模式可以隨時間變化(請參閱[第4章](ch4.md))。
### 次級索引secondary index
與主要資料儲存器一起維護的附加資料結構,使您可以高效地搜尋與某種條件相匹配的記錄。 請參閱“[其他索引結構](ch3.md#其他索引結構)”和“[分割槽與次級索引](ch6.md#分割槽與次級索引)”。
### 可序列化serializable
保證多個併發事務同時執行時,它們的行為與按順序逐個執行事務相同。 請參閱第7章的“[可序列化](ch7.md#可序列化)”。
### 無共享shared-nothing
與共享記憶體或共享磁碟架構相比獨立節點每個節點都有自己的CPU記憶體和磁碟透過傳統網路連線。 見[第二部分](part-ii.md)的介紹。
### 偏斜skew
1.各分割槽負載不平衡,例如某些分割槽有大量請求或資料,而其他分割槽則少得多。也被稱為熱點。請參閱“[負載偏斜和熱點消除](ch6.md#負載偏斜和熱點消除)”和“[處理偏斜](ch10.md#處理偏斜)”。
2.時間線異常導致事件以不期望的順序出現。 請參閱“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”中的關於讀取偏斜的討論,“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中的寫入偏斜以及“[有序事件的時間戳](ch8.md#有序事件的時間戳)”中的時鐘偏斜。
### 腦裂split brain
兩個節點同時認為自己是領導者的情況,這種情況可能違反系統擔保。 請參閱“[處理節點宕機](ch5.md#處理節點宕機)”和“[真相由多數所定義](ch8.md#真相由多數所定義)”。
### 儲存過程stored procedure
一種對事務邏輯進行編碼的方式,它可以完全在資料庫伺服器上執行,事務執行期間無需與客戶端通訊。 請參閱“[真的序列執行](ch7.md#真的序列執行)”。
### 流處理stream process
持續執行的計算。可以持續接收事件流作為輸入,並得出一些輸出。 見[第11章](ch11.md)。
### 同步synchronous
非同步的反義詞。
### 記錄系統system of record
一個儲存主要權威版本資料的系統,也被稱為真相的來源。首先在這裡寫入資料變更,其他資料集可以從記錄系統衍生。 參見[第三部分](part-iii.md)的介紹。
### 超時timeout
檢測故障的最簡單方法之一,即在一段時間內觀察是否缺乏響應。 但是,不可能知道超時是由於遠端節點的問題還是網路中的問題造成的。 請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”。
### 全序total order
一種比較事物的方法(例如時間戳),可以讓您總是說出兩件事中哪一件更大,哪件更小。 總的來說,有些東西是無法比擬的(不能說哪個更大或更小)的順序稱為偏序。 請參見“[因果順序不是全序的](ch9.md#因果順序不是全序的)”。
### 事務transaction
為了簡化錯誤處理和併發問題,將幾個讀寫操作分組到一個邏輯單元中。 見[第7章](ch7.md)。
### 兩階段提交2PC, two-phase commit
一種確保多個數據庫節點全部提交或全部中止事務的演算法。 請參閱[原子提交與二階段提交2PC](ch9.md#原子提交與二階段提交2PC)”。
### 兩階段鎖定2PL, two-phase locking
一種用於實現可序列化隔離的演算法,該演算法透過事務獲取對其讀取或寫入的所有資料的鎖,直到事務結束。 請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”。
### 無邊界unbounded
沒有任何已知的上限或大小。 反義詞是邊界bounded

View File

@ -1,57 +1,29 @@
# 第一部分:資料系統的基石
# 第一部分:資料系統的基石
本書前四章介紹了資料系統底層的基礎概念,無論是在單臺機器上執行的單點資料系統,還是分佈在多臺機器上的分散式資料系統都適用。
1. [第一章](ch1.md)將介紹本書使用的術語和方法。**可靠性,可伸縮性和可維護性** ,這些詞彙到底意味著什麼?如何實現這些目標?
2. [第二章](ch2.md)將對幾種不同的**資料模型和查詢語言**進行比較。從程式設計師的角度看,這是資料庫之間最明顯的區別。不同的資料模型適用於不同的應用場景。
3. [第三章](ch3.md)將深入**儲存引擎**內部,研究資料庫如何在磁碟上擺放資料。不同的儲存引擎針對不同的負載進行最佳化,選擇合適的儲存引擎對系統性能有巨大影響。
4. [第四章](ch4)將對幾種不同的 **資料編碼**進行比較。特別研究了這些格式在應用需求經常變化、模式需要隨時間演變的環境中表現如何。
第二部分將專門討論在**分散式資料系統**中特有的問題。
## 目錄
1. [可靠性、可伸縮性、可維護性](ch1.md)
2. [資料模型與查詢語言](ch2.md)
3. [儲存與檢索](ch3.md)
4. [編碼與演化](ch4.md)
4. [編碼與演化](ch4.md)
------
| 上一章 | 目錄 | 下一章 |
| ------------------ | ------------------------------- | -------------------------------------------- |
| [序言](preface.md) | [設計資料密集型應用](README.md) | [第一章:可靠性、可伸縮性、可維護性](ch1.md) |
| [序言](preface.md) | [設計資料密集型應用](README.md) | [第一章:可靠性、可伸縮性、可維護性](ch1.md) |

View File

@ -1,168 +1,83 @@
# 第二部分:分散式資料
# 第二部分:分散式資料
> 一個成功的技術,現實的優先順序必須高於公關,你可以糊弄別人,但糊弄不了自然規律。
>
> ——羅傑斯委員會報告1986
>
-------
在本書的[第一部分](part-i.md)中,我們討論了資料系統的各個方面,但僅限於資料儲存在單臺機器上的情況。現在我們到了[第二部分](part-ii.md),進入更高的層次,並提出一個問題:如果**多臺機器**參與資料的儲存和檢索,會發生什麼?
你可能會出於各種各樣的原因,希望將資料庫分佈到多臺機器上:
***可伸縮性***
如果你的資料量、讀取負載、寫入負載超出單臺機器的處理能力,可以將負載分散到多臺計算機上。
***容錯/高可用性***
如果你的應用需要在單臺機器(或多臺機器,網路或整個資料中心)出現故障的情況下仍然能繼續工作,則可使用多臺機器,以提供冗餘。一臺故障時,另一臺可以接管。
***延遲***
如果在世界各地都有使用者,你也許會考慮在全球範圍部署多個伺服器,從而每個使用者可以從地理上最近的資料中心獲取服務,避免了等待網路資料包穿越半個世界。
## 伸縮至更高的載荷
如果你需要的只是伸縮至更高的**載荷load**,最簡單的方法就是購買更強大的機器(有時稱為**垂直伸縮vertical scaling**或**向上伸縮scale up**)。許多處理器,記憶體和磁碟可以在同一個作業系統下相互連線,快速的相互連線允許任意處理器訪問記憶體或磁碟的任意部分。在這種 **共享記憶體架構shared-memory architecture** 中,所有的元件都可以看作一臺單獨的機器[^i]。
[^i]: 在大型機中,儘管任意處理器都可以訪問記憶體的任意部分,但總有一些記憶體區域與一些處理器更接近(稱為**非均勻記憶體訪問nonuniform memory access, NUMA**【1】。 為了有效利用這種架構特性,需要對處理進行細分,以便每個處理器主要訪問臨近的記憶體,這意味著即使表面上看起來只有一臺機器在執行,**分割槽partitioning**仍然是必要的。
共享記憶體方法的問題在於,成本增長速度快於線性增長:一臺有著雙倍處理器數量,雙倍記憶體大小,雙倍磁碟容量的機器,通常成本會遠遠超過原來的兩倍。而且可能因為存在瓶頸,並不足以處理雙倍的載荷。
共享記憶體架構可以提供有限的容錯能力,高階機器可以使用熱插拔的元件(不關機更換磁碟,記憶體模組,甚至處理器)——但它必然囿於單個地理位置的桎梏。
另一種方法是**共享磁碟架構shared-disk architecture**,它使用多臺具有獨立處理器和記憶體的機器,但將資料儲存在機器之間共享的磁碟陣列上,這些磁碟透過快速網路連線[^ii]。這種架構用於某些資料倉庫但競爭和鎖定的開銷限制了共享磁碟方法的可伸縮性【2】。
[^ii]: 網路附屬儲存Network Attached Storage, NAS或**儲存區網路Storage Area Network, SAN**
### 無共享架構
相比之下,**無共享架構shared-nothing architecture**(有時稱為**水平伸縮horizontal scale** 或**向外伸縮scale out**)已經相當普及。在這種架構中,執行資料庫軟體的每臺機器/虛擬機器都稱為**節點node**。每個節點只使用各自的處理器,記憶體和磁碟。節點之間的任何協調,都是在軟體層面使用傳統網路實現的。
無共享系統不需要使用特殊的硬體所以你可以用任意機器——比如價效比最好的機器。你也許可以跨多個地理區域分佈資料從而減少使用者延遲或者在損失一整個資料中心的情況下倖免於難。隨著雲端虛擬機器部署的出現即使是小公司現在無需Google級別的運維也可以實現異地分散式架構。
在這一部分裡,我們將重點放在無共享架構上。它不見得是所有場景的最佳選擇,但它是最需要你謹慎從事的架構。如果你的資料分佈在多個節點上,你需要意識到這樣一個分散式系統中約束和權衡 ——資料庫並不能魔術般地把這些東西隱藏起來。
雖然分散式無共享架構有許多優點但它通常也會給應用帶來額外的複雜度有時也會限制你可用資料模型的表達力。在某些情況下一個簡單的單執行緒程式可以比一個擁有超過100個CPU核的叢集表現得更好【4】。另一方面無共享系統可以非常強大。接下來的幾章將詳細討論分散式資料會帶來的問題。
### 複製 vs 分割槽
資料分佈在多個節點上有兩種常見的方式:
***複製Replication***
在幾個不同的節點上儲存資料的相同副本,可能放在不同的位置。 複製提供了冗餘:如果一些節點不可用,剩餘的節點仍然可以提供資料服務。 複製也有助於改善效能。 [第五章](ch5.md)將討論複製。
***分割槽 (Partitioning)***
將一個大型資料庫拆分成較小的子集(稱為**分割槽partitions**),從而不同的分割槽可以指派給不同的**節點node**(亦稱**分片shard**)。 [第六章](ch6.md)將討論分割槽。
複製和分割槽是不同的機制,但它們經常同時使用。如[圖II-1](../img/figii-1.png)所示。
![](../img/figii-1.png)
**圖II-1 一個數據庫切分為兩個分割槽,每個分割槽都有兩個副本**
理解了這些概念,就可以開始討論在分散式系統中需要做出的困難抉擇。[第七章](ch7.md)將討論**事務(Transaction)**,這對於瞭解資料系統中可能出現的各種問題,以及我們可以做些什麼很有幫助。[第八章](ch8.md)和[第九章](ch9.md)將討論分散式系統的根本侷限性。
在本書的[第三部分](part-iii.md)中,將討論如何將多個(可能是分散式的)資料儲存整合為一個更大的系統,以滿足複雜的應用需求。 但首先,我們來聊聊分散式的資料。
## 索引
5. [複製](ch5.md)
6. [分片](ch6.md)
7. [事務](ch7.md)
8. [分散式系統的麻煩](ch8.md)
9. [一致性與共識](ch9.md)
6. [分片](ch6.md)
7. [事務](ch7.md)
8. [分散式系統的麻煩](ch8.md)
9. [一致性與共識](ch9.md)
@ -170,30 +85,16 @@
## 參考文獻
1. Ulrich Drepper: “[What Every Programmer Should Know About Memory](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf),” akkadia.org, November 21, 2007.
2. Ben Stopford: “[Shared Nothing vs. Shared Disk Architectures: An Independent View](http://www.benstopford.com/2009/11/24/understanding-the-shared-nothing-architecture/),” benstopford.com, November 24, 2009.
3. Michael Stonebraker: “[The Case for Shared Nothing](http://db.cs.berkeley.edu/papers/hpts85-nothing.pdf),” IEEE Database EngineeringBulletin, volume 9, number 1, pages 49, March 1986.
4. Frank McSherry, Michael Isard, and Derek G. Murray: “[Scalability! But at What COST?](http://www.frankmcsherry.org/assets/COST.pdf),” at 15th USENIX Workshop on Hot Topics in Operating Systems (HotOS),May 2015.
------
| 上一章 | 目錄 | 下一章 |
| ---------------------------- | ------------------------------- | ---------------------- |
| [第四章:編碼與演化](ch4.md) | [設計資料密集型應用](README.md) | [第五章:複製](ch5.md) |

View File

@ -1,87 +1,44 @@
# 第三部分:衍生資料
在本書的[第一部分](part-i.md)和[第二部分](part-ii.md)中,我們自底向上地把所有關於分散式資料庫的主要考量都過了一遍。從資料在磁碟上的佈局,一直到出現故障時分散式系統一致性的侷限。但所有的討論都假定了應用中只用了一種資料庫。
現實世界中的資料系統往往更為複雜。大型應用程式經常需要以多種方式訪問和處理資料,沒有一個數據庫可以同時滿足所有這些不同的需求。因此應用程式通常組合使用多種元件:資料儲存,索引,快取,分析系統,等等,並實現在這些元件中移動資料的機制。
本書的最後一部分,會研究將多個不同資料系統(可能有著不同資料模型,並針對不同的訪問模式進行最佳化)整合為一個協調一致的應用架構時,會遇到的問題。軟體供應商經常會忽略這一方面的生態建設,並聲稱他們的產品能夠滿足你的所有需求。在現實世界中,整合不同的系統是實際應用中最重要的事情之一。
## 記錄系統和衍生資料系統
從高層次上看,儲存和處理資料的系統可以分為兩大類:
***記錄系統System of record***
**記錄系統**,也被稱為**真相源source of truth**,持有資料的權威版本。當新的資料進入時(例如,使用者輸入)首先會記錄在這裡。每個事實正正好好表示一次(表示通常是**正規化的normalized**)。如果其他系統和**記錄系統**之間存在任何差異,那麼記錄系統中的值是正確的(根據定義)。
***衍生資料系統Derived data systems***
**衍生系統**中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失衍生資料,可以從原始來源重新建立。典型的例子是**快取cache**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。非規範化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常衍生自使用者日誌。
從技術上講,衍生資料是**冗餘的redundant**,因為它重複了已有的資訊。但是衍生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是非規範化的。可以從單個源頭衍生出多個不同的資料集,使你能從不同的“視角”洞察資料。
並不是所有的系統都在其架構中明確區分**記錄系統**和**衍生資料系統**,但是這是一種有用的區分方式,因為它明確了系統中的資料流:系統的哪一部分具有哪些輸入和哪些輸出,以及它們如何相互依賴。
大多數資料庫,儲存引擎和查詢語言,本質上既不是記錄系統也不是衍生系統。資料庫只是一個工具:如何使用它取決於你自己。**記錄系統和衍生資料系統之間的區別不在於工具,而在於應用程式中的使用方式。**
透過梳理資料的衍生關係,可以清楚地理解一個令人困惑的系統架構。這將貫穿本書的這一部分。
## 章節概述
我們將從[第十章](ch10.md)開始研究例如MapReduce這樣 **面向批處理batch-oriented** 的資料流系統。對於建設大規模資料系統,我們將看到,它們提供了優秀的工具和思想。[第十一章](ch11.md)將把這些思想應用到 **流式資料data streams** 中,使我們能用更低的延遲完成同樣的任務。[第十二章](ch12.md)將對本書進行總結,探討如何使用這些工具來構建可靠,可伸縮和可維護的應用。
## 索引
10. [批處理](ch10.md)
11. [流處理](ch11.md)
12. [資料系統的未來](ch12.md)
------
| 上一章 | 目錄 | 下一章 |
| ------------------------------ | ------------------------------- | ------------------------- |
| [第九章:一致性與共識](ch9.md) | [設計資料密集型應用](README.md) | [第十章:批處理](ch10.md) |

View File

@ -1,203 +1,102 @@
# 序言
如果近幾年從業於軟體工程,特別是伺服器端和後端系統開發,那麼您很有可能已經被大量關於資料儲存和處理的時髦詞彙轟炸過了: NoSQL大資料Web-Scale分片最終一致性ACID CAP定理雲服務MapReduce實時
在最近十年中,我們看到了很多有趣的進展,關於資料庫,分散式系統,以及在此基礎上構建應用程式的方式。這些進展有著各種各樣的驅動力:
* 谷歌,雅虎,亞馬遜,臉書,領英,微軟和推特等網際網路公司正在和巨大的流量/資料打交道,這迫使他們去創造能有效應對如此規模的新工具。
* 企業需要變得敏捷,需要低成本地檢驗假設,需要透過縮短開發週期和保持資料模型的靈活性,快速地響應新的市場洞察。
* 免費和開源軟體變得非常成功,在許多環境中比商業軟體和定製軟體更受歡迎。
* 處理器主頻幾乎沒有增長,但是多核處理器已經成為標配,網路也越來越快。這意味著並行化程度只增不減。
* 即使您在一個小團隊中工作現在也可以構建分佈在多臺計算機甚至多個地理區域的系統這要歸功於譬如亞馬遜網路服務AWS等基礎設施即服務IaaS概念的踐行者。
* 許多服務都要求高可用,因停電或維護導致的服務不可用,變得越來越難以接受。
**資料密集型應用data-intensive applications**正在透過使用這些技術進步來推動可能性的邊界。一個應用被稱為**資料密集型**的,如果**資料是其主要挑戰**(資料量,資料複雜度或資料變化速度)—— 與之相對的是**計算密集型**,即處理器速度是其瓶頸。
幫助資料密集型應用儲存和處理資料的工具與技術正迅速地適應這些變化。新型資料庫系統“NoSQL”已經備受關注而訊息佇列快取搜尋索引批處理和流處理框架以及相關技術也非常重要。很多應用組合使用這些工具與技術。
這些生意盎然的時髦詞彙體現出人們對新的可能性的熱情,這是一件好事。但是作為軟體工程師和架構師,如果要開發優秀的應用,我們還需要對各種層出不窮的技術及其利弊權衡有精準的技術理解。為了獲得這種洞察,我們需要深挖時髦詞彙背後的內容。
幸運的是,在技術迅速變化的背後總是存在一些持續成立的原則,無論您使用了特定工具的哪個版本。如果您理解了這些原則,就可以領會這些工具的適用場景,如何充分利用它們,以及如何避免其中的陷阱。這正是本書的初衷。
本書的目標是幫助您在飛速變化的資料處理和資料儲存技術大觀園中找到方向。本書並不是某個特定工具的教程,也不是一本充滿枯燥理論的教科書。相反,我們將看到一些成功資料系統的樣例:許多流行應用每天都要在生產中滿足可伸縮性、效能、以及可靠性的要求,而這些技術構成了這些應用的基礎。
我們將深入這些系統的內部,理清它們的關鍵演算法,討論背後的原則和它們必須做出的權衡。在這個過程中,我們將嘗試尋找**思考**資料系統的有效方式 —— 不僅關於它們**如何**工作,還包括它們**為什麼**以這種方式工作,以及哪些問題是我們需要問的。
閱讀本書後,你能很好地決定哪種技術適合哪種用途,並瞭解如何將工具組合起來,為一個良好應用架構奠定基礎。本書並不足以使你從頭開始構建自己的資料庫儲存引擎,不過幸運的是這基本上很少有必要。你將獲得對系統底層發生事情的敏銳直覺,這樣你就有能力推理它們的行為,做出優秀的設計決策,並追蹤任何可能出現的問題。
## 本書的目標讀者
如果你開發的應用具有用於儲存或處理資料的某種伺服器/後端系統而且使用網路例如Web應用移動應用或連線到網際網路的感測器那麼本書就是為你準備的。
本書是為軟體工程師,軟體架構師,以及喜歡寫程式碼的技術經理準備的。如果您需要對所從事系統的架構做出決策 —— 例如您需要選擇解決某個特定問題的工具,並找出如何最好地使用這些工具,那麼這本書對您尤有價值。但即使你無法選擇你的工具,本書仍將幫助你更好地瞭解所使用工具的長處和短處。
您應當具有一些開發Web應用或網路服務的經驗且應當熟悉關係型資料庫和SQL。任何您瞭解的非關係型資料庫和其他與資料相關工具都會有所幫助但不是必需的。對常見網路協議如TCP和HTTP的大概理解是有幫助的。程式語言或框架的選擇對閱讀本書沒有任何不同影響。
如果以下任意一條對您為真,你會發現這本書很有價值:
* 您想了解如何使資料系統可伸縮例如支援擁有數百萬使用者的Web或移動應用。
* 您需要提高應用程式的可用性(最大限度地減少停機時間),保持穩定執行。
* 您正在尋找使系統在長期執行過程易於維護的方法,即使系統規模增長,需求與技術也發生變化。
* 您對事物的運作方式有著天然的好奇心,並且希望知道一些主流網站和線上服務背後發生的事情。這本書打破了各種資料庫和資料處理系統的內幕,探索這些系統設計中的智慧是非常有趣的。
有時在討論可伸縮的資料系統時,人們會說:“你又不在谷歌或亞馬遜,別操心可伸縮性了,直接上關係型資料庫”。這個陳述有一定的道理:為了不必要的伸縮性而設計程式,不僅會浪費不必要的精力,並且可能會把你鎖死在一個不靈活的設計中。實際上這是一種“過早最佳化”的形式。不過,選擇合適的工具確實很重要,而不同的技術各有優缺點。我們將看到,關係資料庫雖然很重要,但絕不是資料處理的終章。
## 本書涉及的領域
本書並不會嘗試告訴讀者如何安裝或使用特定的軟體包或API因為已經有大量文件給出了詳細的使用說明。相反我們會討論資料系統的基石——各種原則與利弊權衡並探討了不同產品所做出的不同設計決策。
在電子書中包含了線上資源全文的連結。所有連結在出版時都進行了驗證但不幸的是由於網路的自然規律連結往往會頻繁地破損。如果您遇到連結斷開的情況或者正在閱讀本書的列印副本可以使用搜索引擎查詢參考文獻。對於學術論文您可以在Google學術中搜索標題查詢可以公開獲取的PDF檔案。或者您也可以在 https://github.com/ept/ddia-references 中找到所有的參考資料,我們在那兒維護最新的連結。
我們主要關注的是資料系統的**架構architecture**,以及它們被整合到資料密集型應用中的方式。本書沒有足夠的空間覆蓋部署,運維,安全,管理等領域 —— 這些都是複雜而重要的主題,僅僅在本書中用粗略的註解討論這些對它們很不公平。每個領域都值得用單獨的書去講。
本書中描述的許多技術都被涵蓋在 **大資料Big Data** 這個時髦詞的範疇中。然而“大資料”這個術語被濫用,缺乏明確定義,以至於在嚴肅的工程討論中沒有用處。這本書使用歧義更小的術語,如“單節點”之於”分散式系統“,或”線上/互動式系統“之於”離線/批處理系統“。
本書對 **自由和開源軟體FOSS** 有一定偏好,因為閱讀,修改和執行原始碼是瞭解某事物詳細工作原理的好方法。開放的平臺也可以降低供應商壟斷的風險。然而在適當的情況下,我們也會討論專利軟體(閉源軟體,軟體即服務 SaaS或一些在文獻中描述過但未公開發行的公司內部軟體
## 本書綱要
本書分為三部分:
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))。
3. 在[第三部分](part-iii.md)中,我們討論那些從其他資料集衍生出一些資料集的系統。衍生資料經常出現在異構系統中:當沒有單個數據庫可以把所有事情都做的很好時,應用需要整合幾種不同的資料庫,快取,索引等。在[第10章](ch10.md)中我們將從一種衍生資料的批處理方法開始,然後在此基礎上建立在[第11章](ch11.md)中討論的流處理。最後,在[第12章](ch12.md)中,我們將所有內容彙總,討論在將來構建可靠,可伸縮和可維護的應用程式的方法。
## 參考文獻與延伸閱讀
本書中討論的大部分內容已經在其它地方以某種形式出現過了 —— 會議簡報研究論文部落格文章程式碼BUG跟蹤器郵件列表以及工程習慣中。本書總結了不同來源資料中最重要的想法並在文字中包含了指向原始文獻的連結。 如果你想更深入地探索一個領域,那麼每章末尾的參考文獻都是很好的資源,其中大部分可以免費線上獲取。
## OReilly Safari
[Safari](http://oreilly.com/safari) (formerly Safari Books Online) is a membership-based training and reference platform for enterprise, government, educators, and individuals.
Members have access to thousands of books, training videos, Learning Paths, interac tive tutorials, and curated playlists from over 250 publishers, including OReilly Media, Harvard Business Review, Prentice Hall Professional, Addison-Wesley Pro fessional, Microsoft Press, Sams, Que, Peachpit Press, Adobe, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-Hill, Jones & Bartlett, and Course Technology, among others.
For more information, please visit http://oreilly.com/safari.
## 致謝
本書融合了學術研究和工業實踐的經驗融合並系統化了大量其他人的想法與知識。在計算領域我們往往會被各種新鮮花樣所吸引但我認為前人完成的工作中有太多值得我們學習的地方了。本書有800多處引用文章部落格講座文件等對我來說這些都是寶貴的學習資源。我非常感謝這些材料的作者分享他們的知識。
我也從與人交流中學到了很多東西,很多人花費了寶貴的時間與我討論想法並耐心解釋。特別感謝 Joe Adler, Ross Anderson, Peter Bailis, Márton Balassi, Alastair Beresford, Mark Callaghan, Mat Clayton, Patrick Collison, Sean Cribbs, Shirshanka Das, Niklas Ekström, Stephan Ewen, Alan Fekete, Gyula Fóra, Camille Fournier, Andres Freund, John Garbutt, Seth Gilbert, Tom Haggett, Pat Hel land, Joe Hellerstein, Jakob Homan, Heidi Howard, John Hugg, Julian Hyde, Conrad Irwin, Evan Jones, Flavio Junqueira, Jessica Kerr, Kyle Kingsbury, Jay Kreps, Carl Lerche, Nicolas Liochon, Steve Loughran, Lee Mallabone, Nathan Marz, Caitie McCaffrey, Josie McLellan, Christopher Meiklejohn, Ian Meyers, Neha Narkhede, Neha Narula, Cathy ONeil, Onora ONeill, Ludovic Orban, Zoran Perkov, Julia Powles, Chris Riccomini, Henry Robinson, David Rosenthal, Jennifer Rullmann, Matthew Sackman, Martin Scholl, Amit Sela, Gwen Shapira, Greg Spurrier, Sam Stokes, Ben Stopford, Tom Stuart, Diana Vasile, Rahul Vohra, Pete Warden, 以及 Brett Wooldridge.
更多人透過審閱草稿並提供反饋意見在本書的創作過程中做出了無價的貢獻。我要特別感謝Raul Agepati, Tyler Akidau, Mattias Andersson, Sasha Baranov, Veena Basavaraj, David Beyer, Jim Brikman, Paul Carey, Raul Castro Fernandez, Joseph Chow, Derek Elkins, Sam Elliott, Alexander Gallego, Mark Grover, Stu Halloway, Heidi Howard, Nicola Kleppmann, Stefan Kruppa, Bjorn Madsen, Sander Mak, Stefan Podkowinski, Phil Potter, Hamid Ramazani, Sam Stokes, 以及Ben Summers。當然對於本書中的任何遺留錯誤或難以接受的見解我都承擔全部責任。
為了幫助這本書落地並且耐心地處理我緩慢的寫作和不尋常的要求我要對編輯Marie BeaugureauMike LoukidesAnn Spencer和O'Reilly的所有團隊表示感謝。我要感謝Rachel Head幫我找到了合適的術語。我要感謝Alastair BeresfordSusan GoodhueNeha Narkhede和Kevin Scott在其他工作事務之外給了我充分地創作時間和自由。
特別感謝Shabbir Diwan和Edie Freedman他們非常用心地為各章配了地圖。他們提出了不落俗套的靈感創作了這些地圖美麗而引人入勝真是太棒了。
最後我要表達對家人和朋友們的愛,沒有他們,我將無法走完這個將近四年的寫作歷程。你們是最棒的。