ch5 in progress

This commit is contained in:
Vonng 2018-02-08 21:31:12 +08:00
parent 9672e6f332
commit 1003f30fc7
2 changed files with 63 additions and 59 deletions

View File

@ -14,34 +14,34 @@
数据密集型应用通常由标准组件构建而成,标准组件提供了很多通用的功能:例如,许多应用程序需要:
* 存储数据,以便自己或其他应用程序之后能再次找到。(数据库 database
* 存储数据,以便自己或其他应用程序之后能再次找到。( **数据库database**
* 记住开销昂贵操作的结果,加快读取速度。(缓存 cache
* 记住开销昂贵操作的结果,加快读取速度。(**缓存cache**
* 允许用户按关键字搜索数据,或以各种方式对数据进行过滤。(搜索索引 search indexes
* 允许用户按关键字搜索数据,或以各种方式对数据进行过滤。(**搜索索引search indexes**
* 发送消息至其他进程,进行异步处理(流处理 stream processing
* 向其他进程发送消息,进行异步处理。(**流处理stream processing**
* 定期压缩积累的大批量数据(批处理 batch processing
* 定期压缩累积的大批量数据。(**批处理batch processing**
如果这些功能听上去平淡无奇,那真让人心酸……。因为这些*数据系统data system*是如此成功的抽象,我们一直用着它们,却没有想太多。绝大多数工程师不会想从零开始编写存储引擎,开发应用时,数据库已经是足够完美工具了。
如果这些功能听上去平淡无奇,那真让人心酸。因为这些**数据系统data system**是如此成功的抽象,我们一直用着它们,却没有想太多。绝大多数工程师不会想从零开始编写存储引擎,开发应用时,数据库已经是足够完美工具了。
但事实并没有这么简单。不同的应用有不同的需求,所以数据库系统也是百花齐放,有着各式各样的特性。有很多不同的手段可以实现缓存,也有好几种方法可以搞定搜索索引,诸如此类。所以开发应用时,我们仍然需要弄清楚什么样的工具和方法最适合手头的工作。而当单个工具解决不了你的问题时,你会发现组合使用这些工具还是有难度的。
但事实并没有这么简单。不同的应用有不同的需求,所以数据库系统也是百花齐放,有着各式各样的特性。有很多不同的手段可以实现缓存,也有好几种方法可以搞定搜索索引,诸如此类。所以开发应用时仍然有必要弄清楚什么样的工具和方法最适合手头的工作。而且,当单个工具解决不了你的问题时,你会发现组合使用这些工具还是有难度的。
本书将是一趟关于数据系统原理、实践与应用的旅途,并讲述了设计数据密集型应用的方法。我们将探索不同工具之间的共性与特性,以及各自的实现原理。
本章将从探索我们所要实现的基础目标开始:可靠,可扩展、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。
本章将从我们所要实现的基础目标开始:可靠,可扩展、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。
## 关于数据系统的思考
通常认为,数据库,消息队列,缓存这些,是差异显著的工具分类。虽然数据库和消息队列表面上有一些相似性—— 都会存一段时间的数据。——但它们有迥然的访问模式,这意味着迥然的性能特征和迥然的实现。
通常认为,数据库,消息队列,缓存这些,是差异显著的工具分类。虽然数据库和消息队列表面上有一些相似性—— 都会存一段时间的数据。——但它们有迥然不同的访问模式,这意味着迥异的性能特征和迥异的实现。
那么为什么要把它们混为一谈,放在一个 **数据系统data system**的总称之下呢?
@ -55,9 +55,7 @@
**图1-1 组合使用多个组件的数据系统,一种可能的架构**
当你将多个工具组合在一起提供服务时,服务的接口,或*应用程序编程接口API, Application Programming Interface*通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的,专用的数据系统。这个新的复合数据系统可能会提供特定的保证:例如,缓存在写入时作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。
当你将多个工具组合在一起提供服务时,服务的接口,或**应用程序编程接口API, Application Programming Interface**通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的,专用的数据系统。这个新的复合数据系统可能会提供特定的保证:例如,缓存在写入时会作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。
设计数据系统或服务时可能会遇到很多棘手的问题。当系统出问题时如何确保数据的正确性和完整性当部分系统退化降级时如何为客户提供始终如一的良好性能当负载增加时如何扩容应对什么样的API才是好的API
@ -87,33 +85,33 @@
人们对于一个东西是否可靠,都有一个直观的想法。对于可靠的软件,典型的期望包括:
* 应用程序表现出用户所期望的功能。
* 它可以允许用户犯错或以出乎意料的方式使用软件。
* 允许用户犯错,允许用户以出乎意料的方式使用软件。
* 在预期的负载和数据量下,性能满足应用场景的要求。
* 系统可以防止未经授权的访问和滥用。
如果所有这些在一起意味着“正确工作”,那么我们可以把可靠性粗略理解为”即使出现问题,也能继续正确工作”。
如果所有这些在一起意味着“正确工作”,那么可以把可靠性粗略理解为”即使出现问题,也能继续正确工作”。
可能出问题的东西叫做***故障fault***,能预料并应对故障的系统特性可称为***容错fault-tolerant******韧性resilient***。第一个术语可能会产生误导:它暗示可以让系统容忍所有可能的错误,实际中这是不可能的。如果整个地球(及其上的所有服务器)都被黑洞吞噬,容忍这种错误需要将网络托管到宇宙中。能不能通过这种预算就祝你好运了。所以在讲容错时,只有谈论*特定类型*的错误才有意义。
可能出问题的东西叫做**故障fault**,能预料并应对故障的系统特性可称为**容错fault-tolerant**或**韧性resilient**。第一个术语可能会产生误导:它暗示可以让系统容忍所有可能的错误,实际中这是不可能的。如果整个地球(及其上的所有服务器)都被黑洞吞噬,容忍这种错误需要将网络托管到宇宙中。能不能通过这种预算就祝你好运了。所以在讲容错时,只有谈论*特定类型*的错误才有意义。
注意***故障fault***不同于***失效failure***[【2】][2]。**故障**通常定义为系统的一部分偏离其标准,而**失效**则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制,以防**故障**导致**失效**。本书们将介绍几种用不可靠的部件构建可靠系统的技术。
注意**故障fault**不同于**失效failure**[【2】][2]。**故障**通常定义为系统的一部分偏离其标准,而**失效**则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制,以防**故障**导致**失效**。本书们将介绍几种用不可靠的部件构建可靠系统的技术。
反直觉的是,在这类容错系统中,通过故意触发来**提高**故障率是有意义的。例如在没有警告的情况下随机地杀死单个进程。许多高危漏洞实际上由糟糕的错误处理导致的[【3】][3]通过故意引发故障来确保容错机制不断运行并接受考验从而提高故障自然发生时系统能正确处理的信心。Netflix *Chaos Monkey*[【4】][]就是这种方法的一个例子。
反直觉的是,在这类容错系统中,通过故意触发来**提高**故障率是有意义的。例如在没有警告的情况下随机地杀死单个进程。许多高危漏洞实际上由糟糕的错误处理导致的[【3】][3]通过故意引发故障来确保容错机制不断运行并接受考验从而提高故障自然发生时系统能正确处理的信心。Netflix *Chaos Monkey*[【4】][4]就是这种方法的一个例子。
尽管比起**阻止错误Prevent error**,我们更倾向于容忍错误。但也有**预防胜于治疗**的情况(比如不存在治疗方法时)。安全问题就属于这种情况。例如,如果攻击者破坏了系统,并获取了敏感数据,这种事是撤销不了的。但本书主要讨论的是可以恢复的故障类型,如下所述。
尽管比起**阻止错误Prevent error**,我们通常更倾向于容忍错误。但也有**预防胜于治疗**的情况(比如不存在治疗方法时)。安全问题就属于这种情况。例如,如果攻击者破坏了系统,并获取了敏感数据,这种事是撤销不了的。但本书主要讨论的是可以恢复的故障类型,如下所述。
### 硬件故障
我们想到系统失效的原因时,硬件故障总是第一个进入脑海。硬盘崩溃,内存出错,机房断电,有人拔错网线。任何与大型数据中心打交道的人都会告诉你,当拥有很多机器时,这些事情**总是**会发生的。
想到系统失效的原因时,硬件故障总会第一个进入脑海。硬盘崩溃,内存出错,机房断电,有人拔错网线。任何与大型数据中心打交道的人都会告诉你,当拥有很多机器时,这些事情**总是**会发生的。
硬盘的平均无故障时间MTTF, mean time to failure据报道称约为10到50年[【5】][] [【6】][]。因此,在一个有一万个磁盘的存储集群上,期望上每天平均会有一个磁盘挂掉。
硬盘的**平均无故障时间MTTF, mean time to failure**据报道称约为10到50年[【5】][5][【6】][6]。因此,在一个有一万个磁盘的存储集群上,期望上每天平均会有一个磁盘挂掉。
为了减少系统的故障率第一反应通常是增加单个硬件的冗余度。磁盘可以组RAID服务器可能有双路电源和热插拔CPU数据中心可能有电池和柴油发电机作为后备电源。当某个组件挂掉时换下来并由冗余组件取而代之。这种方法并不能完全防止因为硬件问题导致的系统失效但它简单易懂通常也足以让机器不间断运行多年。
直到最近,硬件冗余对于大多数应用来说已经足够了,它使单台机器完全失效变得相当罕见。只要你能快速地把备份恢复到新机器上,故障停机时间对于大多数应用程序都不算灾难性的。少部分需要高可用的应用会采用多个冗余副本。
直到最近,硬件冗余对于大多数应用来说已经足够了,它使单台机器完全失效变得相当罕见。只要你能快速地把备份恢复到新机器上,故障停机时间对于大多数应用程序都算不上灾难性的。少部分需要高可用的应用可能会采用多个冗余硬件副本。
是,随着数据量和应用程序的计算需求的增加,越来越多的应用程序开始使用大量的机器这会相应地增加硬件故障率。此外在一些云平台如亚马逊网络服务AWS虚拟机实例在没有警告的情况下变得不可用[7],这是因为平台旨在优先考虑单机可靠性的灵活性和弹性。
但随着数据量和应用计算需求的增加,越来越多的应用开始大量使用机器,这会相应地增加硬件故障率。此外,在一些云平台(**如亚马逊网络服务AWS, Amazon Web Services**)中,虚拟机实例在没有警告的情况下变得不可用[7],这是因为平台旨在优先考虑单机可靠性的灵活性和弹性。
因此,通过优先使用软件容错技术或除了硬件冗余之外,还有一种趋向于可以容忍整个机器损失的系统。这样的系统还具有操作优势:如果需要重启机器(例如应用操作系统安全补丁),则单服务器系统需要计划的停机时间,而可以容忍机器故障的系统可以一次修补一个节点没有整个系统的停机时间(滚动升级;参见第4章
因此,通过优先使用软件容错技术或除了硬件冗余之外,还有一种趋向于可以容忍整个机器损失的系统。这样的系统还具有运维优势:如果需要重启机器(例如应用操作系统安全补丁),则单服务器系统需要计划的停机时间,而可以容忍机器故障的系统可以一次修补一个节点没有整个系统的停机时间(滚动升级;参见第4章
### 软件错误

View File

@ -30,7 +30,7 @@
存储数据库副本的每个节点称为**副本replica**。当有多个副本时就会不可避免的出现一个问题:如何确保所有数据都落在了所有的副本上?
每一次向数据库的写入都需要传播到所有副本上,否则副本包含的数据就会不一样。最常见的解决方案称为:**基于领导者的复制leader-based replication**(也称**主动/被动 active/passive** 或 **主/从 master/slave**复制),如[图5-1](img/fig5-1.png)所示。它的工作原理如下:
每一次向数据库的写入都需要传播到所有副本上,否则副本包含的数据就会不一样。最常见的解决方案称为:**基于领导者的复制leader-based replication**(也称**主动/被动 active/passive** 或 **主/从 master/slave**复制),如[图5-1](#fig5-1.png)所示。它的工作原理如下:
1. 副本之一被指定为**领导者leader**,也称为 **主master** **首要primary**。当客户端要写入数据库时,它们必须将请求发送给**领导者**,首先将新数据写入领导者的本地存储。
2. 其他副本被称为**追随者followers**,亦称为**只读副本read replicas****从slaves****次要( sencondaries****热备hot-standby**[^i]。每当领导者将新数据写入本地存储时,它也会将数据变更发送给所有的追随者,称之为**复制日志replication log**记录或**变更流change stream**。每个跟随者从领导者拉取日志,并相应更新其本地数据库副本,方法是按照领导者处理的相同顺序应用所有写入。
@ -193,7 +193,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
基于领导者的复制要求所有写入都由一个节点处理但只读查询可以由任何副本处理。所以对于读多写少Web上的常见模式的场景一个有吸引力的选择是创建很多追随者并将读取请求分散到所有的从库上去。这样能减小主库的负载并允许向最近的副本发送读请求。
在这种扩展体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制 - 如果尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。而且越多的节点越有可能会被关闭,所以完全同步的配置将是非常不可靠的。
在这种扩展体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制 - 如果尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。而且越多的节点越有可能会被关闭,所以完全同步的配置将是非常不可靠的。
不幸的是,当应用程序从异步追随者读取时,如果追随者落后,它可能会看到过时的信息。这会导致数据库中出现明显的不一致:同时对领导者和跟随者执行相同的查询,可能得到不同的结果,因为并非所有的写入都反映在跟随者中。这种不一致只是一个暂时的状态——如果停止写入数据库并等待一段时间,追随者最终会赶上并与领导者保持一致。出于这个原因,这种效应被称为**最终一致性eventually consistency**[^iii][22,23]
@ -257,7 +257,8 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
第三个复制延迟例子违反了因果律。 想象一下Poons先生和Cake夫人之间的以下简短对话
> *Mr. Poons*
> Mrs. Cake你能看到未来有多远
> Mrs. Cake你能看到多远的未来
>
> *Mrs. Cake*
> 通常约十秒钟Mr. Poons.
@ -269,7 +270,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
> 通常约十秒钟Mr. Poons.
>
> *Mr. Poons*
> Mrs. Cake你能看到未来有多远?
> Mrs. Cake你能看到多远的未来
对于观察者来说看起来好像Cake夫人在Poons先生发问前就回答了这个问题。
这种超能力让人印象深刻,但也会把人搞糊涂。[25]。
@ -294,56 +295,61 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
单节点事务已经存在了很长时间。然而在走向分布式(复制和分区)数据库时,许多系统放弃了事务。声称事务在性能和可用性上的代价太高,并断言在可扩展系统中最终一致性是不可避免的。这个叙述有一些道理,但过于简单了,本书其余部分将提出更为细致的观点。第七章和第九章将回到事务的话题,并讨论一些替代机制。
## 多主复制
本章到目前为止,我们只考虑使用单个领导的复制架构。 虽然这是一种常见的方法,但也有一些有趣的选择。
本章到目前为止,我们只考虑使用单个领导的复制架构。 虽然这是一种常见的方法,但也有一些有趣的选择。
基于领导者的复制有一个主要的缺点:只有一个领导者,所有的写入都必须通过它.如果数据库被分区见第6章每个分区都有一个领导。 不同的分区可能在不同的节点上有其领导者,但是每个分区必须有一个领导者节点。)如果由于任何原因(例如由于你和领导之间的网络中断)而无法连接到领导者, 你不能写入数据库
基于领导者的复制有一个主要的缺点:只有一个主库,而所有的写入都必须通过它。如果出于任何原因(例如和主库之间的网络连接中断)无法连接到主库, 就无法向数据库写入
基于领导者的复制模型的自然延伸是允许多个节点接受写入。 复制仍然以同样的方式发生:处理写入的每个节点都必须将该数据更改转发给所有其他节点。 我们称之为多领导配置(也称为主 - 主或主动/主动复制)。 在这种情况下,每个领导者同时扮演其他领导者的追随者。
[^iv]: 如果数据库被分区见第6章每个分区都有一个领导。 不同的分区可能在不同的节点上有其领导者,但是每个分区必须有一个领导者节点。
基于领导者的复制模型的自然延伸是允许多个节点接受写入。 复制仍然以同样的方式发生:处理写入的每个节点都必须将该数据更改转发给所有其他节点。 称之为多领导者配置(也称多主、多活复制)。 在这种情况下,每个领导者同时扮演其他领导者的追随者。
### 多主复制的应用场景
在单个数据中心内部使用多领导者设置很少有意义,因为这些好处很少超过复杂性。 但是,在某些情况下,这种配置是合理的。
在单个数据中心内部使用多个主库很少是有意义的,因为好处很少超过复杂性的代价。 但在一些情况下,多活配置是也合理的。
#### 运维多数据中心
#### 运维多数据中心
想象一下,你有一个数据库在几个不同的数据中心(也许这样你可以容忍整个数据中心的故障,或者为了更接近你的用户)有副本。 使用常规的基于领导者的复制设置,领导者必须位于其中一个数据中心,并且所有写入都必须经过该数据中心。
假如你有一个数据库,副本分散在好几个不同的数据中心(也许这样可以容忍单个数据中心的故障,或地理上更接近用户)。 使用常规的基于领导者的复制设置,主库必须位于其中一个数据中心,且所有写入都必须经过该数据中心。
在多领导配置中,您可以在每个数据中心都有领导。 图5-6显示了这个架构的外观。 在每个数据中心内,使用常规的领导者跟随者复制; 在数据中心之间,每个数据中心的负责人都会将其更改复制到其他数据中心的领导
多领导者配置中可以在每个数据中心都有主库。 [图5-6](img/fig5-6.png)展示了这个架构的样子。 在每个数据中心内使用常规的主从复制;;在数据中心之间,每个数据中心的主库都会将其更改复制到其他数据中心的主库中
![](img/fig5-6.png)
**图5-6 跨多个数据中心的多领导复制**
**图5-6 跨多个数据中心的多复制**
我们来比较一下多数据中心部署中的单引擎和多引导器配置:
我们来比较一下在运维多个数据中心时,单主和多主的适应情况。
***性能***
在单领导配置中,每个写作都必须通过互联网与领导者一起进入数据中心。这可能会增加写入时间,并可能违反了首先有多个数据中心的目的。在多领导配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是隐藏的,这意味着感知的性能可能会更好。
在单活配置中,每个写入都必须穿过互联网,进入主库所在的数据中心。这可能会增加写入时间,并可能违背了设置多个数据中心的初心。在多活配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是透明的,这意味着感觉到的性能可能会更好。
***数据中心的中断容忍***
***容忍数据中心停机***
在单引导者配置中,如果引导者的数据中心发生故障,故障转移可以促使另一个数据中心的追随者成为领导者。在多领导者配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心恢复联机时,复制将迎头赶上。
在单主配置中,如果主库所在的数据中心发生故障,故障转移可以使另一个数据中心里的追随者成为领导者。在多活配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。
***容忍网络问题***
数据中心之间的通信通常通过公共互联网,这可能不如数据中心内的本地网络可靠。单引号配置对这个数据中心链接中的问题非常敏感,因为通过这个链接进行写操作是同步的。具有异步复制功能的多领导者配置通常可以更好地承受网络问题:暂时的网络中断不会妨碍正在处理的写入。
数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多活配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。
有些数据库默认情况下支持多领导配置,但通常也使用外部工具实现例如用于MySQL的Tungsten Replicator [26]用于PostgreSQL的BDR [27]以及用于Oracle的GoldenGate [19]。
有些数据库默认情况下支持多主配置,但使用外部工具实现也很常见例如用于MySQL的Tungsten Replicator [26]用于PostgreSQL的BDR [27]以及用于Oracle的GoldenGate [19]。
尽管多领导者复制具有优势但也有一个很大的缺点在两个不同的数据中心可能会同时修改相同的数据并且必须解决这些写冲突如图5-6中的“冲突解决”所示。我们将在第171页的“处理写入冲突”中讨论这个问题。
由于多引导程序复制在许多数据库中都有所改进,所以常常存在微妙的配置缺陷,并且与其他数据库功能之间出现意外的交互。例如,自动增量键,触发器和完整性约束可能是有问题的。出于这个原因,多领导者复制往往被认为是危险的领域,应尽可能避免[28]。
尽管多主复制有这些优势但也有一个很大的缺点两个不同的数据中心可能会同时修改相同的数据写冲突是必须解决的如图5-6中“冲突解决”。本书将在“处理写入冲突”中详细讨论这个问题。
由于多主复制在许多数据库中都属于改装的功能,所以常常存在微妙的配置缺陷,且经常与其他数据库功能之间出现意外的反应。例如自增主键、触发器、完整性约束等,都可能会有麻烦。因此,多主复制往往被认为是危险的领域,应尽可能避免[28]。
#### 需要离线操作的客户端
领导者复制的另一种情况是如果您的应用程序在与Internet断开连接时需要继续工作
主复制的另一种适用场景是:应用程序在断网之后仍然需要继续工作。
例如,考虑手机,笔记本电脑和其他设备上的日历应用程序。无论您的设备目前是否具有互联网连接,您都需要能够随时查看您的会议(发出读取请求)并输入新的会议(发出写入请求)。如果在离线状态下进行任何更改,则设备下次上线时,需要与服务器和其他设备同步。
例如,考虑手机,笔记本电脑和其他设备上的日历应用。无论设备目前是否有互联网连接,你需要能随时查看你的会议(发出读取请求),输入新的会议(发出写入请求)。如果在离线状态下进行任何更改,则设备下次上线时,需要与服务器和其他设备同步。
在这种情况下,每个设备都有一个充当领导者的本地数据库(它接受写请求),并且在您所有设备上的日历副本之间存在异步多领导者复制过程(同步)。复制延迟可能是几小时甚至几天,具体取决于何时可以访问互联网。
在这种情况下,每个设备都有一个充当领导者的本地数据库(它接受写请求),并且在所有设备上的日历副本之间同步时,存在异步的多主复制过程。复制延迟可能是几小时甚至几天,具体取决于何时可以访问互联网。
从架构的角度来看,这种设置基本上与数据中心之间的多领导者复制相同,极端:每个设备都是一个“数据中心”,它们之间的网络连接是非常不可靠的。正如破碎的日历同步实现的丰富历史所表明的,多领导者复制是一件棘手的事情
从架构的角度来看,这种设置实际上与数据中心之间的多领导者复制类似,每个设备都是一个“数据中心”,而它们之间的网络连接是极度不可靠的。从历史上各类日历同步功能的破烂实现可以看出,想把多活配好是多么困难的一件事
有一些工具旨在使这种多领导者配置更容易。例如CouchDB就是为这种操作模式而设计的[29]。
@ -359,17 +365,17 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
多领导者复制的最大问题是可能发生写冲突,这意味着需要解决冲突。
例如考虑一个由两个用户同时编辑的维基页面如图5-7所示。用户1将页面的标题从A更改为B并且用户2同时将标题从A更改为C.每个用户的更改已成功应用到其本地领导。但是,当异步复制时,会发现冲突[33]。单引导数据库中不会出现此问题。
例如,考虑一个由两个用户同时编辑的维基页面,如[图5-7](img/fig5-7.png)所示。用户1将页面的标题从A更改为B并且用户2同时将标题从A更改为C。每个用户的更改已成功应用到其本地主库。但当异步复制时,会发现冲突[33]。单主数据库中不会出现此问题。
![](img/fig5-7.png)
**图5-7 由两位领导同时更新同一记录引起的写入冲突**
**图5-7 两个主库同时更新同一记录引起的写入冲突**
#### 同步与异步冲突检测
在单主数据库中,第二个写入将阻塞并等待第一个写入完成,或中止第二个写入事务,强制用户重试写入。另一方面,在多领导者设置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。
在单主数据库中,第二个写入将阻塞并等待第一个写入完成,或中止第二个写入事务,强制用户重试。另一方面,在多活配置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。
原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多领导者复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单引导程序复制。
原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多领导者复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单程序复制。
#### 避免冲突
@ -387,7 +393,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
如果每个副本只是按照它看到写入的顺序写入那么数据库最终将处于不一致的状态最终值将是在领导者1处的C和在领导者2处的B.这是不可接受的 - 每个复制方案都必须确保数据在所有副本中最终都是相同的。因此,数据库必须以一种趋同的方式解决冲突,这意味着所有副本必须在所有更改都被复制时达到相同的最终值。
实现融合冲突解决有多种途径:
实现冲突合并解决有多种途径:
* 给每个写入一个唯一的ID例如一个时间戳一个长的随机数一个UUID或者一个键和值的哈希挑选最高ID的写入作为胜利者并丢弃其他写入。如果使用时间戳这种技术被称为最后一次写入胜利LWW。虽然这种方法很流行但是很容易造成数据丢失[35]。我们将在本章末尾更详细地讨论LWW第184页的“检测并发写入”
* 为每个副本分配一个唯一的ID并让始发于较高编号副本的写入始终优先于源自较低编号副本的写入。这种方法也意味着数据丢失。
@ -429,9 +435,9 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
#### 什么是冲突?
有些冲突是显而易见的。在图5-7的例子中两个写操作并发地修改了同一记录中的同一个字段,并将其设置为两个不同的值。毫无疑问这是一个冲突。
有些冲突是显而易见的。在[图5-7](img/fig5-7.png)的例子中,两个写操作并发地修改了同一记录中的同一个字段,并将其设置为两个不同的值。毫无疑问这是一个冲突。
其他类型的冲突可能更加微妙地被发现。例如,考虑一个会议室预订系统:它跟踪哪个房间是哪个人在哪个时间预订的。这个应用程序需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。
其他类型的冲突可能更为微妙,难以发现。例如,考虑一个会议室预订系统:它记录哪个房间是哪谁在什么时间预订的。应用需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。
现在还没有一个现成的答案但在接下来的章节中我们将追溯到对这个问题有很好的理解。我们将在第7章中看到更多的冲突示例在第12章中我们将讨论用于检测和解决复制系统中冲突的可扩展方法。
@ -461,7 +467,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
在图5-9中客户端A向领导者1的表中插入一行客户端B在领导者3上更新该行。然而领导者2可以以不同的顺序接收写入它可以首先接收更新其中从它的角度来看是对数据库中不存在的行的更新并且仅在稍后接收到相应的插入其应该在更新之前
这是一个因果关系的问题类似于我们在第165页上的“一致前缀读取”中看到的更新取决于先前的插入所以我们需要确保所有节点先处理插入然后再处理更新。仅仅在每一次写入时添加一个时间戳是不够的因为时钟不可能被充分地同步以便在引导2处正确地排序这些事件见第8章
这是一个因果关系的问题类似于我们在第165页上的“一致前缀读取”中看到的更新取决于先前的插入所以我们需要确保所有节点先处理插入然后再处理更新。仅仅在每一次写入时添加一个时间戳是不够的因为时钟不可能被充分地同步以便在2处正确地排序这些事件见第8章
要正确命令这些事件,可以使用一种称为**版本向量version vectors**的技术本章稍后将讨论这种技术请参阅第174页的“检测并发写入”。然而冲突检测技术在许多多领导者复制系统中执行得不好。例如在撰写本文时PostgreSQL BDR不提供写操作的因果排序[27]而Tungsten Replicator for MySQL甚至不尝试检测冲突[34]。
@ -469,9 +475,9 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
## 无主复制
我们在本章到目前为止所讨论的复制方法 - 单引导者和多引导者复制 - 是基于客户端向一个节点(领导者)发送写请求的想法,数据库系统负责复制写入其他副本。领导决定了写入的顺序,而跟随者按相同的顺序应用领导的写入。
我们在本章到目前为止所讨论的复制方法 - 单主者和多主者复制 - 是基于客户端向一个节点(领导者)发送写请求的想法,数据库系统负责复制写入其他副本。领导决定了写入的顺序,而跟随者按相同的顺序应用领导的写入。
一些数据存储系统采用不同的方法,放弃领导者的概念,并允许任何副本直接接受来自客户端的写入。一些最早的复制数据系统是无领导的[1,44]但是在关系数据库主导时代这个想法大多被遗忘。在亚马逊将其用于其内部的Dynamo系统之后它再一次成为数据库的一种时尚架构[37] .Dynamo不适用于Amazon以外的用户。 令人困惑的是AWS提供了一个名为DynamoDB的托管数据库产品它使用了完全不同的体系结构它基于单引导程序复制。) RiakCassandra和Voldemort是由Dynamo启发的无领导复制模型的开源数据存储所以这类数据库也被称为*Dynamo风格*。
一些数据存储系统采用不同的方法,放弃领导者的概念,并允许任何副本直接接受来自客户端的写入。一些最早的复制数据系统是无领导的[1,44]但是在关系数据库主导时代这个想法大多被遗忘。在亚马逊将其用于其内部的Dynamo系统之后它再一次成为数据库的一种时尚架构[37] .Dynamo不适用于Amazon以外的用户。 令人困惑的是AWS提供了一个名为DynamoDB的托管数据库产品它使用了完全不同的体系结构它基于单程序复制。) RiakCassandra和Voldemort是由Dynamo启发的无领导复制模型的开源数据存储所以这类数据库也被称为*Dynamo风格*。
在一些无领导者的实现中,客户端直接将其写入到几个副本中,而在另一些情况下,协调器节点代表客户端进行写入。但是,与领导者数据库不同,协调员不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。
@ -527,7 +533,7 @@ PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志记录
![](img/fig5-11.png)
**图5-11 如果w + r > n读取r个副本至少有一个r副本必然包含了最近的成功写入**
**图5-11 如果$w + r > n$读取r个副本至少有一个r副本必然包含了最近的成功写入**
如果少于所需的w或r节点可用则写入或读取将返回错误。 由于许多原因,节点可能不可用:因为由于执行操作的错误(由于磁盘已满而无法写入)导致节点关闭(崩溃,关闭电源),由于客户端和服务器之间的网络中断 节点,或任何其他原因。 我们只关心节点是否返回了成功的响应,而不需要区分不同类型的错误。
@ -752,7 +758,7 @@ LWW实现了最终收敛的目标但是以持久性为代价如果同一
***无主复制***
客户端发送每个写入到几个节点,并从多个节点并行读取,以检测和纠正具有陈旧数据的节点。
每种方法都有优点和缺点。单引导复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但代价很难推理,只能提供非常弱的一致性保证。
每种方法都有优点和缺点。单复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但代价很难推理,只能提供非常弱的一致性保证。