mirror of
https://github.com/Vonng/ddia.git
synced 2025-01-05 15:30:06 +08:00
remove unnecessary blank lines and code indentations
This commit is contained in:
parent
e3f8bb25ed
commit
a9a7955772
9
ch1.md
9
ch1.md
@ -29,7 +29,6 @@
|
||||
本章将从我们所要实现的基础目标开始:可靠、可伸缩、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。
|
||||
|
||||
|
||||
|
||||
## 关于数据系统的思考
|
||||
|
||||
我们通常认为,数据库、消息队列、缓存等工具分属于几个差异显著的类别。虽然数据库和消息队列表面上有一些相似性——它们都会存储一段时间的数据——但它们有迥然不同的访问模式,这意味着迥异的性能特征和实现手段。
|
||||
@ -141,7 +140,6 @@
|
||||
在某些情况下,我们可能会选择牺牲可靠性来降低开发成本(例如为未经证实的市场开发产品原型)或运营成本(例如利润率极低的服务),但我们偷工减料时,应该清楚意识到自己在做什么。
|
||||
|
||||
|
||||
|
||||
## 可伸缩性
|
||||
|
||||
系统今天能可靠运行,并不意味未来也能可靠运行。服务 **降级(degradation)** 的一个常见原因是负载增加,例如:系统负载已经从一万个并发用户增长到十万个并发用户,或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多。
|
||||
@ -162,8 +160,6 @@
|
||||
|
||||
用户可以查阅他们关注的人发布的推文(300k请求/秒)。
|
||||
|
||||
|
||||
|
||||
处理每秒12,000次写入(发推文的速率峰值)还是很简单的。然而推特的伸缩性挑战并不是主要来自推特量,而是来自**扇出(fan-out)**——每个用户关注了很多人,也被很多人关注。
|
||||
|
||||
[^ii]: 扇出:从电子工程学中借用的术语,它描述了输入连接到另一个门输出的逻辑门数量。 输出需要提供足够的电流来驱动所有连接的输入。 在事务处理系统中,我们使用它来描述为了服务一个传入请求而需要执行其他服务的请求数量。
|
||||
@ -275,7 +271,6 @@
|
||||
尽管这些架构是应用程序特定的,但可伸缩的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。
|
||||
|
||||
|
||||
|
||||
## 可维护性
|
||||
|
||||
众所周知,软件的大部分开销并不在最初的开发阶段,而是在持续的维护阶段,包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债、添加新的功能等等。
|
||||
@ -326,7 +321,6 @@
|
||||
* 行为可预测,最大限度减少意外
|
||||
|
||||
|
||||
|
||||
### 简单性:管理复杂度
|
||||
|
||||
小型软件项目可以使用简单讨喜的、富表现力的代码,但随着项目越来越大,代码往往变得非常复杂,难以理解。这种复杂度拖慢了所有系统相关人员,进一步增加了维护成本。一个陷入复杂泥潭的软件项目有时被描述为 **烂泥潭(a big ball of mud)** 【30】。
|
||||
@ -356,7 +350,6 @@
|
||||
修改数据系统并使其适应不断变化需求的容易程度,是与**简单性**和**抽象性**密切相关的:简单易懂的系统通常比复杂系统更容易修改。但由于这是一个非常重要的概念,我们将用一个不同的词来指代数据系统层面的敏捷性: **可演化性(evolvability)** 【34】。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
本章探讨了一些关于数据密集型应用的基本思考方式。这些原则将指导我们阅读本书的其余部分,那里将会深入技术细节。
|
||||
@ -375,7 +368,6 @@
|
||||
在本书后面的[第三部分](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.
|
||||
@ -414,7 +406,6 @@
|
||||
1. 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)
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
15
ch10.md
15
ch10.md
@ -37,7 +37,6 @@ Web和越来越多的基于HTTP/REST的API使交互的请求/响应风格变得
|
||||
在本章中,我们将了解MapReduce和其他一些批处理算法和框架,并探索它们在现代数据系统中的作用。但首先我们将看看使用标准Unix工具的数据处理。即使你已经熟悉了它们,Unix的哲学也值得一读,Unix的思想和经验教训可以迁移到大规模、异构的分布式数据系统中。
|
||||
|
||||
|
||||
|
||||
## 使用Unix工具的批处理
|
||||
|
||||
我们从一个简单的例子开始。假设你有一台Web服务器,每次处理请求时都会在日志文件中附加一行。例如,使用nginx默认的访问日志格式,日志的一行可能如下所示:
|
||||
@ -58,7 +57,6 @@ AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36"
|
||||
日志的这一行表明在2015年2月27日17:55:11 UTC,服务器从客户端IP地址`216.58.210.78`接收到对文件`/css/typography.css`的请求。用户没有被认证,所以`$remote_user`被设置为连字符(`-` )。响应状态是200(即请求成功),响应的大小是3377字节。网页浏览器是Chrome 40,URL `http://martin.kleppmann.com/` 的页面中的引用导致该文件被加载。
|
||||
|
||||
|
||||
|
||||
### 简单日志分析
|
||||
|
||||
很多工具可以从这些日志文件生成关于网站流量的漂亮的报告,但为了练手,让我们使用基本的Unix功能创建自己的工具。 例如,假设你想在你的网站上找到五个最受欢迎的网页。 则可以在Unix shell中这样做:[^i]
|
||||
@ -129,7 +127,6 @@ Ruby脚本在内存中保存了一个URL的哈希表,将每个URL映射到它
|
||||
GNU Coreutils(Linux)中的`sort `程序通过溢出至磁盘的方式来自动应对大于内存的数据集,并能同时使用多个CPU核进行并行排序【9】。这意味着我们之前看到的简单的Unix命令链很容易伸缩至大数据集,且不会耗尽内存。瓶颈可能是从磁盘读取输入文件的速度。
|
||||
|
||||
|
||||
|
||||
### Unix哲学
|
||||
|
||||
我们可以非常容易地使用前一个例子中的一系列命令来分析日志文件,这并非巧合:事实上,这实际上是Unix的关键设计思想之一,而且它直至今天也仍然令人讶异地重要。让我们更深入地研究一下,以便从Unix中借鉴一些想法【10】。
|
||||
@ -168,7 +165,6 @@ ASCII文本的统一接口大多数时候都能工作,但它不是很优雅:
|
||||
[^译注i]: **巴尔干化(Balkanization)** 是一个常带有贬义的地缘政治学术语,其定义为:一个国家或政区分裂成多个互相敌对的国家或政区的过程。
|
||||
|
||||
|
||||
|
||||
#### 逻辑与布线相分离
|
||||
|
||||
Unix工具的另一个特点是使用标准输入(`stdin`)和标准输出(`stdout`)。如果你运行一个程序,而不指定任何其他的东西,标准输入来自键盘,标准输出指向屏幕。但是,你也可以从文件输入和/或将输出重定向到文件。管道允许你将一个进程的标准输出附加到另一个进程的标准输入(有个小内存缓冲区,而不需要将整个中间数据流写入磁盘)。
|
||||
@ -182,7 +178,6 @@ Unix工具的另一个特点是使用标准输入(`stdin`)和标准输出(
|
||||
[^iii]: 除了使用一个单独的工具,如`netcat`或`curl`。 Unix起初试图将所有东西都表示为文件,但是BSD套接字API偏离了这个惯例【17】。研究用操作系统Plan 9和Inferno在使用文件方面更加一致:它们将TCP连接表示为`/net/tcp`中的文件【18】。
|
||||
|
||||
|
||||
|
||||
#### 透明度和实验
|
||||
|
||||
使Unix工具如此成功的部分原因是,它们使查看正在发生的事情变得非常容易:
|
||||
@ -196,7 +191,6 @@ Unix工具的另一个特点是使用标准输入(`stdin`)和标准输出(
|
||||
然而,Unix工具的最大局限在于它们只能在一台机器上运行 —— 而Hadoop这样的工具即应运而生。
|
||||
|
||||
|
||||
|
||||
## MapReduce和分布式文件系统
|
||||
|
||||
MapReduce有点像Unix工具,但分布在数千台机器上。像Unix工具一样,它相当简单粗暴,但令人惊异地管用。一个MapReduce作业可以和一个Unix进程相类比:它接受一个或多个输入,并产生一个或多个输出。
|
||||
@ -360,7 +354,6 @@ Hive的偏斜连接优化采取了另一种方法。它需要在表格元数据
|
||||
当按照热键进行分组并聚合时,可以将分组分两个阶段进行。第一个MapReduce阶段将记录发送到随机Reducer,以便每个Reducer只对热键的子集执行分组,为每个键输出一个更紧凑的中间聚合结果。然后第二个MapReduce作业将所有来自第一阶段Reducer的中间聚合结果合并为每个键一个值。
|
||||
|
||||
|
||||
|
||||
### Map侧连接
|
||||
|
||||
上一节描述的连接算法在Reducer中执行实际的连接逻辑,因此被称为Reduce侧连接。Mapper扮演着预处理输入数据的角色:从每个输入记录中提取键值,将键值对分配给Reducer分区,并按键排序。
|
||||
@ -408,7 +401,6 @@ Reduce侧方法的优点是不需要对输入数据做任何假设:无论其
|
||||
在Hadoop生态系统中,这种关于数据集分区的元数据通常在HCatalog和Hive Metastore中维护【37】。
|
||||
|
||||
|
||||
|
||||
### 批处理工作流的输出
|
||||
|
||||
我们已经说了很多用于实现MapReduce工作流的算法,但却忽略了一个重要的问题:这些处理完成之后的最终结果是什么?我们最开始为什么要跑这些作业?
|
||||
@ -524,7 +516,6 @@ MapReduce方式更适用于较大的作业:要处理如此之多的数据并
|
||||
在开源的集群调度器中,抢占的使用较少。 YARN的CapacityScheduler支持抢占,以平衡不同队列的资源分配【58】,但在编写本文时,YARN,Mesos或Kubernetes不支持通用的优先级抢占【60】。在任务不经常被终止的环境中,MapReduce的这一设计决策就没有多少意义了。在下一节中,我们将研究一些与MapReduce设计决策相异的替代方案。
|
||||
|
||||
|
||||
|
||||
## MapReduce之后
|
||||
|
||||
虽然MapReduce在2000年代后期变得非常流行,并受到大量的炒作,但它只是分布式系统的许多可能的编程模型之一。对于不同的数据量,数据结构和处理类型,其他工具可能更适合表示计算。
|
||||
@ -650,7 +641,6 @@ Spark,Flink和Tez避免将中间状态写入HDFS,因此它们采取了不同
|
||||
出于这个原因,如果你的图可以放入一台计算机的内存中,那么单机(甚至可能是单线程)算法很可能会超越分布式批处理【73,74】。图比内存大也没关系,只要能放入单台计算机的磁盘,使用GraphChi等框架进行单机处理是就一个可行的选择【75】。如果图太大,不适合单机处理,那么像Pregel这样的分布式方法是不可避免的。高效的并行图算法是一个进行中的研究领域【76】。
|
||||
|
||||
|
||||
|
||||
### 高级API和语言
|
||||
|
||||
自MapReduce开始流行的这几年以来,分布式批处理的执行引擎已经很成熟了。到目前为止,基础设施已经足够强大,能够存储和处理超过10,000台机器集群上的数PB的数据。由于在这种规模下物理执行批处理的问题已经被认为或多或少解决了,所以关注点已经转向其他领域:改进编程模型,提高处理效率,扩大这些技术可以解决的问题集。
|
||||
@ -688,7 +678,6 @@ Spark,Flink和Tez避免将中间状态写入HDFS,因此它们采取了不同
|
||||
批处理引擎正被用于分布式执行日益广泛的各领域算法。随着批处理系统获得各种内置功能以及高级声明式算子,且随着MPP数据库变得更加灵活和易于编程,两者开始看起来相似了:最终,它们都只是存储和处理数据的系统。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
在本章中,我们探索了批处理的主题。我们首先看到了诸如awk、grep和sort之类的Unix工具,然后我们看到了这些工具的设计理念是如何应用到MapReduce和更近的数据流引擎中的。一些设计原则包括:输入是不可变的,输出是为了作为另一个(仍未知的)程序的输入,而复杂的问题是通过编写“做好一件事”的小工具来解决的。
|
||||
@ -708,7 +697,6 @@ Spark,Flink和Tez避免将中间状态写入HDFS,因此它们采取了不同
|
||||
MapReduce经常写入磁盘,这使得从单个失败的任务恢复很轻松,无需重新启动整个作业,但在无故障的情况下减慢了执行速度。数据流引擎更多地将中间状态保存在内存中,更少地物化中间状态,这意味着如果节点发生故障,则需要重算更多的数据。确定性算子减少了需要重算的数据量。
|
||||
|
||||
|
||||
|
||||
我们讨论了几种MapReduce的连接算法,其中大多数也在MPP数据库和数据流引擎内部使用。它们也很好地演示了分区算法是如何工作的:
|
||||
|
||||
***排序合并连接***
|
||||
@ -732,9 +720,6 @@ MapReduce经常写入磁盘,这使得从单个失败的任务恢复很轻松
|
||||
在下一章中,我们将转向流处理,其中的输入是**无界的(unbounded)** —— 也就是说,你还有活儿要干,然而它的输入是永无止境的数据流。在这种情况下,作业永无完成之日。因为在任何时候都可能有更多的工作涌入。我们将看到,在某些方面上,流处理和批处理是相似的。但是关于无尽数据流的假设也对我们构建系统的方式产生了很多改变。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Jeffrey Dean and Sanjay Ghemawat: “[MapReduce: Simplified Data Processing on Large Clusters](http://research.google.com/archive/mapreduce.html),” at *6th USENIX Symposium on Operating System Design and Implementation* (OSDI), December 2004.
|
||||
|
5
ch11.md
5
ch11.md
@ -202,7 +202,6 @@ Apache Kafka 【17,18】,Amazon Kinesis Streams 【19】和Twitter的Distribut
|
||||
这一方面使得基于日志的消息传递更像上一章的批处理,其中衍生数据通过可重复的转换过程与输入数据显式分离。它允许进行更多的实验,更容易从错误和漏洞中恢复,使其成为在组织内集成数据流的良好工具【24】。
|
||||
|
||||
|
||||
|
||||
## 数据库与流
|
||||
|
||||
我们已经在消息代理和数据库之间进行了一些比较。尽管传统上它们被视为单独的工具类别,但是我们看到基于日志的消息代理已经成功地从数据库中获取灵感并将其应用于消息传递。我们也可以反过来:从消息传递和流中获取灵感,并将它们应用于数据库。
|
||||
@ -405,7 +404,6 @@ $$
|
||||
真正删除数据是非常非常困难的【64】,因为副本可能存在于很多地方:例如,存储引擎,文件系统和SSD通常会向一个新位置写入,而不是原地覆盖旧数据【52】,而备份通常是特意做成不可变的,防止意外删除或损坏。删除操作更多的是指“使取回数据更困难”,而不是指“使取回数据不可能”。无论如何,有时你必须得尝试,正如我们在“[立法与自律](ch12.md#立法与自律)”中所看到的。
|
||||
|
||||
|
||||
|
||||
## 流处理
|
||||
|
||||
到目前为止,本章中我们已经讨论了流的来源(用户活动事件,传感器和写入数据库),我们讨论了流如何传输(直接通过消息传送,通过消息代理,通过事件日志)。
|
||||
@ -680,7 +678,6 @@ Storm的Trident基于类似的想法来处理状态【78】。依赖幂等性意
|
||||
然而,所有这些权衡取决于底层基础架构的性能特征:在某些系统中,网络延迟可能低于磁盘访问延迟,网络带宽也可能与磁盘带宽相当。没有针对所有情况的普适理想权衡,随着存储和网络技术的发展,本地状态与远程状态的优点也可能会互换。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
在本章中,我们讨论了事件流,它们所服务的目的,以及如何处理它们。在某些方面,流处理非常类似于在[第十章](ch10.md) 中讨论的批处理,不过是在无限的(永无止境的)流而不是固定大小的输入上持续进行。从这个角度来看,消息代理和事件日志可以视作文件系统的流式等价物。
|
||||
@ -822,8 +819,6 @@ Storm的Trident基于类似的想法来处理状态【78】。依赖幂等性意
|
||||
1. Adam Warski: “[Kafka Streams – How Does It Fit the Stream Processing Landscape?](https://softwaremill.com/kafka-streams-how-does-it-fit-stream-landscape/),” *softwaremill.com*, June 1, 2016.
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
4
ch12.md
4
ch12.md
@ -865,7 +865,6 @@ ACID意义下的一致性(请参阅“[一致性](ch7.md#一致性)”)基
|
||||
我们究竟能做到哪一步,是一个开放的问题。首先,我们不应该永久保留数据,而是一旦不再需要就立即清除数据【111,112】。清除数据与不变性的想法背道而驰(请参阅“[不变性的局限性](ch11.md#不变性的局限性)”),但这是可以解决的问题。我所看到的一种很有前景的方法是通过加密协议来实施访问控制,而不仅仅是通过策略【113,114】。总的来说,文化与态度的改变是必要的。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
在本章中,我们讨论了设计数据系统的新方式,而且也包括了我的个人观点,以及对未来的猜测。我们从这样一种观察开始:没有单种工具能高效服务所有可能的用例,因此应用必须组合使用几种不同的软件才能实现其目标。我们讨论了如何使用批处理与事件流来解决这一**数据集成(data integration)** 问题,以便让数据变更在不同系统之间流动。
|
||||
@ -887,7 +886,6 @@ ACID意义下的一致性(请参阅“[一致性](ch7.md#一致性)”)基
|
||||
由于软件和数据对世界产生了如此巨大的影响,我们工程师们必须牢记,我们有责任为我们想要的那种世界而努力:一个尊重人们,尊重人性的世界。我希望我们能够一起为实现这一目标而努力。
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Rachid Belaid: “[Postgres Full-Text Search is Good Enough!](http://rachbelaid.com/postgres-full-text-search-is-good-enough/),” *rachbelaid.com*, July 13, 2015.
|
||||
@ -1006,8 +1004,6 @@ ACID意义下的一致性(请参阅“[一致性](ch7.md#一致性)”)基
|
||||
1. Phillip Rogaway: “[The Moral Character of Cryptographic Work](http://web.cs.ucdavis.edu/~rogaway/papers/moral-fn.pdf),” Cryptology ePrint 2015/1162, December 2015.
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
37
ch2.md
37
ch2.md
@ -29,7 +29,6 @@
|
||||
在本章中,我们将研究一系列用于数据存储和查询的通用数据模型(前面列表中的第2点)。特别地,我们将比较关系模型,文档模型和少量基于图形的数据模型。我们还将查看各种查询语言并比较它们的用例。在[第三章](ch3.md)中,我们将讨论存储引擎是如何工作的。也就是说,这些数据模型实际上是如何实现的(列表中的第3点)。
|
||||
|
||||
|
||||
|
||||
## 关系模型与文档模型
|
||||
|
||||
现在最著名的数据模型可能是SQL。它基于Edgar Codd在1970年提出的关系模型【1】:数据被组织成**关系**(SQL中称作**表**),其中每个关系是**元组**(SQL中称作**行**)的无序集合。
|
||||
@ -162,6 +161,7 @@ JSON表示比[图2-1](img/fig2-1.png)中的多表模式具有更好的**局部
|
||||
***推荐***
|
||||
|
||||
假设你想添加一个新的功能:一个用户可以为另一个用户写一个推荐。在用户的简历上显示推荐,并附上推荐用户的姓名和照片。如果推荐人更新他们的照片,那他们写的任何推荐都需要显示新的照片。因此,推荐应该拥有作者个人简介的引用。
|
||||
|
||||
![](img/fig2-3.png)
|
||||
|
||||
**图2-3 公司名不仅是字符串,还是一个指向公司实体的链接(LinkedIn截图)**
|
||||
@ -248,8 +248,8 @@ CODASYL中的查询是通过利用遍历记录列和跟随访问路径表在数
|
||||
|
||||
```go
|
||||
if (user && user.name && !user.first_name) {
|
||||
// Documents written before Dec 8, 2013 don't have first_name
|
||||
user.first_name = user.name.split(" ")[0];
|
||||
// Documents written before Dec 8, 2013 don't have first_name
|
||||
user.first_name = user.name.split(" ")[0];
|
||||
}
|
||||
```
|
||||
|
||||
@ -297,7 +297,6 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL
|
||||
[^v]: Codd对关系模型【1】的原始描述实际上允许在关系模式中与JSON文档非常相似。他称之为**非简单域(nonsimple domains)**。这个想法是,一行中的值不一定是一个像数字或字符串一样的原始数据类型,也可以是一个嵌套的关系(表),因此可以把一个任意嵌套的树结构作为一个值,这很像30年后添加到SQL中的JSON或XML支持。
|
||||
|
||||
|
||||
|
||||
## 数据查询语言
|
||||
|
||||
当引入关系模型时,关系模型包含了一种查询数据的新方法:SQL是一种 **声明式** 查询语言,而IMS和CODASYL使用 **命令式** 代码来查询数据库。那是什么意思?
|
||||
@ -370,7 +369,7 @@ SQL示例不确保任何特定的顺序,因此不在意顺序是否改变。
|
||||
|
||||
```css
|
||||
li.selected > p {
|
||||
background-color: blue;
|
||||
background-color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
@ -429,8 +428,8 @@ MapReduce既不是一个声明式的查询语言,也不是一个完全命令
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
date_trunc('month', observation_timestamp) AS observation_month,
|
||||
sum(num_animals) AS total_animals
|
||||
date_trunc('month', observation_timestamp) AS observation_month,
|
||||
sum(num_animals) AS total_animals
|
||||
FROM observations
|
||||
WHERE family = 'Sharks'
|
||||
GROUP BY observation_month;
|
||||
@ -508,7 +507,6 @@ db.observations.aggregate([
|
||||
聚合管道语言与SQL的子集具有类似表现力,但是它使用基于JSON的语法而不是SQL的英语句子式语法; 这种差异也许是口味问题。这个故事的寓意是NoSQL系统可能会发现自己意外地重新发明了SQL,尽管带着伪装。
|
||||
|
||||
|
||||
|
||||
## 图数据模型
|
||||
|
||||
如我们之前所见,多对多关系是不同数据模型之间具有区别性的重要特征。如果你的应用程序大多数的关系是一对多关系(树状结构化数据),或者大多数记录之间不存在关系,那么使用文档模型是合适的。
|
||||
@ -600,12 +598,12 @@ Cypher是属性图的声明式查询语言,为Neo4j图形数据库而发明【
|
||||
|
||||
```cypher
|
||||
CREATE
|
||||
(NAmerica:Location {name:'North America', type:'continent'}),
|
||||
(USA:Location {name:'United States', type:'country' }),
|
||||
(Idaho:Location {name:'Idaho', type:'state' }),
|
||||
(Lucy:Person {name:'Lucy' }),
|
||||
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
|
||||
(Lucy) -[:BORN_IN]-> (Idaho)
|
||||
(NAmerica:Location {name:'North America', type:'continent'}),
|
||||
(USA:Location {name:'United States', type:'country' }),
|
||||
(Idaho:Location {name:'Idaho', type:'state' }),
|
||||
(Lucy:Person {name:'Lucy' }),
|
||||
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
|
||||
(Lucy) -[:BORN_IN]-> (Idaho)
|
||||
```
|
||||
|
||||
当[图2-5](img/fig2-5.png)的所有顶点和边被添加到数据库后,让我们提些有趣的问题:例如,找到所有从美国移民到欧洲的人的名字。更确切地说,这里我们想要找到符合下面条件的所有顶点,并且返回这些顶点的`name`属性:该顶点拥有一条连到美国任一位置的`BORN_IN`边,和一条连到欧洲的任一位置的`LIVING_IN`边。
|
||||
@ -616,8 +614,8 @@ CREATE
|
||||
|
||||
```cypher
|
||||
MATCH
|
||||
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
|
||||
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
|
||||
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
|
||||
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
|
||||
RETURN person.name
|
||||
```
|
||||
|
||||
@ -872,7 +870,7 @@ born_in(lucy, idaho).
|
||||
within_recursive(Location, Name) :- name(Location, Name). /* Rule 1 */
|
||||
|
||||
within_recursive(Location, Name) :- within(Location, Via), /* Rule 2 */
|
||||
within_recursive(Via, Name).
|
||||
within_recursive(Via, Name).
|
||||
|
||||
migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */
|
||||
born_in(Person, BornLoc),
|
||||
@ -881,7 +879,6 @@ migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */
|
||||
within_recursive(LivingLoc, LivingIn).
|
||||
|
||||
?- migrated(Who, 'United States', 'Europe'). /* Who = 'Lucy'. */
|
||||
|
||||
```
|
||||
|
||||
Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步。我们定义**规则**,以将新谓语告诉数据库:在这里,我们定义了两个新的谓语,`within_recursive`和`migrated`。这些谓语不是存储在数据库中的三元组中,而是它们是从数据或其他规则派生而来的。规则可以引用其他规则,就像函数可以调用其他函数或者递归地调用自己一样。像这样,复杂的查询可以一次构建其中的一小块。
|
||||
@ -907,7 +904,6 @@ Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步
|
||||
相对于本章讨论的其他查询语言,我们需要采取不同的思维方式来思考Datalog方法,但这是一种非常强大的方法,因为规则可以在不同的查询中进行组合和重用。虽然对于简单的一次性查询,显得不太方便,但是它可以更好地处理数据很复杂的情况。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
数据模型是一个巨大的课题,在本章中,我们快速浏览了各种不同的模型。我们没有足够的空间来详细介绍每个模型的细节,但是希望这个概述足以激起你的兴趣,以更多地了解最适合你的应用需求的模型。
|
||||
@ -932,9 +928,6 @@ Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步
|
||||
让我们暂时将其放在一边。在[下一章](ch3.md)中,我们将讨论在**实现**本章描述的数据模型时会遇到的一些权衡。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Edgar F. Codd: “[A Relational Model of Data for Large Shared Data Banks](https://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf),” *Communications of the ACM*, volume 13, number 6, pages 377–387, June 1970. [doi:10.1145/362384.362685](http://dx.doi.org/10.1145/362384.362685)
|
||||
|
68
ch3.md
68
ch3.md
@ -26,14 +26,14 @@
|
||||
世界上最简单的数据库可以用两个Bash函数实现:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
db_set () {
|
||||
echo "$1,$2" >> database
|
||||
}
|
||||
#!/bin/bash
|
||||
db_set () {
|
||||
echo "$1,$2" >> database
|
||||
}
|
||||
|
||||
db_get () {
|
||||
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
|
||||
}
|
||||
db_get () {
|
||||
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
|
||||
}
|
||||
```
|
||||
|
||||
这两个函数实现了键值存储的功能。执行 `db_set key value` 会将 **键(key)** 和**值(value)** 存储在数据库中。键和值(几乎)可以是你喜欢的任何东西,例如,值可以是JSON文档。然后调用 `db_get key` 会查找与该键关联的最新值并将其返回。
|
||||
@ -41,26 +41,26 @@
|
||||
麻雀虽小,五脏俱全:
|
||||
|
||||
```bash
|
||||
$ db_set 123456 '{"name":"London","attractions":["Big Ben","London Eye"]}'
|
||||
$ db_set 123456 '{"name":"London","attractions":["Big Ben","London Eye"]}'
|
||||
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}'
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}'
|
||||
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
```
|
||||
|
||||
底层的存储格式非常简单:一个文本文件,每行包含一条逗号分隔的键值对(忽略转义问题的话,大致与CSV文件类似)。每次对 `db_set` 的调用都会向文件末尾追加记录,所以更新键的时候旧版本的值不会被覆盖 —— 因而查找最新值的时候,需要找到文件中键最后一次出现的位置(因此 `db_get` 中使用了 `tail -n 1 ` 。)
|
||||
|
||||
```bash
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'
|
||||
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
|
||||
$ cat database
|
||||
123456,{"name":"London","attractions":["Big Ben","London Eye"]}
|
||||
42,{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
42,{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
$ cat database
|
||||
123456,{"name":"London","attractions":["Big Ben","London Eye"]}
|
||||
42,{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
42,{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
```
|
||||
|
||||
`db_set` 函数对于极其简单的场景其实有非常好的性能,因为在文件尾部追加写入通常是非常高效的。与`db_set`做的事情类似,许多数据库在内部使用了**日志(log)**,也就是一个 **仅追加(append-only)** 的数据文件。真正的数据库有更多的问题需要处理(如并发控制,回收硬盘空间以避免日志无限增长,处理错误与部分写入的记录),但基本原理是一样的。日志极其有用,我们还将在本书的其它部分重复见到它好几次。
|
||||
@ -323,8 +323,8 @@ B树在数据库架构中是非常根深蒂固的,为许多工作负载都提
|
||||
**多维索引(multi-dimensional index)** 是一种查询多个列的更一般的方法,这对于地理空间数据尤为重要。例如,餐厅搜索网站可能有一个数据库,其中包含每个餐厅的经度和纬度。当用户在地图上查看餐馆时,网站需要搜索用户正在查看的矩形地图区域内的所有餐馆。这需要一个二维范围查询,如下所示:
|
||||
|
||||
```sql
|
||||
SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
|
||||
AND longitude > -0.1162 AND longitude < -0.1004;
|
||||
SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
|
||||
AND longitude > -0.1162 AND longitude < -0.1004;
|
||||
```
|
||||
|
||||
一个标准的B树或者LSM树索引不能够高效地处理这种查询:它可以返回一个纬度范围内的所有餐馆(但经度可能是任意值),或者返回在同一个经度范围内的所有餐馆(但纬度可能是北极和南极之间的任意地方),但不能同时满足两个条件。
|
||||
@ -454,18 +454,18 @@ Teradata、Vertica、SAP HANA和ParAccel等数据仓库供应商通常使用昂
|
||||
**例3-1 分析人们是否更倾向于在一周的某一天购买新鲜水果或糖果**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
dim_date.weekday,
|
||||
dim_product.category,
|
||||
SUM(fact_sales.quantity) AS quantity_sold
|
||||
FROM fact_sales
|
||||
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
|
||||
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
|
||||
WHERE
|
||||
dim_date.year = 2013 AND
|
||||
dim_product.category IN ('Fresh fruit', 'Candy')
|
||||
GROUP BY
|
||||
dim_date.weekday, dim_product.category;
|
||||
SELECT
|
||||
dim_date.weekday,
|
||||
dim_product.category,
|
||||
SUM(fact_sales.quantity) AS quantity_sold
|
||||
FROM fact_sales
|
||||
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
|
||||
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
|
||||
WHERE
|
||||
dim_date.year = 2013 AND
|
||||
dim_product.category IN ('Fresh fruit', 'Candy')
|
||||
GROUP BY
|
||||
dim_date.weekday, dim_product.category;
|
||||
```
|
||||
|
||||
我们如何有效地执行这个查询?
|
||||
@ -502,13 +502,13 @@ Teradata、Vertica、SAP HANA和ParAccel等数据仓库供应商通常使用昂
|
||||
这些位图索引非常适合数据仓库中常见的各种查询。例如:
|
||||
|
||||
```sql
|
||||
WHERE product_sk IN(30,68,69)
|
||||
WHERE product_sk IN(30,68,69)
|
||||
```
|
||||
|
||||
加载`product_sk = 30`、`product_sk = 68`和`product_sk = 69`这三个位图,并计算三个位图的按位或(OR),这可以非常有效地完成。
|
||||
|
||||
```sql
|
||||
WHERE product_sk = 31 AND store_sk = 3
|
||||
WHERE product_sk = 31 AND store_sk = 3
|
||||
```
|
||||
|
||||
加载`product_sk = 31`和`store_sk = 3`的位图,并计算按位与(AND)。这是因为列按照相同的顺序包含行,因此一列的位图中的第k位和另一列的位图中的第k位对应相同的行。
|
||||
|
11
ch4.md
11
ch4.md
@ -213,8 +213,8 @@ record Person {
|
||||
"fields": [
|
||||
{"name": "userName", "type": "string"},
|
||||
{"name": "favoriteNumber", "type": ["null", "long"], "default": null},
|
||||
{"name": "interests", "type": {"type": "array", "items": "string"}
|
||||
]
|
||||
{"name": "interests", "type": {"type": "array", "items": "string"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@ -316,7 +316,6 @@ Avro为静态类型编程语言提供了可选的代码生成功能,但是它
|
||||
总而言之,模式进化允许与JSON数据库提供的无模式/读时模式相同的灵活性(请参阅“[文档模型中的模式灵活性](ch2.md#文档模型中的模式灵活性)”),同时还可以更好地保证数据和更好的工具。
|
||||
|
||||
|
||||
|
||||
## 数据流的类型
|
||||
|
||||
在本章的开始部分,我们曾经说过,无论何时你想要将某些数据发送到不共享内存的另一个进程,例如,只要你想通过网络发送数据或将其写入文件,就需要将它编码为一个字节序列。然后我们讨论了做这个的各种不同的编码。
|
||||
@ -330,7 +329,6 @@ Avro为静态类型编程语言提供了可选的代码生成功能,但是它
|
||||
* 通过异步消息传递(请参阅“[消息传递中的数据流](#消息传递中的数据流)”)
|
||||
|
||||
|
||||
|
||||
### 数据库中的数据流
|
||||
|
||||
在数据库中,写入数据库的过程对数据进行编码,从数据库读取的过程对数据进行解码。可能只有一个进程访问数据库,在这种情况下,读者只是相同进程的后续版本 - 在这种情况下,你可以考虑将数据库中的内容存储为向未来的自我发送消息。
|
||||
@ -362,7 +360,6 @@ Avro为静态类型编程语言提供了可选的代码生成功能,但是它
|
||||
[^v]: 除了MySQL,即使并非真的必要,它也经常会重写整个表,正如“[文档模型中的模式灵活性](ch3.md#文档模型中的模式灵活性)”中所提到的。
|
||||
|
||||
|
||||
|
||||
#### 归档存储
|
||||
|
||||
也许你不时为数据库创建一个快照,例如备份或加载到数据仓库(请参阅“[数据仓库](ch3.md#数据仓库)”)。在这种情况下,即使源数据库中的原始编码包含来自不同时代的模式版本的混合,数据转储通常也将使用最新模式进行编码。既然你不管怎样都要拷贝数据,那么你可以对这个数据拷贝进行一致的编码。
|
||||
@ -372,7 +369,6 @@ Avro为静态类型编程语言提供了可选的代码生成功能,但是它
|
||||
在[第十章](ch10.md)中,我们将详细讨论使用档案存储中的数据。
|
||||
|
||||
|
||||
|
||||
### 服务中的数据流:REST与RPC
|
||||
|
||||
当你需要通过网络进行通信的进程时,安排该通信的方式有几种。最常见的安排是有两个角色:客户端和服务器。服务器通过网络公开API,并且客户端可以连接到服务器以向该API发出请求。服务器公开的API被称为服务。
|
||||
@ -500,8 +496,6 @@ Actor模型是单个进程中并发的编程模型。逻辑被封装在actor中
|
||||
* 在Erlang OTP中,对记录模式进行更改是非常困难的(尽管系统具有许多为高可用性设计的功能)。 滚动升级是可能的,但需要仔细计划【53】。 一个新的实验性的`maps`数据类型(2014年在Erlang R17中引入的类似于JSON的结构)可能使得这个数据类型在未来更容易【54】。
|
||||
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
在本章中,我们研究了将数据结构转换为网络中的字节或磁盘上的字节的几种方法。我们看到了这些编码的细节不仅影响其效率,更重要的是也影响了应用程序的体系结构和部署它们的选项。
|
||||
@ -525,7 +519,6 @@ Actor模型是单个进程中并发的编程模型。逻辑被封装在actor中
|
||||
我们可以小心地得出这样的结论:前向兼容性和滚动升级在某种程度上是可以实现的。愿你的应用程序的演变迅速、敏捷部署。
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. “[Java Object Serialization Specification](http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html),” *docs.oracle.com*, 2010.
|
||||
|
20
ch5.md
20
ch5.md
@ -182,7 +182,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
基于触发器的复制通常比其他复制方法具有更高的开销,并且比数据库的内置复制更容易出错,也有很多限制。然而由于其灵活性,仍然是很有用的。
|
||||
|
||||
|
||||
|
||||
## 复制延迟问题
|
||||
|
||||
容忍节点故障只是需要复制的一个原因。正如在[第二部分](part-ii.md)的介绍中提到的,另一个原因是可伸缩性(处理比单个机器更多的请求)和延迟(让副本在地理位置上更接近用户)。
|
||||
@ -231,7 +230,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
* 如果副本分布在不同的数据中心,很难保证来自不同设备的连接会路由到同一数据中心。 (例如,用户的台式计算机使用家庭宽带连接,而移动设备使用蜂窝数据网络,则设备的网络路线可能完全不同)。如果你的方法需要读主库,可能首先需要把来自同一用户的请求路由到同一个数据中心。
|
||||
|
||||
|
||||
|
||||
### 单调读
|
||||
|
||||
从异步从库读取第二个异常例子是,用户可能会遇到 **时光倒流(moving backward in time)**。
|
||||
@ -247,7 +245,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
实现单调读取的一种方式是确保每个用户总是从同一个副本进行读取(不同的用户可以从不同的副本读取)。例如,可以基于用户ID的散列来选择副本,而不是随机选择副本。但是,如果该副本失败,用户的查询将需要重新路由到另一个副本。
|
||||
|
||||
|
||||
|
||||
### 一致前缀读
|
||||
|
||||
第三个复制延迟例子违反了因果律。 想象一下Poons先生和Cake夫人之间的以下简短对话:
|
||||
@ -292,7 +289,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
单节点事务已经存在了很长时间。然而在走向分布式(复制和分区)数据库时,许多系统放弃了事务。声称事务在性能和可用性上的代价太高,并断言在可伸缩系统中最终一致性是不可避免的。这个叙述有一些道理,但过于简单了,本书其余部分将提出更为细致的观点。第七章和第九章将回到事务的话题,并讨论一些替代机制。
|
||||
|
||||
|
||||
|
||||
## 多主复制
|
||||
|
||||
本章到目前为止,我们只考虑使用单个领导者的复制架构。 虽然这是一种常见的方法,但也有一些有趣的选择。
|
||||
@ -397,7 +393,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
* 用一种可保留所有信息的显式数据结构来记录冲突,并编写解决冲突的应用程序代码(也许通过提示用户的方式)。
|
||||
|
||||
|
||||
|
||||
#### 自定义冲突解决逻辑
|
||||
|
||||
作为解决冲突最合适的方法可能取决于应用程序,大多数多主复制工具允许使用应用程序代码编写冲突解决逻辑。该代码可以在写入或读取时执行:
|
||||
@ -413,7 +408,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
请注意,冲突解决通常适用于单个行或文档层面,而不是整个事务【36】。因此,如果你有一个事务会原子性地进行几次不同的写入(请参阅[第七章](ch7.md),对于冲突解决而言,每个写入仍需分开单独考虑。
|
||||
|
||||
|
||||
|
||||
> #### 自动冲突解决
|
||||
>
|
||||
> 冲突解决规则可能很快变得复杂,并且自定义代码可能容易出错。亚马逊是一个经常被引用的例子,由于冲突解决处理程序令人意外的效果:一段时间以来,购物车上的冲突解决逻辑将保留添加到购物车的物品,但不包括从购物车中移除的物品。因此,顾客有时会看到物品重新出现在他们的购物车中,即使他们之前已经被移走【37】。
|
||||
@ -428,7 +422,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
>
|
||||
|
||||
|
||||
|
||||
#### 什么是冲突?
|
||||
|
||||
有些冲突是显而易见的。在[图5-7](img/fig5-7.png)的例子中,两个写操作并发地修改了同一条记录中的同一个字段,并将其设置为两个不同的值。毫无疑问这是一个冲突。
|
||||
@ -438,7 +431,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
现在还没有一个现成的答案,但在接下来的章节中,我们将更好地了解这个问题。我们将在[第七章](ch7.md)中看到更多的冲突示例,在[第十二章](ch12.md)中我们将讨论用于检测和解决复制系统中冲突的可伸缩方法。
|
||||
|
||||
|
||||
|
||||
### 多主复制拓扑
|
||||
|
||||
**复制拓扑**(replication topology)描述写入从一个节点传播到另一个节点的通信路径。如果你有两个领导者,如[图5-7](img/fig5-7.png)所示,只有一个合理的拓扑结构:领导者1必须把他所有的写到领导者2,反之亦然。当有两个以上的领导,各种不同的拓扑是可能的。[图5-8](img/fig5-8.png)举例说明了一些例子。
|
||||
@ -470,7 +462,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
如果你正在使用具有多领导者复制功能的系统,那么应该了解这些问题,仔细阅读文档,并彻底测试你的数据库,以确保它确实提供了你认为具有的保证。
|
||||
|
||||
|
||||
|
||||
## 无主复制
|
||||
|
||||
我们在本章到目前为止所讨论的复制方法 ——单主复制、多主复制——都是这样的想法:客户端向一个主库发送写请求,而数据库系统负责将写入复制到其他副本。主库决定写入的顺序,而从库按相同顺序应用主库的写入。
|
||||
@ -541,7 +532,6 @@ PostgreSQL和Oracle等使用这种复制方法【16】。主要缺点是日志
|
||||
如果少于所需的w或r节点可用,则写入或读取将返回错误。 由于许多原因,节点可能不可用:因为执行操作的错误(由于磁盘已满而无法写入),因为节点关闭(崩溃,关闭电源),由于客户端和服务器节点之间的网络中断,或任何其他原因。 我们只关心节点是否返回了成功的响应,而不需要区分不同类型的错误。
|
||||
|
||||
|
||||
|
||||
### 法定人数一致性的局限性
|
||||
|
||||
如果你有n个副本,并且你选择w和r,使得$w + r> n$,你通常可以期望每个键的读取都能返回最近写入的值。情况就是这样,因为你写入的节点集合和你读取的节点集合必须重叠。也就是说,你读取的节点中必须至少有一个具有最新值的节点(如[图5-11](img/fig5-11.png)所示)。
|
||||
@ -650,7 +640,6 @@ LWW实现了最终收敛的目标,但以**持久性**为代价:如果同一
|
||||
因此,只要有两个操作A和B,就有三种可能性:A在B之前发生,或者B在A之前发生,或者A和B并发。我们需要的是一个算法来告诉我们两个操作是否是并发的。如果一个操作发生在另一个操作之前,则后面的操作应该覆盖较早的操作,但是如果这些操作是并发的,则存在需要解决的冲突。
|
||||
|
||||
|
||||
|
||||
> #### 并发性,时间和相对性
|
||||
>
|
||||
> 如果两个操作 **“同时”** 发生,似乎应该称为并发——但事实上,它们在字面时间上重叠与否并不重要。由于分布式系统中的时钟问题,现实中是很难判断两个事件是否**同时**发生的,这个问题我们将在[第八章](ch8.md)中详细讨论。
|
||||
@ -660,7 +649,6 @@ LWW实现了最终收敛的目标,但以**持久性**为代价:如果同一
|
||||
> 在计算机系统中,即使光速原则上允许一个操作影响另一个操作,但两个操作也可能是**并行的**。例如,如果网络缓慢或中断,两个操作间可能会出现一段时间间隔,但仍然是并发的,因为网络问题阻止一个操作意识到另一个操作的存在。
|
||||
|
||||
|
||||
|
||||
#### 捕获"此前发生"关系
|
||||
|
||||
来看一个算法,它确定两个操作是否为并发的,还是一个在另一个之前。为了简单起见,我们从一个只有一个副本的数据库开始。一旦我们已经制定了如何在单个副本上完成这项工作,我们可以将该方法推广到具有多个副本的无领导者数据库。
|
||||
@ -742,7 +730,6 @@ LWW实现了最终收敛的目标,但以**持久性**为代价:如果同一
|
||||
通过在副本上读,能够处理比单机更大的读取量
|
||||
|
||||
|
||||
|
||||
尽管是一个简单的目标 - 在几台机器上保留相同数据的副本,但复制却是一个非常棘手的问题。它需要仔细考虑并发和所有可能出错的事情,并处理这些故障的后果。至少,我们需要处理不可用的节点和网络中断(这还不包括更隐蔽的故障,例如由于软件错误导致的静默数据损坏)。
|
||||
|
||||
我们讨论了复制的三种主要方法:
|
||||
@ -771,22 +758,17 @@ LWW实现了最终收敛的目标,但以**持久性**为代价:如果同一
|
||||
|
||||
***单调读***
|
||||
|
||||
用户在看到某一个时间点的数据后,他们不应该再看到某个更早时间点的数据。
|
||||
用户在看到某一个时间点的数据后,他们不应该再看到某个更早时间点的数据。
|
||||
|
||||
***一致前缀读***
|
||||
|
||||
用户应该看到数据处于一种具有因果意义的状态:例如,按正确的顺序看到一个问题和对应的回答。
|
||||
|
||||
|
||||
|
||||
最后,我们讨论了多领导者和无领导者复制方法所固有的并发问题:因为他们允许多个写入并发发生,这可能会导致冲突。我们研究了一个数据库可能使用的算法来确定一个操作是否发生在另一个操作之前,或者它们是否同时发生。我们还谈到了通过合并并发更新来解决冲突的方法。
|
||||
|
||||
在下一章中,我们将继续研究分布在多个机器上的数据,通过复制的同僚:将大数据集分割成分区。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Bruce G. Lindsay, Patricia Griffiths Selinger, C. Galtieri, et al.: “[Notes on Distributed Databases](http://domino.research.ibm.com/library/cyberdig.nsf/papers/A776EC17FC2FCE73852579F100578964/$File/RJ2571.pdf),” IBM Research, Research Report RJ2571(33471), July 1979.
|
||||
|
3
ch6.md
3
ch6.md
@ -311,7 +311,6 @@ Couchbase不会自动重新平衡,这简化了设计。通常情况下,它
|
||||
通过散列进行分区时,通常先提前创建固定数量的分区,为每个节点分配多个分区,并在添加或删除节点时将整个分区从一个节点移动到另一个节点。也可以使用动态分区。
|
||||
|
||||
|
||||
|
||||
两种方法搭配使用也是可行的,例如使用复合主键:使用键的一部分来标识分区,而使用另一部分作为排序顺序。
|
||||
|
||||
我们还讨论了分区和次级索引之间的相互作用。次级索引也需要分区,有两种方法:
|
||||
@ -324,7 +323,6 @@ Couchbase不会自动重新平衡,这简化了设计。通常情况下,它
|
||||
按照设计,多数情况下每个分区是独立运行的 — 这就是分区数据库可以伸缩到多台机器的原因。但是,需要写入多个分区的操作结果可能难以预料:例如,如果写入一个分区成功,但另一个分区失败,会发生什么情况?我们将在下面的章节中讨论这个问题。
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
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 85–98, June 1992. [doi:10.1145/129888.129894](http://dx.doi.org/10.1145/129888.129894)
|
||||
@ -362,7 +360,6 @@ Couchbase不会自动重新平衡,这简化了设计。通常情况下,它
|
||||
1. 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 1–104, November 2013.[doi:10.1561/1900000036](http://dx.doi.org/10.1561/1900000036)
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
6
ch7.md
6
ch7.md
@ -34,7 +34,6 @@
|
||||
本章同时适用于单机数据库与分布式数据库;在[第八章](ch8.md)中将重点讨论仅出现在分布式系统中的特殊挑战。
|
||||
|
||||
|
||||
|
||||
## 事务的棘手概念
|
||||
|
||||
现今,几乎所有的关系型数据库和一些非关系数据库都支持**事务**。其中大多数遵循IBM System R(第一个SQL数据库)在1975年引入的风格【1,2,3】。40年里,尽管一些实现细节发生了变化,但总体思路大同小异:MySQL,PostgreSQL,Oracle,SQL Server等数据库中的事务支持与System R异乎寻常地相似。
|
||||
@ -417,7 +416,7 @@ UPDATE counters SET value = value + 1 WHERE key = 'foo';
|
||||
```plsql
|
||||
BEGIN TRANSACTION;
|
||||
SELECT * FROM figures
|
||||
WHERE name = 'robot' AND game_id = 222
|
||||
WHERE name = 'robot' AND game_id = 222
|
||||
FOR UPDATE;
|
||||
|
||||
-- 检查玩家的操作是否有效,然后更新先前SELECT返回棋子的位置。
|
||||
@ -526,7 +525,7 @@ BEGIN TRANSACTION;
|
||||
-- 检查所有现存的与12:00~13:00重叠的预定
|
||||
SELECT COUNT(*) FROM bookings
|
||||
WHERE room_id = 123 AND
|
||||
end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';
|
||||
end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';
|
||||
|
||||
-- 如果之前的查询返回0
|
||||
INSERT INTO bookings(room_id, start_time, end_time, user_id)
|
||||
@ -877,7 +876,6 @@ WHERE room_id = 123 AND
|
||||
本章主要是在单机数据库的上下文中,探讨了各种想法和算法。分布式数据库中的事务,则引入了一系列新的困难挑战,我们将在接下来的两章中讨论。
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Donald D. Chamberlin, Morton M. Astrahan, Michael W. Blasgen, et al.: “[A History and Evaluation of System R](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.348&rep=rep1&type=pdf),” *Communications of the ACM*, volume 24, number 10, pages 632–646, October 1981. [doi:10.1145/358769.358784](http://dx.doi.org/10.1145/358769.358784)
|
||||
|
6
ch8.md
6
ch8.md
@ -89,7 +89,6 @@
|
||||
> 虽然更可靠的高级系统并不完美,但它仍然有用,因为它处理了一些棘手的低级错误,所以其余的错误通常更容易推理和处理。我们将在“[数据库的端到端原则](ch12.md#数据库的端到端原则)”中进一步探讨这个问题。
|
||||
|
||||
|
||||
|
||||
## 不可靠的网络
|
||||
|
||||
正如在[第二部分](part-ii.md)的介绍中所讨论的那样,我们在本书中关注的分布式系统是无共享的系统,即通过网络连接的一堆机器。网络是这些机器可以通信的唯一途径——我们假设每台机器都有自己的内存和磁盘,一台机器不能访问另一台机器的内存或磁盘(除了通过网络向服务器发出请求)。
|
||||
@ -441,7 +440,6 @@ while (true) {
|
||||
这些措施不能完全阻止垃圾回收暂停,但可以有效地减少它们对应用的影响。
|
||||
|
||||
|
||||
|
||||
## 知识、真相与谎言
|
||||
|
||||
本章到目前为止,我们已经探索了分布式系统与运行在单台计算机上的程序的不同之处:没有共享内存,只有通过可变延迟的不可靠网络传递的消息,系统可能遭受部分失效,不可靠的时钟和处理暂停。
|
||||
@ -655,7 +653,6 @@ Web应用程序确实需要预期受终端用户控制的客户端(如Web浏
|
||||
本章一直在讲存在的问题,给我们展现了一幅黯淡的前景。在[下一章](ch9.md)中,我们将继续讨论解决方案,并讨论一些旨在解决分布式系统中所有问题的算法。
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Mark Cavage: Just No Getting Around It: You’re Building a Distributed System](http://queue.acm.org/detail.cfm?id=2482856),” *ACM Queue*, volume 11, number 4, pages 80-89, April 2013. [doi:10.1145/2466486.2482856](http://dx.doi.org/10.1145/2466486.2482856)
|
||||
@ -757,9 +754,6 @@ Web应用程序确实需要预期受终端用户控制的客户端(如Web浏
|
||||
[^译著1]: 原诗为:Hey I just met you. The network’s laggy. But here’s my data. So store it maybe.Hey, 应改编自《Call Me Maybe》歌词:I just met you, And this is crazy, But here's my number, So call me, maybe?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
16
ch9.md
16
ch9.md
@ -26,7 +26,6 @@
|
||||
分布式系统领域的研究人员几十年来一直在研究这些主题,所以有很多资料—— 我们只能介绍一些皮毛。在本书中,我们没有空间去详细介绍形式模型和证明的细节,所以我们将坚持非正式的直觉。如果你有兴趣,参考文献可以提供更多的深度。
|
||||
|
||||
|
||||
|
||||
## 一致性保证
|
||||
|
||||
在“[复制延迟问题](ch5.md#复制延迟问题)”中,我们看到了数据库复制中发生的一些时序问题。如果你在同一时刻查看两个数据库节点,则可能在两个节点上看到不同的数据,因为写请求在不同的时间到达不同的节点。无论数据库使用何种复制方法(单主复制,多主复制或无主复制),都会出现这些不一致情况。
|
||||
@ -50,7 +49,6 @@
|
||||
* 在第三节的(“[分布式事务与共识](#分布式事务与共识)”)中将探讨如何原子地提交分布式事务,这将最终引领我们走向共识问题的解决方案。
|
||||
|
||||
|
||||
|
||||
## 线性一致性
|
||||
|
||||
在**最终一致**的数据库,如果你在同一时刻问两个不同副本相同的问题,可能会得到两个不同的答案。这很让人困惑。如果数据库可以提供只有一个副本的假象(即,只有一个数据副本),那么事情就简单太多了。那么每个客户端都会有相同的数据视图,且不必担心复制滞后了。
|
||||
@ -132,7 +130,6 @@
|
||||
这就是线性一致性背后的直觉。 正式的定义【6】更准确地描述了它。 通过记录所有请求和响应的时序,并检查它们是否可以排列成有效的顺序,以测试一个系统的行为是否线性一致性是可能的(尽管在计算上是昂贵的)【11】。
|
||||
|
||||
|
||||
|
||||
> ### 线性一致性与可串行化
|
||||
>
|
||||
> **线性一致性**容易和[**可串行化**](ch7.md#可串行化)相混淆,因为两个词似乎都是类似“可以按顺序排列”的东西。但它们是两种完全不同的保证,区分两者非常重要:
|
||||
@ -241,7 +238,6 @@
|
||||
总而言之,最安全的做法是:假设采用Dynamo风格无主复制的系统不能提供线性一致性。
|
||||
|
||||
|
||||
|
||||
### 线性一致性的代价
|
||||
|
||||
一些复制方法可以提供线性一致性,另一些复制方法则不能,因此深入地探讨线性一致性的优缺点是很有趣的。
|
||||
@ -302,7 +298,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
能找到一个更高效的线性一致存储实现吗?看起来答案是否定的:Attiya和Welch 【47】证明,如果你想要线性一致性,读写请求的响应时间至少与网络延迟的不确定性成正比。在像大多数计算机网络一样具有高度可变延迟的网络中(请参阅“[超时与无穷的延迟](ch8.md#超时与无穷的延迟)”),线性读写的响应时间不可避免地会很高。更快地线性一致算法不存在,但更弱的一致性模型可以快得多,所以对延迟敏感的系统而言,这类权衡非常重要。在[第十二章](ch12.md)中将讨论一些在不牺牲正确性的前提下,绕开线性一致性的方法。
|
||||
|
||||
|
||||
|
||||
## 顺序保证
|
||||
|
||||
之前说过,线性一致寄存器的行为就好像只有单个数据副本一样,且每个操作似乎都是在某个时间点以原子性的方式生效的。这个定义意味着操作是按照某种良好定义的顺序执行的。我们将操作以看上去被执行的顺序连接起来,以此说明了[图9-4](img/fig9-4.png)中的顺序。
|
||||
@ -331,7 +326,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
如果一个系统服从因果关系所规定的顺序,我们说它是**因果一致(causally consistent)** 的。例如,快照隔离提供了因果一致性:当你从数据库中读取到一些数据时,你一定还能够看到其因果前驱(假设在此期间这些数据还没有被删除)。
|
||||
|
||||
|
||||
|
||||
#### 因果顺序不是全序的
|
||||
|
||||
**全序(total order)** 允许任意两个元素进行比较,所以如果有两个元素,你总是可以说出哪个更大,哪个更小。例如,自然数集是全序的:给定两个自然数,比如说5和13,那么你可以告诉我,13大于5。
|
||||
@ -381,7 +375,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
为了确定因果顺序,数据库需要知道应用读取了哪个版本的数据。这就是为什么在 [图5-13 ](img/fig5-13.png)中,来自先前操作的版本号在写入时被传回到数据库的原因。在SSI 的冲突检测中会出现类似的想法,如“[可串行化快照隔离](ch7.md#可串行化快照隔离)”中所述:当事务要提交时,数据库将检查它所读取的数据版本是否仍然是最新的。为此,数据库跟踪哪些数据被哪些事务所读取。
|
||||
|
||||
|
||||
|
||||
### 序列号顺序
|
||||
|
||||
虽然因果是一个重要的理论概念,但实际上跟踪所有的因果关系是不切实际的。在许多应用中,客户端在写入内容之前会先读取大量数据,我们无法弄清写入因果依赖于先前全部的读取内容,还是仅包括其中一部分。显式跟踪所有已读数据意味着巨大的额外开销。
|
||||
@ -417,7 +410,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
* 在分配区块的情况下,某个操作可能会被赋予一个范围在1,001到2,000内的序列号,然而一个因果上更晚的操作可能被赋予一个范围在1到1,000之间的数字。这里序列号与因果关系也是不一致的。
|
||||
|
||||
|
||||
|
||||
#### 兰伯特时间戳
|
||||
|
||||
尽管刚才描述的三个序列号生成器与因果不一致,但实际上有一个简单的方法来产生与因果关系一致的序列号。它被称为兰伯特时间戳,莱斯利·兰伯特(Leslie Lamport)于1978年提出【56】,现在是分布式系统领域中被引用最多的论文之一。
|
||||
@ -540,7 +532,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
现在是时候正面处理共识问题了,我们将在本章的其余部分进行讨论。
|
||||
|
||||
|
||||
|
||||
## 分布式事务与共识
|
||||
|
||||
**共识**是分布式计算中最重要也是最基本的问题之一。从表面上看似乎很简单:非正式地讲,目标只是**让几个节点达成一致(get serveral nodes to agree on something)**。你也许会认为这不会太难。不幸的是,许多出故障的系统都是因为错误地轻信这个问题很容易解决。
|
||||
@ -573,7 +564,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
通过对2PC的学习,我们将继续努力实现更好的一致性算法,比如ZooKeeper(Zab)和etcd(Raft)中使用的算法。
|
||||
|
||||
|
||||
|
||||
### 原子提交与两阶段提交
|
||||
|
||||
在[第七章](ch7.md)中我们了解到,事务原子性的目的是在多次写操作中途出错的情况下,提供一种简单的语义。事务的结果要么是成功提交,在这种情况下,事务的所有写入都是持久化的;要么是中止,在这种情况下,事务的所有写入都被回滚(即撤消或丢弃)。
|
||||
@ -664,7 +654,6 @@ CAP定理的正式定义仅限于很狭隘的范围【30】,它只考虑了一
|
||||
通常,非阻塞原子提交需要一个**完美的故障检测器(perfect failure detector)**【67,71】—— 即一个可靠的机制来判断一个节点是否已经崩溃。在具有无限延迟的网络中,超时并不是一种可靠的故障检测机制,因为即使没有节点崩溃,请求也可能由于网络问题而超时。出于这个原因,2PC仍然被使用,尽管大家都清楚可能存在协调者故障的问题。
|
||||
|
||||
|
||||
|
||||
### 实践中的分布式事务
|
||||
|
||||
分布式事务的名声毁誉参半,尤其是那些通过两阶段提交实现的。一方面,它被视作提供了一个难以实现的重要的安全性保证;另一方面,它们因为导致运维问题,造成性能下降,做出超过能力范围的承诺而饱受批评【81,82,83,84】。许多云服务由于其导致的运维问题,而选择不实现分布式事务【85,86】。
|
||||
@ -737,7 +726,6 @@ XA事务解决了保持多个参与者(数据系统)相互一致的现实的
|
||||
这些事实是否意味着我们应该放弃保持几个系统相互一致的所有希望?不完全是 —— 还有其他的办法,可以让我们在没有异构分布式事务的痛苦的情况下实现同样的事情。我们将在[第十一章](ch11.md) 和[第十二章](ch12.md) 回到这些话题。但首先,我们应该概括一下关于**共识**的话题。
|
||||
|
||||
|
||||
|
||||
### 容错共识
|
||||
|
||||
非正式地,共识意味着让几个节点就某事达成一致。例如,如果有几个人**同时(concurrently)** 尝试预订飞机上的最后一个座位,或剧院中的同一个座位,或者尝试使用相同的用户名注册一个帐户。共识算法可以用来确定这些**互不相容(mutually incompatible)** 的操作中,哪一个才是赢家。
|
||||
@ -894,7 +882,6 @@ ZooKeeper和它的小伙伴们可以看作是成员资格服务(membership ser
|
||||
即使它确实存在,仍然可能发生一个节点被共识错误地宣告死亡。但是对于一个系统来说,知道哪些节点构成了当前的成员关系是非常有用的。例如,选择领导者可能意味着简单地选择当前成员中编号最小的成员,但如果不同的节点对现有的成员都有谁有不同意见,则这种方法将不起作用。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
在本章中,我们从几个不同的角度审视了关于一致性与共识的话题。我们深入研究了线性一致性(一种流行的一致性模型):其目标是使多副本数据看起来好像只有一个副本一样,并使其上所有操作都原子性地生效。虽然线性一致性因为简单易懂而很吸引人 —— 它使数据库表现的好像单线程程序中的一个变量一样,但它有着速度缓慢的缺点,特别是在网络延迟很大的环境中。
|
||||
@ -948,7 +935,6 @@ ZooKeeper和它的小伙伴们可以看作是成员资格服务(membership ser
|
||||
这里已经到了本书[第二部分](part-ii.md)的末尾,第二部介绍了复制([第五章](ch5.md)),分区([第六章](ch6.md)),事务([第七章](ch7.md)),分布式系统的故障模型([第八章](ch8.md))以及最后的一致性与共识([第九章](ch9.md))。现在我们已经奠定了扎实的理论基础,我们将在[第三部分](part-iii.md)再次转向更实际的系统,并讨论如何使用异构的组件积木块构建强大的应用。
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Peter Bailis and Ali Ghodsi: “[Eventual Consistency Today: Limitations, Extensions, and Beyond](http://queue.acm.org/detail.cfm?id=2462076),” *ACM Queue*, volume 11, number 3, pages 55-63, March 2013. [doi:10.1145/2460276.2462076](http://dx.doi.org/10.1145/2460276.2462076)
|
||||
@ -1063,8 +1049,6 @@ ZooKeeper和它的小伙伴们可以看作是成员资格服务(membership ser
|
||||
1. Kenneth P. Birman: “[A History of the Virtual Synchrony Replication Model](https://www.truststc.org/pubs/713.html),” in *Replication: Theory and Practice*, Springer LNCS volume 5959, chapter 6, pages 91–120, 2010. ISBN: 978-3-642-11293-5, [doi:10.1007/978-3-642-11294-2_6](http://dx.doi.org/10.1007/978-3-642-11294-2_6)
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
@ -9,7 +9,6 @@ Martin是一位常规会议演讲者,博主和开源贡献者。他认为,
|
||||
![](http://martin.kleppmann.com/2017/03/ddia-poster.jpg)
|
||||
|
||||
|
||||
|
||||
## 关于译者
|
||||
|
||||
[冯若航](https://vonng.com/about)
|
||||
@ -19,7 +18,6 @@ PostgreSQL DBA @ TanTan
|
||||
Alibaba+-Finplus 架构师/全栈工程师 (2015 ~ 2017)
|
||||
|
||||
|
||||
|
||||
## 后记
|
||||
|
||||
《设计数据密集型应用》封面上的动物是**印度野猪(Sus scrofa cristatus)**,它是在印度、缅甸、尼泊尔、斯里兰卡和泰国发现的一种野猪的亚种。与欧洲野猪不同,它们有更高的背部鬃毛,没有体表绒毛,以及更大更直的头骨。
|
||||
|
@ -10,7 +10,6 @@
|
||||
第二部分将专门讨论在**分布式数据系统**中特有的问题。
|
||||
|
||||
|
||||
|
||||
## 目录
|
||||
|
||||
|
||||
@ -20,8 +19,6 @@
|
||||
4. [编码与演化](ch4.md)
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目录 | 下一章 |
|
||||
|
@ -70,7 +70,6 @@
|
||||
在本书的[第三部分](part-iii.md)中,将讨论如何将多个(可能是分布式的)数据存储集成为一个更大的系统,以满足复杂的应用需求。 但首先,我们来聊聊分布式的数据。
|
||||
|
||||
|
||||
|
||||
## 索引
|
||||
|
||||
5. [复制](ch5.md)
|
||||
@ -80,9 +79,6 @@
|
||||
9. [一致性与共识](ch9.md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. Ulrich Drepper: “[What Every Programmer Should Know About Memory](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf),” akka‐dia.org, November 21, 2007.
|
||||
|
@ -26,7 +26,6 @@
|
||||
阅读本书后,你能很好地决定哪种技术适合哪种用途,并了解如何将工具组合起来,为一个良好应用架构奠定基础。本书并不足以使你从头开始构建自己的数据库存储引擎,不过幸运的是这基本上很少有必要。你将获得对系统底层发生事情的敏锐直觉,这样你就有能力推理它们的行为,做出优秀的设计决策,并追踪任何可能出现的问题。
|
||||
|
||||
|
||||
|
||||
## 本书的目标读者
|
||||
|
||||
如果你开发的应用具有用于存储或处理数据的某种服务器/后端系统,而且使用网络(例如,Web应用,移动应用或连接到互联网的传感器),那么本书就是为你准备的。
|
||||
@ -45,7 +44,6 @@
|
||||
有时在讨论可伸缩的数据系统时,人们会说:“你又不在谷歌或亚马逊,别操心可伸缩性了,直接上关系型数据库”。这个陈述有一定的道理:为了不必要的伸缩性而设计程序,不仅会浪费不必要的精力,并且可能会把你锁死在一个不灵活的设计中。实际上这是一种“过早优化”的形式。不过,选择合适的工具确实很重要,而不同的技术各有优缺点。我们将看到,关系数据库虽然很重要,但绝不是数据处理的终章。
|
||||
|
||||
|
||||
|
||||
## 本书涉及的领域
|
||||
|
||||
本书并不会尝试告诉读者如何安装或使用特定的软件包或API,因为已经有大量文档给出了详细的使用说明。相反,我们会讨论数据系统的基石——各种原则与利弊权衡,并探讨了不同产品所做出的不同设计决策。
|
||||
@ -69,14 +67,11 @@
|
||||
3. 在[第三部分](part-iii.md)中,我们讨论那些从其他数据集衍生出一些数据集的系统。衍生数据经常出现在异构系统中:当没有单个数据库可以把所有事情都做的很好时,应用需要集成几种不同的数据库,缓存,索引等。在[第十章](ch10.md)中我们将从一种衍生数据的批处理方法开始,然后在此基础上建立在[第十一章](ch11.md)中讨论的流处理。最后,在[第十二章](ch12.md)中,我们将所有内容汇总,讨论在将来构建可靠,可伸缩和可维护的应用程序的方法。
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考文献与延伸阅读
|
||||
|
||||
本书中讨论的大部分内容已经在其它地方以某种形式出现过了 —— 会议演示文稿,研究论文,博客文章,代码,BUG跟踪器,邮件列表,以及工程习惯中。本书总结了不同来源资料中最重要的想法,并在文本中包含了指向原始文献的链接。 如果你想更深入地探索一个领域,那么每章末尾的参考文献都是很好的资源,其中大部分可以免费在线获取。
|
||||
|
||||
|
||||
|
||||
## O‘Reilly Safari
|
||||
|
||||
[Safari](http://oreilly.com/safari) (formerly Safari Books Online) is a membership-based training and reference platform for enterprise, government, educators, and individuals.
|
||||
@ -86,7 +81,6 @@ Members have access to thousands of books, training videos, Learning Paths, inte
|
||||
For more information, please visit http://oreilly.com/safari.
|
||||
|
||||
|
||||
|
||||
## 致谢
|
||||
|
||||
本书融合了学术研究和工业实践的经验,融合并系统化了大量其他人的想法与知识。在计算领域,我们往往会被各种新鲜花样所吸引,但我认为前人完成的工作中,有太多值得我们学习的地方了。本书有800多处引用:文章,博客,讲座,文档等,对我来说这些都是宝贵的学习资源。我非常感谢这些材料的作者分享他们的知识。
|
||||
|
@ -29,7 +29,6 @@
|
||||
本章將從我們所要實現的基礎目標開始:可靠、可伸縮、可維護的資料系統。我們將澄清這些詞語的含義,概述考量這些目標的方法。並回顧一些後續章節所需的基礎知識。在接下來的章節中我們將抽絲剝繭,研究設計資料密集型應用時可能遇到的設計決策。
|
||||
|
||||
|
||||
|
||||
## 關於資料系統的思考
|
||||
|
||||
我們通常認為,資料庫、訊息佇列、快取等工具分屬於幾個差異顯著的類別。雖然資料庫和訊息隊列表面上有一些相似性——它們都會儲存一段時間的資料——但它們有迥然不同的訪問模式,這意味著迥異的效能特徵和實現手段。
|
||||
@ -141,7 +140,6 @@
|
||||
在某些情況下,我們可能會選擇犧牲可靠性來降低開發成本(例如為未經證實的市場開發產品原型)或運營成本(例如利潤率極低的服務),但我們偷工減料時,應該清楚意識到自己在做什麼。
|
||||
|
||||
|
||||
|
||||
## 可伸縮性
|
||||
|
||||
系統今天能可靠執行,並不意味未來也能可靠執行。服務 **降級(degradation)** 的一個常見原因是負載增加,例如:系統負載已經從一萬個併發使用者增長到十萬個併發使用者,或者從一百萬增長到一千萬。也許現在處理的資料量級要比過去大得多。
|
||||
@ -162,8 +160,6 @@
|
||||
|
||||
使用者可以查閱他們關注的人釋出的推文(300k請求/秒)。
|
||||
|
||||
|
||||
|
||||
處理每秒12,000次寫入(發推文的速率峰值)還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量,而是來自**扇出(fan-out)**——每個使用者關注了很多人,也被很多人關注。
|
||||
|
||||
[^ii]: 扇出:從電子工程學中借用的術語,它描述了輸入連線到另一個門輸出的邏輯閘數量。 輸出需要提供足夠的電流來驅動所有連線的輸入。 在事務處理系統中,我們使用它來描述為了服務一個傳入請求而需要執行其他服務的請求數量。
|
||||
@ -275,7 +271,6 @@
|
||||
儘管這些架構是應用程式特定的,但可伸縮的架構通常也是從通用的積木塊搭建而成的,並以常見的模式排列。在本書中,我們將討論這些構件和模式。
|
||||
|
||||
|
||||
|
||||
## 可維護性
|
||||
|
||||
眾所周知,軟體的大部分開銷並不在最初的開發階段,而是在持續的維護階段,包括修復漏洞、保持系統正常執行、調查失效、適配新的平臺、為新的場景進行修改、償還技術債、新增新的功能等等。
|
||||
@ -326,7 +321,6 @@
|
||||
* 行為可預測,最大限度減少意外
|
||||
|
||||
|
||||
|
||||
### 簡單性:管理複雜度
|
||||
|
||||
小型軟體專案可以使用簡單討喜的、富表現力的程式碼,但隨著專案越來越大,程式碼往往變得非常複雜,難以理解。這種複雜度拖慢了所有系統相關人員,進一步增加了維護成本。一個陷入複雜泥潭的軟體專案有時被描述為 **爛泥潭(a big ball of mud)** 【30】。
|
||||
@ -356,7 +350,6 @@
|
||||
修改資料系統並使其適應不斷變化需求的容易程度,是與**簡單性**和**抽象性**密切相關的:簡單易懂的系統通常比複雜系統更容易修改。但由於這是一個非常重要的概念,我們將用一個不同的詞來指代資料系統層面的敏捷性: **可演化性(evolvability)** 【34】。
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
本章探討了一些關於資料密集型應用的基本思考方式。這些原則將指導我們閱讀本書的其餘部分,那裡將會深入技術細節。
|
||||
@ -375,7 +368,6 @@
|
||||
在本書後面的[第三部分](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.
|
||||
@ -414,7 +406,6 @@
|
||||
1. 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)
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
@ -37,7 +37,6 @@ Web和越來越多的基於HTTP/REST的API使互動的請求/響應風格變得
|
||||
在本章中,我們將瞭解MapReduce和其他一些批處理演算法和框架,並探索它們在現代資料系統中的作用。但首先我們將看看使用標準Unix工具的資料處理。即使你已經熟悉了它們,Unix的哲學也值得一讀,Unix的思想和經驗教訓可以遷移到大規模、異構的分散式資料系統中。
|
||||
|
||||
|
||||
|
||||
## 使用Unix工具的批處理
|
||||
|
||||
我們從一個簡單的例子開始。假設你有一臺Web伺服器,每次處理請求時都會在日誌檔案中附加一行。例如,使用nginx預設的訪問日誌格式,日誌的一行可能如下所示:
|
||||
@ -58,7 +57,6 @@ AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36"
|
||||
日誌的這一行表明在2015年2月27日17:55:11 UTC,伺服器從客戶端IP地址`216.58.210.78`接收到對檔案`/css/typography.css`的請求。使用者沒有被認證,所以`$remote_user`被設定為連字元(`-` )。響應狀態是200(即請求成功),響應的大小是3377位元組。網頁瀏覽器是Chrome 40,URL `http://martin.kleppmann.com/` 的頁面中的引用導致該檔案被載入。
|
||||
|
||||
|
||||
|
||||
### 簡單日誌分析
|
||||
|
||||
很多工具可以從這些日誌檔案生成關於網站流量的漂亮的報告,但為了練手,讓我們使用基本的Unix功能建立自己的工具。 例如,假設你想在你的網站上找到五個最受歡迎的網頁。 則可以在Unix shell中這樣做:[^i]
|
||||
@ -129,7 +127,6 @@ Ruby指令碼在記憶體中儲存了一個URL的雜湊表,將每個URL對映
|
||||
GNU Coreutils(Linux)中的`sort `程式透過溢位至磁碟的方式來自動應對大於記憶體的資料集,並能同時使用多個CPU核進行並行排序【9】。這意味著我們之前看到的簡單的Unix命令鏈很容易伸縮至大資料集,且不會耗盡記憶體。瓶頸可能是從磁碟讀取輸入檔案的速度。
|
||||
|
||||
|
||||
|
||||
### Unix哲學
|
||||
|
||||
我們可以非常容易地使用前一個例子中的一系列命令來分析日誌檔案,這並非巧合:事實上,這實際上是Unix的關鍵設計思想之一,而且它直至今天也仍然令人訝異地重要。讓我們更深入地研究一下,以便從Unix中借鑑一些想法【10】。
|
||||
@ -168,7 +165,6 @@ ASCII文字的統一介面大多數時候都能工作,但它不是很優雅:
|
||||
[^譯註i]: **巴爾幹化(Balkanization)** 是一個常帶有貶義的地緣政治學術語,其定義為:一個國家或政區分裂成多個互相敵對的國家或政區的過程。
|
||||
|
||||
|
||||
|
||||
#### 邏輯與佈線相分離
|
||||
|
||||
Unix工具的另一個特點是使用標準輸入(`stdin`)和標準輸出(`stdout`)。如果你執行一個程式,而不指定任何其他的東西,標準輸入來自鍵盤,標準輸出指向螢幕。但是,你也可以從檔案輸入和/或將輸出重定向到檔案。管道允許你將一個程序的標準輸出附加到另一個程序的標準輸入(有個小記憶體緩衝區,而不需要將整個中間資料流寫入磁碟)。
|
||||
@ -182,7 +178,6 @@ Unix工具的另一個特點是使用標準輸入(`stdin`)和標準輸出(
|
||||
[^iii]: 除了使用一個單獨的工具,如`netcat`或`curl`。 Unix起初試圖將所有東西都表示為檔案,但是BSD套接字API偏離了這個慣例【17】。研究用作業系統Plan 9和Inferno在使用檔案方面更加一致:它們將TCP連線表示為`/net/tcp`中的檔案【18】。
|
||||
|
||||
|
||||
|
||||
#### 透明度和實驗
|
||||
|
||||
使Unix工具如此成功的部分原因是,它們使檢視正在發生的事情變得非常容易:
|
||||
@ -196,7 +191,6 @@ Unix工具的另一個特點是使用標準輸入(`stdin`)和標準輸出(
|
||||
然而,Unix工具的最大侷限在於它們只能在一臺機器上執行 —— 而Hadoop這樣的工具即應運而生。
|
||||
|
||||
|
||||
|
||||
## MapReduce和分散式檔案系統
|
||||
|
||||
MapReduce有點像Unix工具,但分佈在數千臺機器上。像Unix工具一樣,它相當簡單粗暴,但令人驚異地管用。一個MapReduce作業可以和一個Unix程序相類比:它接受一個或多個輸入,併產生一個或多個輸出。
|
||||
@ -360,7 +354,6 @@ Hive的偏斜連線最佳化採取了另一種方法。它需要在表格元資
|
||||
當按照熱鍵進行分組並聚合時,可以將分組分兩個階段進行。第一個MapReduce階段將記錄傳送到隨機Reducer,以便每個Reducer只對熱鍵的子集執行分組,為每個鍵輸出一個更緊湊的中間聚合結果。然後第二個MapReduce作業將所有來自第一階段Reducer的中間聚合結果合併為每個鍵一個值。
|
||||
|
||||
|
||||
|
||||
### Map側連線
|
||||
|
||||
上一節描述的連線演算法在Reducer中執行實際的連線邏輯,因此被稱為Reduce側連線。Mapper扮演著預處理輸入資料的角色:從每個輸入記錄中提取鍵值,將鍵值對分配給Reducer分割槽,並按鍵排序。
|
||||
@ -408,7 +401,6 @@ Reduce側方法的優點是不需要對輸入資料做任何假設:無論其
|
||||
在Hadoop生態系統中,這種關於資料集分割槽的元資料通常在HCatalog和Hive Metastore中維護【37】。
|
||||
|
||||
|
||||
|
||||
### 批處理工作流的輸出
|
||||
|
||||
我們已經說了很多用於實現MapReduce工作流的演算法,但卻忽略了一個重要的問題:這些處理完成之後的最終結果是什麼?我們最開始為什麼要跑這些作業?
|
||||
@ -524,7 +516,6 @@ MapReduce方式更適用於較大的作業:要處理如此之多的資料並
|
||||
在開源的叢集排程器中,搶佔的使用較少。 YARN的CapacityScheduler支援搶佔,以平衡不同佇列的資源分配【58】,但在編寫本文時,YARN,Mesos或Kubernetes不支援通用的優先順序搶佔【60】。在任務不經常被終止的環境中,MapReduce的這一設計決策就沒有多少意義了。在下一節中,我們將研究一些與MapReduce設計決策相異的替代方案。
|
||||
|
||||
|
||||
|
||||
## MapReduce之後
|
||||
|
||||
雖然MapReduce在2000年代後期變得非常流行,並受到大量的炒作,但它只是分散式系統的許多可能的程式設計模型之一。對於不同的資料量,資料結構和處理型別,其他工具可能更適合表示計算。
|
||||
@ -650,7 +641,6 @@ Spark,Flink和Tez避免將中間狀態寫入HDFS,因此它們採取了不同
|
||||
出於這個原因,如果你的圖可以放入一臺計算機的記憶體中,那麼單機(甚至可能是單執行緒)演算法很可能會超越分散式批處理【73,74】。圖比記憶體大也沒關係,只要能放入單臺計算機的磁碟,使用GraphChi等框架進行單機處理是就一個可行的選擇【75】。如果圖太大,不適合單機處理,那麼像Pregel這樣的分散式方法是不可避免的。高效的並行圖演算法是一個進行中的研究領域【76】。
|
||||
|
||||
|
||||
|
||||
### 高階API和語言
|
||||
|
||||
自MapReduce開始流行的這幾年以來,分散式批處理的執行引擎已經很成熟了。到目前為止,基礎設施已經足夠強大,能夠儲存和處理超過10,000臺機器叢集上的數PB的資料。由於在這種規模下物理執行批處理的問題已經被認為或多或少解決了,所以關注點已經轉向其他領域:改進程式設計模型,提高處理效率,擴大這些技術可以解決的問題集。
|
||||
@ -688,7 +678,6 @@ Spark,Flink和Tez避免將中間狀態寫入HDFS,因此它們採取了不同
|
||||
批處理引擎正被用於分散式執行日益廣泛的各領域演算法。隨著批處理系統獲得各種內建功能以及高階宣告式運算元,且隨著MPP資料庫變得更加靈活和易於程式設計,兩者開始看起來相似了:最終,它們都只是儲存和處理資料的系統。
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
在本章中,我們探索了批處理的主題。我們首先看到了諸如awk、grep和sort之類的Unix工具,然後我們看到了這些工具的設計理念是如何應用到MapReduce和更近的資料流引擎中的。一些設計原則包括:輸入是不可變的,輸出是為了作為另一個(仍未知的)程式的輸入,而複雜的問題是透過編寫“做好一件事”的小工具來解決的。
|
||||
@ -708,7 +697,6 @@ Spark,Flink和Tez避免將中間狀態寫入HDFS,因此它們採取了不同
|
||||
MapReduce經常寫入磁碟,這使得從單個失敗的任務恢復很輕鬆,無需重新啟動整個作業,但在無故障的情況下減慢了執行速度。資料流引擎更多地將中間狀態儲存在記憶體中,更少地物化中間狀態,這意味著如果節點發生故障,則需要重算更多的資料。確定性運算元減少了需要重算的資料量。
|
||||
|
||||
|
||||
|
||||
我們討論了幾種MapReduce的連線演算法,其中大多數也在MPP資料庫和資料流引擎內部使用。它們也很好地演示了分割槽演算法是如何工作的:
|
||||
|
||||
***排序合併連線***
|
||||
@ -732,9 +720,6 @@ MapReduce經常寫入磁碟,這使得從單個失敗的任務恢復很輕鬆
|
||||
在下一章中,我們將轉向流處理,其中的輸入是**無界的(unbounded)** —— 也就是說,你還有活兒要幹,然而它的輸入是永無止境的資料流。在這種情況下,作業永無完成之日。因為在任何時候都可能有更多的工作湧入。我們將看到,在某些方面上,流處理和批處理是相似的。但是關於無盡資料流的假設也對我們構建系統的方式產生了很多改變。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Jeffrey Dean and Sanjay Ghemawat: “[MapReduce: Simplified Data Processing on Large Clusters](http://research.google.com/archive/mapreduce.html),” at *6th USENIX Symposium on Operating System Design and Implementation* (OSDI), December 2004.
|
||||
|
@ -202,7 +202,6 @@ Apache Kafka 【17,18】,Amazon Kinesis Streams 【19】和Twitter的Distribut
|
||||
這一方面使得基於日誌的訊息傳遞更像上一章的批處理,其中衍生資料透過可重複的轉換過程與輸入資料顯式分離。它允許進行更多的實驗,更容易從錯誤和漏洞中恢復,使其成為在組織內整合資料流的良好工具【24】。
|
||||
|
||||
|
||||
|
||||
## 資料庫與流
|
||||
|
||||
我們已經在訊息代理和資料庫之間進行了一些比較。儘管傳統上它們被視為單獨的工具類別,但是我們看到基於日誌的訊息代理已經成功地從資料庫中獲取靈感並將其應用於訊息傳遞。我們也可以反過來:從訊息傳遞和流中獲取靈感,並將它們應用於資料庫。
|
||||
@ -405,7 +404,6 @@ $$
|
||||
真正刪除資料是非常非常困難的【64】,因為副本可能存在於很多地方:例如,儲存引擎,檔案系統和SSD通常會向一個新位置寫入,而不是原地覆蓋舊資料【52】,而備份通常是特意做成不可變的,防止意外刪除或損壞。刪除操作更多的是指“使取回資料更困難”,而不是指“使取回資料不可能”。無論如何,有時你必須得嘗試,正如我們在“[立法與自律](ch12.md#立法與自律)”中所看到的。
|
||||
|
||||
|
||||
|
||||
## 流處理
|
||||
|
||||
到目前為止,本章中我們已經討論了流的來源(使用者活動事件,感測器和寫入資料庫),我們討論了流如何傳輸(直接透過訊息傳送,透過訊息代理,透過事件日誌)。
|
||||
@ -680,7 +678,6 @@ Storm的Trident基於類似的想法來處理狀態【78】。依賴冪等性意
|
||||
然而,所有這些權衡取決於底層基礎架構的效能特徵:在某些系統中,網路延遲可能低於磁碟訪問延遲,網路頻寬也可能與磁碟頻寬相當。沒有針對所有情況的普適理想權衡,隨著儲存和網路技術的發展,本地狀態與遠端狀態的優點也可能會互換。
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
在本章中,我們討論了事件流,它們所服務的目的,以及如何處理它們。在某些方面,流處理非常類似於在[第十章](ch10.md) 中討論的批處理,不過是在無限的(永無止境的)流而不是固定大小的輸入上持續進行。從這個角度來看,訊息代理和事件日誌可以視作檔案系統的流式等價物。
|
||||
@ -822,8 +819,6 @@ Storm的Trident基於類似的想法來處理狀態【78】。依賴冪等性意
|
||||
1. Adam Warski: “[Kafka Streams – How Does It Fit the Stream Processing Landscape?](https://softwaremill.com/kafka-streams-how-does-it-fit-stream-landscape/),” *softwaremill.com*, June 1, 2016.
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
@ -865,7 +865,6 @@ ACID意義下的一致性(請參閱“[一致性](ch7.md#一致性)”)基
|
||||
我們究竟能做到哪一步,是一個開放的問題。首先,我們不應該永久保留資料,而是一旦不再需要就立即清除資料【111,112】。清除資料與不變性的想法背道而馳(請參閱“[不變性的侷限性](ch11.md#不變性的侷限性)”),但這是可以解決的問題。我所看到的一種很有前景的方法是透過加密協議來實施訪問控制,而不僅僅是透過策略【113,114】。總的來說,文化與態度的改變是必要的。
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
在本章中,我們討論了設計資料系統的新方式,而且也包括了我的個人觀點,以及對未來的猜測。我們從這樣一種觀察開始:沒有單種工具能高效服務所有可能的用例,因此應用必須組合使用幾種不同的軟體才能實現其目標。我們討論瞭如何使用批處理與事件流來解決這一**資料整合(data integration)** 問題,以便讓資料變更在不同系統之間流動。
|
||||
@ -887,7 +886,6 @@ ACID意義下的一致性(請參閱“[一致性](ch7.md#一致性)”)基
|
||||
由於軟體和資料對世界產生了如此巨大的影響,我們工程師們必須牢記,我們有責任為我們想要的那種世界而努力:一個尊重人們,尊重人性的世界。我希望我們能夠一起為實現這一目標而努力。
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Rachid Belaid: “[Postgres Full-Text Search is Good Enough!](http://rachbelaid.com/postgres-full-text-search-is-good-enough/),” *rachbelaid.com*, July 13, 2015.
|
||||
@ -1006,8 +1004,6 @@ ACID意義下的一致性(請參閱“[一致性](ch7.md#一致性)”)基
|
||||
1. Phillip Rogaway: “[The Moral Character of Cryptographic Work](http://web.cs.ucdavis.edu/~rogaway/papers/moral-fn.pdf),” Cryptology ePrint 2015/1162, December 2015.
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
37
zh-tw/ch2.md
37
zh-tw/ch2.md
@ -29,7 +29,6 @@
|
||||
在本章中,我們將研究一系列用於資料儲存和查詢的通用資料模型(前面列表中的第2點)。特別地,我們將比較關係模型,文件模型和少量基於圖形的資料模型。我們還將檢視各種查詢語言並比較它們的用例。在[第三章](ch3.md)中,我們將討論儲存引擎是如何工作的。也就是說,這些資料模型實際上是如何實現的(列表中的第3點)。
|
||||
|
||||
|
||||
|
||||
## 關係模型與文件模型
|
||||
|
||||
現在最著名的資料模型可能是SQL。它基於Edgar Codd在1970年提出的關係模型【1】:資料被組織成**關係**(SQL中稱作**表**),其中每個關係是**元組**(SQL中稱作**行**)的無序集合。
|
||||
@ -162,6 +161,7 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區
|
||||
***推薦***
|
||||
|
||||
假設你想新增一個新的功能:一個使用者可以為另一個使用者寫一個推薦。在使用者的簡歷上顯示推薦,並附上推薦使用者的姓名和照片。如果推薦人更新他們的照片,那他們寫的任何推薦都需要顯示新的照片。因此,推薦應該擁有作者個人簡介的引用。
|
||||
|
||||
![](../img/fig2-3.png)
|
||||
|
||||
**圖2-3 公司名不僅是字串,還是一個指向公司實體的連結(LinkedIn截圖)**
|
||||
@ -248,8 +248,8 @@ CODASYL中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資
|
||||
|
||||
```go
|
||||
if (user && user.name && !user.first_name) {
|
||||
// Documents written before Dec 8, 2013 don't have first_name
|
||||
user.first_name = user.name.split(" ")[0];
|
||||
// Documents written before Dec 8, 2013 don't have first_name
|
||||
user.first_name = user.name.split(" ")[0];
|
||||
}
|
||||
```
|
||||
|
||||
@ -297,7 +297,6 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL
|
||||
[^v]: Codd對關係模型【1】的原始描述實際上允許在關係模式中與JSON文件非常相似。他稱之為**非簡單域(nonsimple domains)**。這個想法是,一行中的值不一定是一個像數字或字串一樣的原始資料型別,也可以是一個巢狀的關係(表),因此可以把一個任意巢狀的樹結構作為一個值,這很像30年後新增到SQL中的JSON或XML支援。
|
||||
|
||||
|
||||
|
||||
## 資料查詢語言
|
||||
|
||||
當引入關係模型時,關係模型包含了一種查詢資料的新方法:SQL是一種 **宣告式** 查詢語言,而IMS和CODASYL使用 **命令式** 程式碼來查詢資料庫。那是什麼意思?
|
||||
@ -370,7 +369,7 @@ SQL示例不確保任何特定的順序,因此不在意順序是否改變。
|
||||
|
||||
```css
|
||||
li.selected > p {
|
||||
background-color: blue;
|
||||
background-color: blue;
|
||||
}
|
||||
```
|
||||
|
||||
@ -429,8 +428,8 @@ MapReduce既不是一個宣告式的查詢語言,也不是一個完全命令
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
date_trunc('month', observation_timestamp) AS observation_month,
|
||||
sum(num_animals) AS total_animals
|
||||
date_trunc('month', observation_timestamp) AS observation_month,
|
||||
sum(num_animals) AS total_animals
|
||||
FROM observations
|
||||
WHERE family = 'Sharks'
|
||||
GROUP BY observation_month;
|
||||
@ -508,7 +507,6 @@ db.observations.aggregate([
|
||||
聚合管道語言與SQL的子集具有類似表現力,但是它使用基於JSON的語法而不是SQL的英語句子式語法; 這種差異也許是口味問題。這個故事的寓意是NoSQL系統可能會發現自己意外地重新發明了SQL,儘管帶著偽裝。
|
||||
|
||||
|
||||
|
||||
## 圖資料模型
|
||||
|
||||
如我們之前所見,多對多關係是不同資料模型之間具有區別性的重要特徵。如果你的應用程式大多數的關係是一對多關係(樹狀結構化資料),或者大多數記錄之間不存在關係,那麼使用文件模型是合適的。
|
||||
@ -600,12 +598,12 @@ Cypher是屬性圖的宣告式查詢語言,為Neo4j圖形資料庫而發明【
|
||||
|
||||
```cypher
|
||||
CREATE
|
||||
(NAmerica:Location {name:'North America', type:'continent'}),
|
||||
(USA:Location {name:'United States', type:'country' }),
|
||||
(Idaho:Location {name:'Idaho', type:'state' }),
|
||||
(Lucy:Person {name:'Lucy' }),
|
||||
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
|
||||
(Lucy) -[:BORN_IN]-> (Idaho)
|
||||
(NAmerica:Location {name:'North America', type:'continent'}),
|
||||
(USA:Location {name:'United States', type:'country' }),
|
||||
(Idaho:Location {name:'Idaho', type:'state' }),
|
||||
(Lucy:Person {name:'Lucy' }),
|
||||
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
|
||||
(Lucy) -[:BORN_IN]-> (Idaho)
|
||||
```
|
||||
|
||||
當[圖2-5](../img/fig2-5.png)的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下麵條件的所有頂點,並且返回這些頂點的`name`屬性:該頂點擁有一條連到美國任一位置的`BORN_IN`邊,和一條連到歐洲的任一位置的`LIVING_IN`邊。
|
||||
@ -616,8 +614,8 @@ CREATE
|
||||
|
||||
```cypher
|
||||
MATCH
|
||||
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
|
||||
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
|
||||
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
|
||||
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
|
||||
RETURN person.name
|
||||
```
|
||||
|
||||
@ -872,7 +870,7 @@ born_in(lucy, idaho).
|
||||
within_recursive(Location, Name) :- name(Location, Name). /* Rule 1 */
|
||||
|
||||
within_recursive(Location, Name) :- within(Location, Via), /* Rule 2 */
|
||||
within_recursive(Via, Name).
|
||||
within_recursive(Via, Name).
|
||||
|
||||
migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */
|
||||
born_in(Person, BornLoc),
|
||||
@ -881,7 +879,6 @@ migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */
|
||||
within_recursive(LivingLoc, LivingIn).
|
||||
|
||||
?- migrated(Who, 'United States', 'Europe'). /* Who = 'Lucy'. */
|
||||
|
||||
```
|
||||
|
||||
Cypher和SPARQL使用SELECT立即跳轉,但是Datalog一次只進行一小步。我們定義**規則**,以將新謂語告訴資料庫:在這裡,我們定義了兩個新的謂語,`within_recursive`和`migrated`。這些謂語不是儲存在資料庫中的三元組中,而是它們是從資料或其他規則派生而來的。規則可以引用其他規則,就像函式可以呼叫其他函式或者遞迴地呼叫自己一樣。像這樣,複雜的查詢可以一次構建其中的一小塊。
|
||||
@ -907,7 +904,6 @@ Cypher和SPARQL使用SELECT立即跳轉,但是Datalog一次只進行一小步
|
||||
相對於本章討論的其他查詢語言,我們需要採取不同的思維方式來思考Datalog方法,但這是一種非常強大的方法,因為規則可以在不同的查詢中進行組合和重用。雖然對於簡單的一次性查詢,顯得不太方便,但是它可以更好地處理資料很複雜的情況。
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
資料模型是一個巨大的課題,在本章中,我們快速瀏覽了各種不同的模型。我們沒有足夠的空間來詳細介紹每個模型的細節,但是希望這個概述足以激起你的興趣,以更多地瞭解最適合你的應用需求的模型。
|
||||
@ -932,9 +928,6 @@ Cypher和SPARQL使用SELECT立即跳轉,但是Datalog一次只進行一小步
|
||||
讓我們暫時將其放在一邊。在[下一章](ch3.md)中,我們將討論在**實現**本章描述的資料模型時會遇到的一些權衡。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Edgar F. Codd: “[A Relational Model of Data for Large Shared Data Banks](https://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf),” *Communications of the ACM*, volume 13, number 6, pages 377–387, June 1970. [doi:10.1145/362384.362685](http://dx.doi.org/10.1145/362384.362685)
|
||||
|
68
zh-tw/ch3.md
68
zh-tw/ch3.md
@ -26,14 +26,14 @@
|
||||
世界上最簡單的資料庫可以用兩個Bash函式實現:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
db_set () {
|
||||
echo "$1,$2" >> database
|
||||
}
|
||||
#!/bin/bash
|
||||
db_set () {
|
||||
echo "$1,$2" >> database
|
||||
}
|
||||
|
||||
db_get () {
|
||||
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
|
||||
}
|
||||
db_get () {
|
||||
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
|
||||
}
|
||||
```
|
||||
|
||||
這兩個函式實現了鍵值儲存的功能。執行 `db_set key value` 會將 **鍵(key)** 和**值(value)** 儲存在資料庫中。鍵和值(幾乎)可以是你喜歡的任何東西,例如,值可以是JSON文件。然後呼叫 `db_get key` 會查詢與該鍵關聯的最新值並將其返回。
|
||||
@ -41,26 +41,26 @@
|
||||
麻雀雖小,五臟俱全:
|
||||
|
||||
```bash
|
||||
$ db_set 123456 '{"name":"London","attractions":["Big Ben","London Eye"]}'
|
||||
$ db_set 123456 '{"name":"London","attractions":["Big Ben","London Eye"]}'
|
||||
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}'
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}'
|
||||
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
```
|
||||
|
||||
底層的儲存格式非常簡單:一個文字檔案,每行包含一條逗號分隔的鍵值對(忽略轉義問題的話,大致與CSV檔案類似)。每次對 `db_set` 的呼叫都會向檔案末尾追加記錄,所以更新鍵的時候舊版本的值不會被覆蓋 —— 因而查詢最新值的時候,需要找到檔案中鍵最後一次出現的位置(因此 `db_get` 中使用了 `tail -n 1 ` 。)
|
||||
|
||||
```bash
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'
|
||||
$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'
|
||||
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
$ db_get 42
|
||||
{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
|
||||
$ cat database
|
||||
123456,{"name":"London","attractions":["Big Ben","London Eye"]}
|
||||
42,{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
42,{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
$ cat database
|
||||
123456,{"name":"London","attractions":["Big Ben","London Eye"]}
|
||||
42,{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
|
||||
42,{"name":"San Francisco","attractions":["Exploratorium"]}
|
||||
```
|
||||
|
||||
`db_set` 函式對於極其簡單的場景其實有非常好的效能,因為在檔案尾部追加寫入通常是非常高效的。與`db_set`做的事情類似,許多資料庫在內部使用了**日誌(log)**,也就是一個 **僅追加(append-only)** 的資料檔案。真正的資料庫有更多的問題需要處理(如併發控制,回收硬碟空間以避免日誌無限增長,處理錯誤與部分寫入的記錄),但基本原理是一樣的。日誌極其有用,我們還將在本書的其它部分重複見到它好幾次。
|
||||
@ -323,8 +323,8 @@ B樹在資料庫架構中是非常根深蒂固的,為許多工作負載都提
|
||||
**多維索引(multi-dimensional index)** 是一種查詢多個列的更一般的方法,這對於地理空間資料尤為重要。例如,餐廳搜尋網站可能有一個數據庫,其中包含每個餐廳的經度和緯度。當用戶在地圖上檢視餐館時,網站需要搜尋使用者正在檢視的矩形地圖區域內的所有餐館。這需要一個二維範圍查詢,如下所示:
|
||||
|
||||
```sql
|
||||
SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
|
||||
AND longitude > -0.1162 AND longitude < -0.1004;
|
||||
SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
|
||||
AND longitude > -0.1162 AND longitude < -0.1004;
|
||||
```
|
||||
|
||||
一個標準的B樹或者LSM樹索引不能夠高效地處理這種查詢:它可以返回一個緯度範圍內的所有餐館(但經度可能是任意值),或者返回在同一個經度範圍內的所有餐館(但緯度可能是北極和南極之間的任意地方),但不能同時滿足兩個條件。
|
||||
@ -454,18 +454,18 @@ Teradata、Vertica、SAP HANA和ParAccel等資料倉庫供應商通常使用昂
|
||||
**例3-1 分析人們是否更傾向於在一週的某一天購買新鮮水果或糖果**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
dim_date.weekday,
|
||||
dim_product.category,
|
||||
SUM(fact_sales.quantity) AS quantity_sold
|
||||
FROM fact_sales
|
||||
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
|
||||
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
|
||||
WHERE
|
||||
dim_date.year = 2013 AND
|
||||
dim_product.category IN ('Fresh fruit', 'Candy')
|
||||
GROUP BY
|
||||
dim_date.weekday, dim_product.category;
|
||||
SELECT
|
||||
dim_date.weekday,
|
||||
dim_product.category,
|
||||
SUM(fact_sales.quantity) AS quantity_sold
|
||||
FROM fact_sales
|
||||
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
|
||||
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
|
||||
WHERE
|
||||
dim_date.year = 2013 AND
|
||||
dim_product.category IN ('Fresh fruit', 'Candy')
|
||||
GROUP BY
|
||||
dim_date.weekday, dim_product.category;
|
||||
```
|
||||
|
||||
我們如何有效地執行這個查詢?
|
||||
@ -502,13 +502,13 @@ Teradata、Vertica、SAP HANA和ParAccel等資料倉庫供應商通常使用昂
|
||||
這些點陣圖索引非常適合資料倉庫中常見的各種查詢。例如:
|
||||
|
||||
```sql
|
||||
WHERE product_sk IN(30,68,69)
|
||||
WHERE product_sk IN(30,68,69)
|
||||
```
|
||||
|
||||
載入`product_sk = 30`、`product_sk = 68`和`product_sk = 69`這三個點陣圖,並計算三個點陣圖的按位或(OR),這可以非常有效地完成。
|
||||
|
||||
```sql
|
||||
WHERE product_sk = 31 AND store_sk = 3
|
||||
WHERE product_sk = 31 AND store_sk = 3
|
||||
```
|
||||
|
||||
載入`product_sk = 31`和`store_sk = 3`的點陣圖,並計算按位與(AND)。這是因為列按照相同的順序包含行,因此一列的點陣圖中的第k位和另一列的點陣圖中的第k位對應相同的行。
|
||||
|
@ -213,7 +213,7 @@ record Person {
|
||||
"fields": [
|
||||
{"name": "userName", "type": "string"},
|
||||
{"name": "favoriteNumber", "type": ["null", "long"], "default": null},
|
||||
{"name": "interests", "type": {"type": "array", "items": "string"}
|
||||
{"name": "interests", "type": {"type": "array", "items": "string"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
@ -316,7 +316,6 @@ Avro為靜態型別程式語言提供了可選的程式碼生成功能,但是
|
||||
總而言之,模式進化允許與JSON資料庫提供的無模式/讀時模式相同的靈活性(請參閱“[文件模型中的模式靈活性](ch2.md#文件模型中的模式靈活性)”),同時還可以更好地保證資料和更好的工具。
|
||||
|
||||
|
||||
|
||||
## 資料流的型別
|
||||
|
||||
在本章的開始部分,我們曾經說過,無論何時你想要將某些資料傳送到不共享記憶體的另一個程序,例如,只要你想透過網路傳送資料或將其寫入檔案,就需要將它編碼為一個位元組序列。然後我們討論了做這個的各種不同的編碼。
|
||||
@ -330,7 +329,6 @@ Avro為靜態型別程式語言提供了可選的程式碼生成功能,但是
|
||||
* 透過非同步訊息傳遞(請參閱“[訊息傳遞中的資料流](#訊息傳遞中的資料流)”)
|
||||
|
||||
|
||||
|
||||
### 資料庫中的資料流
|
||||
|
||||
在資料庫中,寫入資料庫的過程對資料進行編碼,從資料庫讀取的過程對資料進行解碼。可能只有一個程序訪問資料庫,在這種情況下,讀者只是相同程序的後續版本 - 在這種情況下,你可以考慮將資料庫中的內容儲存為向未來的自我傳送訊息。
|
||||
@ -362,7 +360,6 @@ Avro為靜態型別程式語言提供了可選的程式碼生成功能,但是
|
||||
[^v]: 除了MySQL,即使並非真的必要,它也經常會重寫整個表,正如“[文件模型中的模式靈活性](ch3.md#文件模型中的模式靈活性)”中所提到的。
|
||||
|
||||
|
||||
|
||||
#### 歸檔儲存
|
||||
|
||||
也許你不時為資料庫建立一個快照,例如備份或載入到資料倉庫(請參閱“[資料倉庫](ch3.md#資料倉庫)”)。在這種情況下,即使源資料庫中的原始編碼包含來自不同時代的模式版本的混合,資料轉儲通常也將使用最新模式進行編碼。既然你不管怎樣都要複製資料,那麼你可以對這個資料複製進行一致的編碼。
|
||||
@ -372,7 +369,6 @@ Avro為靜態型別程式語言提供了可選的程式碼生成功能,但是
|
||||
在[第十章](ch10.md)中,我們將詳細討論使用檔案儲存中的資料。
|
||||
|
||||
|
||||
|
||||
### 服務中的資料流:REST與RPC
|
||||
|
||||
當你需要透過網路進行通訊的程序時,安排該通訊的方式有幾種。最常見的安排是有兩個角色:客戶端和伺服器。伺服器透過網路公開API,並且客戶端可以連線到伺服器以向該API發出請求。伺服器公開的API被稱為服務。
|
||||
@ -500,8 +496,6 @@ Actor模型是單個程序中併發的程式設計模型。邏輯被封裝在act
|
||||
* 在Erlang OTP中,對記錄模式進行更改是非常困難的(儘管系統具有許多為高可用性設計的功能)。 滾動升級是可能的,但需要仔細計劃【53】。 一個新的實驗性的`maps`資料型別(2014年在Erlang R17中引入的類似於JSON的結構)可能使得這個資料型別在未來更容易【54】。
|
||||
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
在本章中,我們研究了將資料結構轉換為網路中的位元組或磁碟上的位元組的幾種方法。我們看到了這些編碼的細節不僅影響其效率,更重要的是也影響了應用程式的體系結構和部署它們的選項。
|
||||
@ -525,7 +519,6 @@ Actor模型是單個程序中併發的程式設計模型。邏輯被封裝在act
|
||||
我們可以小心地得出這樣的結論:前向相容性和滾動升級在某種程度上是可以實現的。願你的應用程式的演變迅速、敏捷部署。
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. “[Java Object Serialization Specification](http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html),” *docs.oracle.com*, 2010.
|
||||
|
20
zh-tw/ch5.md
20
zh-tw/ch5.md
@ -182,7 +182,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
基於觸發器的複製通常比其他複製方法具有更高的開銷,並且比資料庫的內建複製更容易出錯,也有很多限制。然而由於其靈活性,仍然是很有用的。
|
||||
|
||||
|
||||
|
||||
## 複製延遲問題
|
||||
|
||||
容忍節點故障只是需要複製的一個原因。正如在[第二部分](part-ii.md)的介紹中提到的,另一個原因是可伸縮性(處理比單個機器更多的請求)和延遲(讓副本在地理位置上更接近使用者)。
|
||||
@ -231,7 +230,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
* 如果副本分佈在不同的資料中心,很難保證來自不同裝置的連線會路由到同一資料中心。 (例如,使用者的臺式計算機使用家庭寬頻連線,而移動裝置使用蜂窩資料網路,則裝置的網路路線可能完全不同)。如果你的方法需要讀主庫,可能首先需要把來自同一使用者的請求路由到同一個資料中心。
|
||||
|
||||
|
||||
|
||||
### 單調讀
|
||||
|
||||
從非同步從庫讀取第二個異常例子是,使用者可能會遇到 **時光倒流(moving backward in time)**。
|
||||
@ -247,7 +245,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
實現單調讀取的一種方式是確保每個使用者總是從同一個副本進行讀取(不同的使用者可以從不同的副本讀取)。例如,可以基於使用者ID的雜湊來選擇副本,而不是隨機選擇副本。但是,如果該副本失敗,使用者的查詢將需要重新路由到另一個副本。
|
||||
|
||||
|
||||
|
||||
### 一致字首讀
|
||||
|
||||
第三個複製延遲例子違反了因果律。 想象一下Poons先生和Cake夫人之間的以下簡短對話:
|
||||
@ -292,7 +289,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
單節點事務已經存在了很長時間。然而在走向分散式(複製和分割槽)資料庫時,許多系統放棄了事務。聲稱事務在效能和可用性上的代價太高,並斷言在可伸縮系統中最終一致性是不可避免的。這個敘述有一些道理,但過於簡單了,本書其餘部分將提出更為細緻的觀點。第七章和第九章將回到事務的話題,並討論一些替代機制。
|
||||
|
||||
|
||||
|
||||
## 多主複製
|
||||
|
||||
本章到目前為止,我們只考慮使用單個領導者的複製架構。 雖然這是一種常見的方法,但也有一些有趣的選擇。
|
||||
@ -397,7 +393,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
* 用一種可保留所有資訊的顯式資料結構來記錄衝突,並編寫解決衝突的應用程式程式碼(也許透過提示使用者的方式)。
|
||||
|
||||
|
||||
|
||||
#### 自定義衝突解決邏輯
|
||||
|
||||
作為解決衝突最合適的方法可能取決於應用程式,大多數多主複製工具允許使用應用程式程式碼編寫衝突解決邏輯。該程式碼可以在寫入或讀取時執行:
|
||||
@ -413,7 +408,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
請注意,衝突解決通常適用於單個行或文件層面,而不是整個事務【36】。因此,如果你有一個事務會原子性地進行幾次不同的寫入(請參閱[第七章](ch7.md),對於衝突解決而言,每個寫入仍需分開單獨考慮。
|
||||
|
||||
|
||||
|
||||
> #### 自動衝突解決
|
||||
>
|
||||
> 衝突解決規則可能很快變得複雜,並且自定義程式碼可能容易出錯。亞馬遜是一個經常被引用的例子,由於衝突解決處理程式令人意外的效果:一段時間以來,購物車上的衝突解決邏輯將保留新增到購物車的物品,但不包括從購物車中移除的物品。因此,顧客有時會看到物品重新出現在他們的購物車中,即使他們之前已經被移走【37】。
|
||||
@ -428,7 +422,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
>
|
||||
|
||||
|
||||
|
||||
#### 什麼是衝突?
|
||||
|
||||
有些衝突是顯而易見的。在[圖5-7](../img/fig5-7.png)的例子中,兩個寫操作併發地修改了同一條記錄中的同一個欄位,並將其設定為兩個不同的值。毫無疑問這是一個衝突。
|
||||
@ -438,7 +431,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
現在還沒有一個現成的答案,但在接下來的章節中,我們將更好地瞭解這個問題。我們將在[第七章](ch7.md)中看到更多的衝突示例,在[第十二章](ch12.md)中我們將討論用於檢測和解決複製系統中衝突的可伸縮方法。
|
||||
|
||||
|
||||
|
||||
### 多主複製拓撲
|
||||
|
||||
**複製拓撲**(replication topology)描述寫入從一個節點傳播到另一個節點的通訊路徑。如果你有兩個領導者,如[圖5-7](../img/fig5-7.png)所示,只有一個合理的拓撲結構:領導者1必須把他所有的寫到領導者2,反之亦然。當有兩個以上的領導,各種不同的拓撲是可能的。[圖5-8](../img/fig5-8.png)舉例說明了一些例子。
|
||||
@ -470,7 +462,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
如果你正在使用具有多領導者複製功能的系統,那麼應該瞭解這些問題,仔細閱讀文件,並徹底測試你的資料庫,以確保它確實提供了你認為具有的保證。
|
||||
|
||||
|
||||
|
||||
## 無主複製
|
||||
|
||||
我們在本章到目前為止所討論的複製方法 ——單主複製、多主複製——都是這樣的想法:客戶端向一個主庫傳送寫請求,而資料庫系統負責將寫入複製到其他副本。主庫決定寫入的順序,而從庫按相同順序應用主庫的寫入。
|
||||
@ -541,7 +532,6 @@ PostgreSQL和Oracle等使用這種複製方法【16】。主要缺點是日誌
|
||||
如果少於所需的w或r節點可用,則寫入或讀取將返回錯誤。 由於許多原因,節點可能不可用:因為執行操作的錯誤(由於磁碟已滿而無法寫入),因為節點關閉(崩潰,關閉電源),由於客戶端和伺服器節點之間的網路中斷,或任何其他原因。 我們只關心節點是否返回了成功的響應,而不需要區分不同型別的錯誤。
|
||||
|
||||
|
||||
|
||||
### 法定人數一致性的侷限性
|
||||
|
||||
如果你有n個副本,並且你選擇w和r,使得$w + r> n$,你通常可以期望每個鍵的讀取都能返回最近寫入的值。情況就是這樣,因為你寫入的節點集合和你讀取的節點集合必須重疊。也就是說,你讀取的節點中必須至少有一個具有最新值的節點(如[圖5-11](../img/fig5-11.png)所示)。
|
||||
@ -650,7 +640,6 @@ LWW實現了最終收斂的目標,但以**永續性**為代價:如果同一
|
||||
因此,只要有兩個操作A和B,就有三種可能性:A在B之前發生,或者B在A之前發生,或者A和B併發。我們需要的是一個演算法來告訴我們兩個操作是否是併發的。如果一個操作發生在另一個操作之前,則後面的操作應該覆蓋較早的操作,但是如果這些操作是併發的,則存在需要解決的衝突。
|
||||
|
||||
|
||||
|
||||
> #### 併發性,時間和相對性
|
||||
>
|
||||
> 如果兩個操作 **“同時”** 發生,似乎應該稱為併發——但事實上,它們在字面時間上重疊與否並不重要。由於分散式系統中的時鐘問題,現實中是很難判斷兩個事件是否**同時**發生的,這個問題我們將在[第八章](ch8.md)中詳細討論。
|
||||
@ -660,7 +649,6 @@ LWW實現了最終收斂的目標,但以**永續性**為代價:如果同一
|
||||
> 在計算機系統中,即使光速原則上允許一個操作影響另一個操作,但兩個操作也可能是**並行的**。例如,如果網路緩慢或中斷,兩個操作間可能會出現一段時間間隔,但仍然是併發的,因為網路問題阻止一個操作意識到另一個操作的存在。
|
||||
|
||||
|
||||
|
||||
#### 捕獲"此前發生"關係
|
||||
|
||||
來看一個演算法,它確定兩個操作是否為併發的,還是一個在另一個之前。為了簡單起見,我們從一個只有一個副本的資料庫開始。一旦我們已經制定了如何在單個副本上完成這項工作,我們可以將該方法推廣到具有多個副本的無領導者資料庫。
|
||||
@ -742,7 +730,6 @@ LWW實現了最終收斂的目標,但以**永續性**為代價:如果同一
|
||||
透過在副本上讀,能夠處理比單機更大的讀取量
|
||||
|
||||
|
||||
|
||||
儘管是一個簡單的目標 - 在幾臺機器上保留相同資料的副本,但複製卻是一個非常棘手的問題。它需要仔細考慮併發和所有可能出錯的事情,並處理這些故障的後果。至少,我們需要處理不可用的節點和網路中斷(這還不包括更隱蔽的故障,例如由於軟體錯誤導致的靜默資料損壞)。
|
||||
|
||||
我們討論了複製的三種主要方法:
|
||||
@ -771,22 +758,17 @@ LWW實現了最終收斂的目標,但以**永續性**為代價:如果同一
|
||||
|
||||
***單調讀***
|
||||
|
||||
使用者在看到某一個時間點的資料後,他們不應該再看到某個更早時間點的資料。
|
||||
使用者在看到某一個時間點的資料後,他們不應該再看到某個更早時間點的資料。
|
||||
|
||||
***一致字首讀***
|
||||
|
||||
使用者應該看到資料處於一種具有因果意義的狀態:例如,按正確的順序看到一個問題和對應的回答。
|
||||
|
||||
|
||||
|
||||
最後,我們討論了多領導者和無領導者複製方法所固有的併發問題:因為他們允許多個寫入併發發生,這可能會導致衝突。我們研究了一個數據庫可能使用的演算法來確定一個操作是否發生在另一個操作之前,或者它們是否同時發生。我們還談到了透過合併併發更新來解決衝突的方法。
|
||||
|
||||
在下一章中,我們將繼續研究分佈在多個機器上的資料,透過複製的同僚:將大資料集分割成分割槽。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Bruce G. Lindsay, Patricia Griffiths Selinger, C. Galtieri, et al.: “[Notes on Distributed Databases](http://domino.research.ibm.com/library/cyberdig.nsf/papers/A776EC17FC2FCE73852579F100578964/$File/RJ2571.pdf),” IBM Research, Research Report RJ2571(33471), July 1979.
|
||||
|
@ -311,7 +311,6 @@ Couchbase不會自動重新平衡,這簡化了設計。通常情況下,它
|
||||
透過雜湊進行分割槽時,通常先提前建立固定數量的分割槽,為每個節點分配多個分割槽,並在新增或刪除節點時將整個分割槽從一個節點移動到另一個節點。也可以使用動態分割槽。
|
||||
|
||||
|
||||
|
||||
兩種方法搭配使用也是可行的,例如使用複合主鍵:使用鍵的一部分來標識分割槽,而使用另一部分作為排序順序。
|
||||
|
||||
我們還討論了分割槽和次級索引之間的相互作用。次級索引也需要分割槽,有兩種方法:
|
||||
@ -324,7 +323,6 @@ Couchbase不會自動重新平衡,這簡化了設計。通常情況下,它
|
||||
按照設計,多數情況下每個分割槽是獨立執行的 — 這就是分割槽資料庫可以伸縮到多臺機器的原因。但是,需要寫入多個分割槽的操作結果可能難以預料:例如,如果寫入一個分割槽成功,但另一個分割槽失敗,會發生什麼情況?我們將在下面的章節中討論這個問題。
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
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 85–98, June 1992. [doi:10.1145/129888.129894](http://dx.doi.org/10.1145/129888.129894)
|
||||
@ -362,7 +360,6 @@ Couchbase不會自動重新平衡,這簡化了設計。通常情況下,它
|
||||
1. 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 1–104, November 2013.[doi:10.1561/1900000036](http://dx.doi.org/10.1561/1900000036)
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
@ -34,7 +34,6 @@
|
||||
本章同時適用於單機資料庫與分散式資料庫;在[第八章](ch8.md)中將重點討論僅出現在分散式系統中的特殊挑戰。
|
||||
|
||||
|
||||
|
||||
## 事務的棘手概念
|
||||
|
||||
現今,幾乎所有的關係型資料庫和一些非關係資料庫都支援**事務**。其中大多數遵循IBM System R(第一個SQL資料庫)在1975年引入的風格【1,2,3】。40年裡,儘管一些實現細節發生了變化,但總體思路大同小異:MySQL,PostgreSQL,Oracle,SQL Server等資料庫中的事務支援與System R異乎尋常地相似。
|
||||
@ -417,7 +416,7 @@ UPDATE counters SET value = value + 1 WHERE key = 'foo';
|
||||
```plsql
|
||||
BEGIN TRANSACTION;
|
||||
SELECT * FROM figures
|
||||
WHERE name = 'robot' AND game_id = 222
|
||||
WHERE name = 'robot' AND game_id = 222
|
||||
FOR UPDATE;
|
||||
|
||||
-- 檢查玩家的操作是否有效,然後更新先前SELECT返回棋子的位置。
|
||||
@ -526,7 +525,7 @@ BEGIN TRANSACTION;
|
||||
-- 檢查所有現存的與12:00~13:00重疊的預定
|
||||
SELECT COUNT(*) FROM bookings
|
||||
WHERE room_id = 123 AND
|
||||
end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';
|
||||
end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';
|
||||
|
||||
-- 如果之前的查詢返回0
|
||||
INSERT INTO bookings(room_id, start_time, end_time, user_id)
|
||||
@ -877,7 +876,6 @@ WHERE room_id = 123 AND
|
||||
本章主要是在單機資料庫的上下文中,探討了各種想法和演算法。分散式資料庫中的事務,則引入了一系列新的困難挑戰,我們將在接下來的兩章中討論。
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Donald D. Chamberlin, Morton M. Astrahan, Michael W. Blasgen, et al.: “[A History and Evaluation of System R](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.348&rep=rep1&type=pdf),” *Communications of the ACM*, volume 24, number 10, pages 632–646, October 1981. [doi:10.1145/358769.358784](http://dx.doi.org/10.1145/358769.358784)
|
||||
|
@ -89,7 +89,6 @@
|
||||
> 雖然更可靠的高階系統並不完美,但它仍然有用,因為它處理了一些棘手的低階錯誤,所以其餘的錯誤通常更容易推理和處理。我們將在“[資料庫的端到端原則](ch12.md#資料庫的端到端原則)”中進一步探討這個問題。
|
||||
|
||||
|
||||
|
||||
## 不可靠的網路
|
||||
|
||||
正如在[第二部分](part-ii.md)的介紹中所討論的那樣,我們在本書中關注的分散式系統是無共享的系統,即透過網路連線的一堆機器。網路是這些機器可以通訊的唯一途徑——我們假設每臺機器都有自己的記憶體和磁碟,一臺機器不能訪問另一臺機器的記憶體或磁碟(除了透過網路向伺服器發出請求)。
|
||||
@ -441,7 +440,6 @@ while (true) {
|
||||
這些措施不能完全阻止垃圾回收暫停,但可以有效地減少它們對應用的影響。
|
||||
|
||||
|
||||
|
||||
## 知識、真相與謊言
|
||||
|
||||
本章到目前為止,我們已經探索了分散式系統與執行在單臺計算機上的程式的不同之處:沒有共享記憶體,只有透過可變延遲的不可靠網路傳遞的訊息,系統可能遭受部分失效,不可靠的時鐘和處理暫停。
|
||||
@ -655,7 +653,6 @@ Web應用程式確實需要預期受終端使用者控制的客戶端(如Web
|
||||
本章一直在講存在的問題,給我們展現了一幅黯淡的前景。在[下一章](ch9.md)中,我們將繼續討論解決方案,並討論一些旨在解決分散式系統中所有問題的演算法。
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Mark Cavage: Just No Getting Around It: You’re Building a Distributed System](http://queue.acm.org/detail.cfm?id=2482856),” *ACM Queue*, volume 11, number 4, pages 80-89, April 2013. [doi:10.1145/2466486.2482856](http://dx.doi.org/10.1145/2466486.2482856)
|
||||
@ -757,9 +754,6 @@ Web應用程式確實需要預期受終端使用者控制的客戶端(如Web
|
||||
[^譯著1]: 原詩為:Hey I just met you. The network’s laggy. But here’s my data. So store it maybe.Hey, 應改編自《Call Me Maybe》歌詞:I just met you, And this is crazy, But here's my number, So call me, maybe?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
16
zh-tw/ch9.md
16
zh-tw/ch9.md
@ -26,7 +26,6 @@
|
||||
分散式系統領域的研究人員幾十年來一直在研究這些主題,所以有很多資料—— 我們只能介紹一些皮毛。在本書中,我們沒有空間去詳細介紹形式模型和證明的細節,所以我們將堅持非正式的直覺。如果你有興趣,參考文獻可以提供更多的深度。
|
||||
|
||||
|
||||
|
||||
## 一致性保證
|
||||
|
||||
在“[複製延遲問題](ch5.md#複製延遲問題)”中,我們看到了資料庫複製中發生的一些時序問題。如果你在同一時刻檢視兩個資料庫節點,則可能在兩個節點上看到不同的資料,因為寫請求在不同的時間到達不同的節點。無論資料庫使用何種複製方法(單主複製,多主複製或無主複製),都會出現這些不一致情況。
|
||||
@ -50,7 +49,6 @@
|
||||
* 在第三節的(“[分散式事務與共識](#分散式事務與共識)”)中將探討如何原子地提交分散式事務,這將最終引領我們走向共識問題的解決方案。
|
||||
|
||||
|
||||
|
||||
## 線性一致性
|
||||
|
||||
在**最終一致**的資料庫,如果你在同一時刻問兩個不同副本相同的問題,可能會得到兩個不同的答案。這很讓人困惑。如果資料庫可以提供只有一個副本的假象(即,只有一個數據副本),那麼事情就簡單太多了。那麼每個客戶端都會有相同的資料檢視,且不必擔心複製滯後了。
|
||||
@ -132,7 +130,6 @@
|
||||
這就是線性一致性背後的直覺。 正式的定義【6】更準確地描述了它。 透過記錄所有請求和響應的時序,並檢查它們是否可以排列成有效的順序,以測試一個系統的行為是否線性一致性是可能的(儘管在計算上是昂貴的)【11】。
|
||||
|
||||
|
||||
|
||||
> ### 線性一致性與可序列化
|
||||
>
|
||||
> **線性一致性**容易和[**可序列化**](ch7.md#可序列化)相混淆,因為兩個詞似乎都是類似“可以按順序排列”的東西。但它們是兩種完全不同的保證,區分兩者非常重要:
|
||||
@ -241,7 +238,6 @@
|
||||
總而言之,最安全的做法是:假設採用Dynamo風格無主複製的系統不能提供線性一致性。
|
||||
|
||||
|
||||
|
||||
### 線性一致性的代價
|
||||
|
||||
一些複製方法可以提供線性一致性,另一些複製方法則不能,因此深入地探討線性一致性的優缺點是很有趣的。
|
||||
@ -302,7 +298,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
能找到一個更高效的線性一致儲存實現嗎?看起來答案是否定的:Attiya和Welch 【47】證明,如果你想要線性一致性,讀寫請求的響應時間至少與網路延遲的不確定性成正比。在像大多數計算機網路一樣具有高度可變延遲的網路中(請參閱“[超時與無窮的延遲](ch8.md#超時與無窮的延遲)”),線性讀寫的響應時間不可避免地會很高。更快地線性一致演算法不存在,但更弱的一致性模型可以快得多,所以對延遲敏感的系統而言,這類權衡非常重要。在[第十二章](ch12.md)中將討論一些在不犧牲正確性的前提下,繞開線性一致性的方法。
|
||||
|
||||
|
||||
|
||||
## 順序保證
|
||||
|
||||
之前說過,線性一致暫存器的行為就好像只有單個數據副本一樣,且每個操作似乎都是在某個時間點以原子性的方式生效的。這個定義意味著操作是按照某種良好定義的順序執行的。我們將操作以看上去被執行的順序連線起來,以此說明了[圖9-4](../img/fig9-4.png)中的順序。
|
||||
@ -331,7 +326,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
如果一個系統服從因果關係所規定的順序,我們說它是**因果一致(causally consistent)** 的。例如,快照隔離提供了因果一致性:當你從資料庫中讀取到一些資料時,你一定還能夠看到其因果前驅(假設在此期間這些資料還沒有被刪除)。
|
||||
|
||||
|
||||
|
||||
#### 因果順序不是全序的
|
||||
|
||||
**全序(total order)** 允許任意兩個元素進行比較,所以如果有兩個元素,你總是可以說出哪個更大,哪個更小。例如,自然數集是全序的:給定兩個自然數,比如說5和13,那麼你可以告訴我,13大於5。
|
||||
@ -381,7 +375,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](../img/fig5-13.png)中,來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化快照隔離](ch7.md#可序列化快照隔離)”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。
|
||||
|
||||
|
||||
|
||||
### 序列號順序
|
||||
|
||||
雖然因果是一個重要的理論概念,但實際上跟蹤所有的因果關係是不切實際的。在許多應用中,客戶端在寫入內容之前會先讀取大量資料,我們無法弄清寫入因果依賴於先前全部的讀取內容,還是僅包括其中一部分。顯式跟蹤所有已讀資料意味著巨大的額外開銷。
|
||||
@ -417,7 +410,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
* 在分配區塊的情況下,某個操作可能會被賦予一個範圍在1,001到2,000內的序列號,然而一個因果上更晚的操作可能被賦予一個範圍在1到1,000之間的數字。這裡序列號與因果關係也是不一致的。
|
||||
|
||||
|
||||
|
||||
#### 蘭伯特時間戳
|
||||
|
||||
儘管剛才描述的三個序列號生成器與因果不一致,但實際上有一個簡單的方法來產生與因果關係一致的序列號。它被稱為蘭伯特時間戳,萊斯利·蘭伯特(Leslie Lamport)於1978年提出【56】,現在是分散式系統領域中被引用最多的論文之一。
|
||||
@ -540,7 +532,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
現在是時候正面處理共識問題了,我們將在本章的其餘部分進行討論。
|
||||
|
||||
|
||||
|
||||
## 分散式事務與共識
|
||||
|
||||
**共識**是分散式計算中最重要也是最基本的問題之一。從表面上看似乎很簡單:非正式地講,目標只是**讓幾個節點達成一致(get serveral nodes to agree on something)**。你也許會認為這不會太難。不幸的是,許多出故障的系統都是因為錯誤地輕信這個問題很容易解決。
|
||||
@ -573,7 +564,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
透過對2PC的學習,我們將繼續努力實現更好的一致性演算法,比如ZooKeeper(Zab)和etcd(Raft)中使用的演算法。
|
||||
|
||||
|
||||
|
||||
### 原子提交與兩階段提交
|
||||
|
||||
在[第七章](ch7.md)中我們瞭解到,事務原子性的目的是在多次寫操作中途出錯的情況下,提供一種簡單的語義。事務的結果要麼是成功提交,在這種情況下,事務的所有寫入都是持久化的;要麼是中止,在這種情況下,事務的所有寫入都被回滾(即撤消或丟棄)。
|
||||
@ -664,7 +654,6 @@ CAP定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了一
|
||||
通常,非阻塞原子提交需要一個**完美的故障檢測器(perfect failure detector)**【67,71】—— 即一個可靠的機制來判斷一個節點是否已經崩潰。在具有無限延遲的網路中,超時並不是一種可靠的故障檢測機制,因為即使沒有節點崩潰,請求也可能由於網路問題而超時。出於這個原因,2PC仍然被使用,儘管大家都清楚可能存在協調者故障的問題。
|
||||
|
||||
|
||||
|
||||
### 實踐中的分散式事務
|
||||
|
||||
分散式事務的名聲譭譽參半,尤其是那些透過兩階段提交實現的。一方面,它被視作提供了一個難以實現的重要的安全性保證;另一方面,它們因為導致運維問題,造成效能下降,做出超過能力範圍的承諾而飽受批評【81,82,83,84】。許多雲服務由於其導致的運維問題,而選擇不實現分散式事務【85,86】。
|
||||
@ -737,7 +726,6 @@ XA事務解決了保持多個參與者(資料系統)相互一致的現實的
|
||||
這些事實是否意味著我們應該放棄保持幾個系統相互一致的所有希望?不完全是 —— 還有其他的辦法,可以讓我們在沒有異構分散式事務的痛苦的情況下實現同樣的事情。我們將在[第十一章](ch11.md) 和[第十二章](ch12.md) 回到這些話題。但首先,我們應該概括一下關於**共識**的話題。
|
||||
|
||||
|
||||
|
||||
### 容錯共識
|
||||
|
||||
非正式地,共識意味著讓幾個節點就某事達成一致。例如,如果有幾個人**同時(concurrently)** 嘗試預訂飛機上的最後一個座位,或劇院中的同一個座位,或者嘗試使用相同的使用者名稱註冊一個帳戶。共識演算法可以用來確定這些**互不相容(mutually incompatible)** 的操作中,哪一個才是贏家。
|
||||
@ -894,7 +882,6 @@ ZooKeeper和它的小夥伴們可以看作是成員資格服務(membership ser
|
||||
即使它確實存在,仍然可能發生一個節點被共識錯誤地宣告死亡。但是對於一個系統來說,知道哪些節點構成了當前的成員關係是非常有用的。例如,選擇領導者可能意味著簡單地選擇當前成員中編號最小的成員,但如果不同的節點對現有的成員都有誰有不同意見,則這種方法將不起作用。
|
||||
|
||||
|
||||
|
||||
## 本章小結
|
||||
|
||||
在本章中,我們從幾個不同的角度審視了關於一致性與共識的話題。我們深入研究了線性一致性(一種流行的一致性模型):其目標是使多副本資料看起來好像只有一個副本一樣,並使其上所有操作都原子性地生效。雖然線性一致性因為簡單易懂而很吸引人 —— 它使資料庫表現的好像單執行緒程式中的一個變數一樣,但它有著速度緩慢的缺點,特別是在網路延遲很大的環境中。
|
||||
@ -948,7 +935,6 @@ ZooKeeper和它的小夥伴們可以看作是成員資格服務(membership ser
|
||||
這裡已經到了本書[第二部分](part-ii.md)的末尾,第二部介紹了複製([第五章](ch5.md)),分割槽([第六章](ch6.md)),事務([第七章](ch7.md)),分散式系統的故障模型([第八章](ch8.md))以及最後的一致性與共識([第九章](ch9.md))。現在我們已經奠定了紮實的理論基礎,我們將在[第三部分](part-iii.md)再次轉向更實際的系統,並討論如何使用異構的元件積木塊構建強大的應用。
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Peter Bailis and Ali Ghodsi: “[Eventual Consistency Today: Limitations, Extensions, and Beyond](http://queue.acm.org/detail.cfm?id=2462076),” *ACM Queue*, volume 11, number 3, pages 55-63, March 2013. [doi:10.1145/2460276.2462076](http://dx.doi.org/10.1145/2460276.2462076)
|
||||
@ -1063,8 +1049,6 @@ ZooKeeper和它的小夥伴們可以看作是成員資格服務(membership ser
|
||||
1. Kenneth P. Birman: “[A History of the Virtual Synchrony Replication Model](https://www.truststc.org/pubs/713.html),” in *Replication: Theory and Practice*, Springer LNCS volume 5959, chapter 6, pages 91–120, 2010. ISBN: 978-3-642-11293-5, [doi:10.1007/978-3-642-11294-2_6](http://dx.doi.org/10.1007/978-3-642-11294-2_6)
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
@ -9,7 +9,6 @@ Martin是一位常規會議演講者,博主和開源貢獻者。他認為,
|
||||
![](http://martin.kleppmann.com/2017/03/ddia-poster.jpg)
|
||||
|
||||
|
||||
|
||||
## 關於譯者
|
||||
|
||||
[馮若航](https://vonng.com/about)
|
||||
@ -19,7 +18,6 @@ PostgreSQL DBA @ TanTan
|
||||
Alibaba+-Finplus 架構師/全棧工程師 (2015 ~ 2017)
|
||||
|
||||
|
||||
|
||||
## 後記
|
||||
|
||||
《設計資料密集型應用》封面上的動物是**印度野豬(Sus scrofa cristatus)**,它是在印度、緬甸、尼泊爾、斯里蘭卡和泰國發現的一種野豬的亞種。與歐洲野豬不同,它們有更高的背部鬃毛,沒有體表絨毛,以及更大更直的頭骨。
|
||||
|
@ -10,7 +10,6 @@
|
||||
第二部分將專門討論在**分散式資料系統**中特有的問題。
|
||||
|
||||
|
||||
|
||||
## 目錄
|
||||
|
||||
|
||||
@ -20,8 +19,6 @@
|
||||
4. [編碼與演化](ch4.md)
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
|
@ -70,7 +70,6 @@
|
||||
在本書的[第三部分](part-iii.md)中,將討論如何將多個(可能是分散式的)資料儲存整合為一個更大的系統,以滿足複雜的應用需求。 但首先,我們來聊聊分散式的資料。
|
||||
|
||||
|
||||
|
||||
## 索引
|
||||
|
||||
5. [複製](ch5.md)
|
||||
@ -80,9 +79,6 @@
|
||||
9. [一致性與共識](ch9.md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 參考文獻
|
||||
|
||||
1. Ulrich Drepper: “[What Every Programmer Should Know About Memory](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf),” akka‐dia.org, November 21, 2007.
|
||||
|
@ -26,7 +26,6 @@
|
||||
閱讀本書後,你能很好地決定哪種技術適合哪種用途,並瞭解如何將工具組合起來,為一個良好應用架構奠定基礎。本書並不足以使你從頭開始構建自己的資料庫儲存引擎,不過幸運的是這基本上很少有必要。你將獲得對系統底層發生事情的敏銳直覺,這樣你就有能力推理它們的行為,做出優秀的設計決策,並追蹤任何可能出現的問題。
|
||||
|
||||
|
||||
|
||||
## 本書的目標讀者
|
||||
|
||||
如果你開發的應用具有用於儲存或處理資料的某種伺服器/後端系統,而且使用網路(例如,Web應用,移動應用或連線到網際網路的感測器),那麼本書就是為你準備的。
|
||||
@ -45,7 +44,6 @@
|
||||
有時在討論可伸縮的資料系統時,人們會說:“你又不在谷歌或亞馬遜,別操心可伸縮性了,直接上關係型資料庫”。這個陳述有一定的道理:為了不必要的伸縮性而設計程式,不僅會浪費不必要的精力,並且可能會把你鎖死在一個不靈活的設計中。實際上這是一種“過早最佳化”的形式。不過,選擇合適的工具確實很重要,而不同的技術各有優缺點。我們將看到,關係資料庫雖然很重要,但絕不是資料處理的終章。
|
||||
|
||||
|
||||
|
||||
## 本書涉及的領域
|
||||
|
||||
本書並不會嘗試告訴讀者如何安裝或使用特定的軟體包或API,因為已經有大量文件給出了詳細的使用說明。相反,我們會討論資料系統的基石——各種原則與利弊權衡,並探討了不同產品所做出的不同設計決策。
|
||||
@ -69,14 +67,11 @@
|
||||
3. 在[第三部分](part-iii.md)中,我們討論那些從其他資料集衍生出一些資料集的系統。衍生資料經常出現在異構系統中:當沒有單個數據庫可以把所有事情都做的很好時,應用需要整合幾種不同的資料庫,快取,索引等。在[第十章](ch10.md)中我們將從一種衍生資料的批處理方法開始,然後在此基礎上建立在[第十一章](ch11.md)中討論的流處理。最後,在[第十二章](ch12.md)中,我們將所有內容彙總,討論在將來構建可靠,可伸縮和可維護的應用程式的方法。
|
||||
|
||||
|
||||
|
||||
|
||||
## 參考文獻與延伸閱讀
|
||||
|
||||
本書中討論的大部分內容已經在其它地方以某種形式出現過了 —— 會議簡報,研究論文,部落格文章,程式碼,BUG跟蹤器,郵件列表,以及工程習慣中。本書總結了不同來源資料中最重要的想法,並在文字中包含了指向原始文獻的連結。 如果你想更深入地探索一個領域,那麼每章末尾的參考文獻都是很好的資源,其中大部分可以免費線上獲取。
|
||||
|
||||
|
||||
|
||||
## O‘Reilly Safari
|
||||
|
||||
[Safari](http://oreilly.com/safari) (formerly Safari Books Online) is a membership-based training and reference platform for enterprise, government, educators, and individuals.
|
||||
@ -86,7 +81,6 @@ Members have access to thousands of books, training videos, Learning Paths, inte
|
||||
For more information, please visit http://oreilly.com/safari.
|
||||
|
||||
|
||||
|
||||
## 致謝
|
||||
|
||||
本書融合了學術研究和工業實踐的經驗,融合並系統化了大量其他人的想法與知識。在計算領域,我們往往會被各種新鮮花樣所吸引,但我認為前人完成的工作中,有太多值得我們學習的地方了。本書有800多處引用:文章,部落格,講座,文件等,對我來說這些都是寶貴的學習資源。我非常感謝這些材料的作者分享他們的知識。
|
||||
|
Loading…
Reference in New Issue
Block a user