mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
commit
221c8708e3
28
ch5.md
28
ch5.md
@ -305,7 +305,7 @@
|
||||
|
||||
### 多主复制的应用场景
|
||||
|
||||
在单个数据中心内部使用多个主库很少是有意义的,因为好处很少超过复杂性的代价。 但在一些情况下,多活配置是也合理的。
|
||||
在单个数据中心内部使用多个主库没有太大意义,因为复杂性已经超过了能带来的好处。 但在一些情况下,多活配置是也合理的。
|
||||
|
||||
#### 运维多个数据中心
|
||||
|
||||
@ -321,15 +321,15 @@
|
||||
|
||||
***性能***
|
||||
|
||||
在单活配置中,每个写入都必须穿过互联网,进入主库所在的数据中心。这可能会增加写入时间,并可能违背了设置多个数据中心的初心。在多活配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是透明的,这意味着感觉到的性能可能会更好。
|
||||
在单主配置中,每个写入都必须穿过互联网,进入主库所在的数据中心。这可能会增加写入时间,并可能违背了设置多个数据中心的初心。在多主配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是透明的,这意味着感觉到的性能可能会更好。
|
||||
|
||||
***容忍数据中心停机***
|
||||
|
||||
在单主配置中,如果主库所在的数据中心发生故障,故障切换可以使另一个数据中心里的追随者成为领导者。在多活配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。
|
||||
在单主配置中,如果主库所在的数据中心发生故障,故障切换必须使另一个数据中心里的追随者成为领导者。在多主配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。
|
||||
|
||||
***容忍网络问题***
|
||||
|
||||
数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多活配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。
|
||||
数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多主配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。
|
||||
|
||||
有些数据库默认情况下支持多主配置,但使用外部工具实现也很常见,例如用于MySQL的Tungsten Replicator 【26】,用于PostgreSQL的BDR【27】以及用于Oracle的GoldenGate 【19】。
|
||||
|
||||
@ -353,7 +353,7 @@
|
||||
|
||||
实时协作编辑应用程序允许多个人同时编辑文档。例如,Etherpad 【30】和Google Docs 【31】允许多人同时编辑文本文档或电子表格(该算法在“[自动冲突解决](#自动冲突解决)”中简要讨论)。我们通常不会将协作式编辑视为数据库复制问题,但与前面提到的离线编辑用例有许多相似之处。当一个用户编辑文档时,所做的更改将立即应用到其本地副本(Web浏览器或客户端应用程序中的文档状态),并异步复制到服务器和编辑同一文档的任何其他用户。
|
||||
|
||||
如果要保证不会发生编辑冲突,则应用程序必须先取得文档的锁定,然后用户才能对其进行编辑。如果另一个用户想要编辑同一个文档,他们首先必须等到第一个用户提交修改并释放锁定。这种协作模式相当于在领导者上进行交易的单领导者复制。
|
||||
如果要保证不会发生编辑冲突,则应用程序必须先取得文档的锁定,然后用户才能对其进行编辑。如果另一个用户想要编辑同一个文档,他们首先必须等到第一个用户提交修改并释放锁定。这种协作模式相当于主从复制模型下在主节点上执行事务操作。
|
||||
|
||||
但是,为了加速协作,您可能希望将更改的单位设置得非常小(例如,一个按键),并避免锁定。这种方法允许多个用户同时进行编辑,但同时也带来了多领导者复制的所有挑战,包括需要解决冲突【32】。
|
||||
|
||||
@ -369,7 +369,7 @@
|
||||
|
||||
#### 同步与异步冲突检测
|
||||
|
||||
在单主数据库中,第二个写入将被阻塞,并等待第一个写入完成,或中止第二个写入事务,强制用户重试。另一方面,在多活配置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。
|
||||
在单主数据库中,第二个写入将被阻塞,并等待第一个写入完成,或中止第二个写入事务,强制用户重试。另一方面,在多主配置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。
|
||||
|
||||
原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多主复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单主程序复制。
|
||||
|
||||
@ -447,13 +447,13 @@
|
||||
|
||||
**图5-8 三个可以设置多领导者复制的示例拓扑。**
|
||||
|
||||
最普遍的拓扑是全部到全部([图5-8 (c)](img/fig5-8.png)),其中每个领导者将其写入每个其他领导。但是,也会使用更多受限制的拓扑:例如,默认情况下,MySQL仅支持**环形拓扑(circular topology)**【34】,其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。另一种流行的拓扑结构具有星形的形状[^v]。一个指定的根节点将写入转发给所有其他节点。星型拓扑可以推广到树。
|
||||
最普遍的拓扑是全部到全部([图5-8 (c)](img/fig5-8.png)),其中每个领导者将其写入每个其他领导。但是,也会使用更多受限制的拓扑:例如,默认情况下,MySQL仅支持**环形拓扑(circular topology)**【34】,其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。另一种流行的拓扑结构具有星形的形状[^v]。一个指定的根节点将写入转发给所有其他节点。星形拓扑可以推广到树。
|
||||
|
||||
[^v]: 不要与星型模式混淆(请参阅“[星型和雪花型:分析的模式](ch3.md#星型和雪花型:分析的模式)”),其中描述了数据模型的结构,而不是节点之间的通信拓扑。
|
||||
|
||||
在圆形和星形拓扑中,写入可能需要在到达所有副本之前通过多个节点。因此,节点需要转发从其他节点收到的数据更改。为了防止无限复制循环,每个节点被赋予一个唯一的标识符,并且在复制日志中,每个写入都被标记了所有已经过的节点的标识符【43】。当一个节点收到用自己的标识符标记的数据更改时,该数据更改将被忽略,因为节点知道它已经被处理过。
|
||||
在环形和星形拓扑中,写入可能需要在到达所有副本之前通过多个节点。因此,节点需要转发从其他节点收到的数据更改。为了防止无限复制循环,每个节点被赋予一个唯一的标识符,并且在复制日志中,每个写入都被标记了所有已经过的节点的标识符【43】。当一个节点收到用自己的标识符标记的数据更改时,该数据更改将被忽略,因为节点知道它已经被处理过。
|
||||
|
||||
循环和星型拓扑的问题是,如果只有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。拓扑结构可以重新配置为在发生故障的节点上工作,但在大多数部署中,这种重新配置必须手动完成。更密集连接的拓扑结构(例如全部到全部)的容错性更好,因为它允许消息沿着不同的路径传播,避免单点故障。
|
||||
环形和星形拓扑的问题是,如果只有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。拓扑结构可以重新配置为在发生故障的节点上工作,但在大多数部署中,这种重新配置必须手动完成。更密集连接的拓扑结构(例如全部到全部)的容错性更好,因为它允许消息沿着不同的路径传播,避免单点故障。
|
||||
|
||||
另一方面,全部到全部的拓扑也可能有问题。特别是,一些网络链接可能比其他网络链接更快(例如,由于网络拥塞),结果是一些复制消息可能“超过”其他复制消息,如[图5-9](img/fig5-9.png)所示。
|
||||
|
||||
@ -479,7 +479,7 @@
|
||||
|
||||
[^vi]: Dynamo不适用于Amazon以外的用户。 令人困惑的是,AWS提供了一个名为DynamoDB的托管数据库产品,它使用了完全不同的体系结构:它基于单领导者复制。
|
||||
|
||||
在一些无领导者的实现中,客户端直接将写入发送到到几个副本中,而另一些情况下,一个 **协调者(coordinator)** 节点代表客户端进行写入。但与主库数据库不同,协调者不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。
|
||||
在一些无领导者的实现中,客户端直接将写入发送到几个副本中,而另一些情况下,一个 **协调者(coordinator)** 节点代表客户端进行写入。但与主库数据库不同,协调者不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。
|
||||
|
||||
### 当节点故障时写入数据库
|
||||
|
||||
@ -513,7 +513,7 @@
|
||||
|
||||
#### 读写的法定人数
|
||||
|
||||
在[图5-10](img/fig5-10.png)的示例中,我们认为即使仅在三个副本中的两个上进行处理,写入仍然是成功的。如果三个副本中只有一个接受了写入,会怎样?我们能推多远呢?
|
||||
在[图5-10](img/fig5-10.png)的示例中,我们认为即使仅在三个副本中的两个上进行处理,写入仍然是成功的。如果三个副本中只有一个接受了写入,会怎样?以此类推,究竟多少个副本完成才可以认为写成功?
|
||||
|
||||
如果我们知道,每个成功的写操作意味着在三个副本中至少有两个出现,这意味着至多有一个副本可能是陈旧的。因此,如果我们从至少两个副本读取,我们可以确定至少有一个是最新的。如果第三个副本停机或响应速度缓慢,则读取仍可以继续返回最新值。
|
||||
|
||||
@ -573,7 +573,7 @@
|
||||
|
||||
然而,在无领导者复制的系统中,没有固定的写入顺序,这使得监控变得更加困难。而且,如果数据库只使用读修复(没有反熵过程),那么对于一个值可能会有多大的限制是没有限制的 - 如果一个值很少被读取,那么由一个陈旧副本返回的值可能是古老的。
|
||||
|
||||
已经有一些关于衡量无主复制数据库中的复制陈旧度的研究,并根据参数n,w和r来预测陈旧读取的预期百分比【48】。不幸的是,这还不是很常见的做法,但是将陈旧测量值包含在数据库的度量标准集中是一件好事。最终一致性是一种有意模糊的保证,但是从可操作性角度来说,能够量化“最终”是很重要的。
|
||||
已经有一些关于衡量无主复制数据库中的复制陈旧度的研究,并根据参数n,w和r来预测陈旧读取的预期百分比【48】。不幸的是,这还不是很常见的做法,但是将陈旧测量值包含在数据库的度量标准集中是一件好事。最终一致性是一种非常模糊的保证,但是从可操作性角度来说,能够量化“最终”是很重要的。
|
||||
|
||||
### 宽松的法定人数与提示移交
|
||||
|
||||
@ -668,7 +668,7 @@
|
||||
[图5-13](img/fig5-13.png)显示了两个客户端同时向同一购物车添加项目。 (如果这样的例子让你觉得太麻烦了,那么可以想象,两个空中交通管制员同时把飞机添加到他们正在跟踪的区域)最初,购物车是空的。在它们之间,客户端向数据库发出五次写入:
|
||||
|
||||
1. 客户端 1 将牛奶加入购物车。这是该键的第一次写入,服务器成功存储了它并为其分配版本号1,最后将值与版本号一起回送给客户端。
|
||||
2. 客户端 2 将鸡蛋加入购物车,不知道客户端 1 同时添加了牛奶(客户端 2 认为它的鸡蛋是购物车中的唯一物品)。服务器为此写入分配版本号 2,并将鸡蛋和牛奶存储为两个单独的值。然后它将这两个值**都**反回给客户端 2 ,并附上版本号 2 。
|
||||
2. 客户端 2 将鸡蛋加入购物车,不知道客户端 1 同时添加了牛奶(客户端 2 认为它的鸡蛋是购物车中的唯一物品)。服务器为此写入分配版本号 2,并将鸡蛋和牛奶存储为两个单独的值。然后它将这两个值**都**返回给客户端 2 ,并附上版本号 2 。
|
||||
3. 客户端 1 不知道客户端 2 的写入,想要将面粉加入购物车,因此认为当前的购物车内容应该是 [牛奶,面粉]。它将此值与服务器先前向客户端 1 提供的版本号 1 一起发送到服务器。服务器可以从版本号中知道[牛奶,面粉]的写入取代了[牛奶]的先前值,但与[鸡蛋]的值是**并发**的。因此,服务器将版本 3 分配给[牛奶,面粉],覆盖版本1值[牛奶],但保留版本 2 的值[蛋],并将所有的值返回给客户端 1 。
|
||||
4. 同时,客户端 2 想要加入火腿,不知道客端户 1 刚刚加了面粉。客户端 2 在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户端 2 现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器,带着之前的版本号 2 。服务器检测到新值会覆盖版本 2 [鸡蛋],但新值也会与版本 3 [牛奶,面粉]**并发**,所以剩下的两个是v3 [牛奶,面粉],和v4:[鸡蛋,牛奶,火腿]
|
||||
5. 最后,客户端 1 想要加培根。它以前在v3中从服务器接收[牛奶,面粉]和[鸡蛋],所以它合并这些,添加培根,并将最终值[牛奶,面粉,鸡蛋,培根]连同版本号v3发往服务器。这会覆盖v3[牛奶,面粉](请注意[鸡蛋]已经在最后一步被覆盖),但与v4[鸡蛋,牛奶,火腿]并发,所以服务器保留这两个并发值。
|
||||
@ -700,7 +700,7 @@
|
||||
|
||||
以购物车为例,一种合理的合并兄弟方法就是集合求并集。在[图5-14](img/fig5-14.png)中,最后的两个兄弟是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋同时出现在两个兄弟里,即使他们每个只被写过一次。合并的值可以是[牛奶,面粉,鸡蛋,培根,火腿],没有重复。
|
||||
|
||||
然而,如果你想让人们也可以从他们的手推车中**删除**东西,而不是仅仅添加东西,那么把兄弟求并集可能不会产生正确的结果:如果你合并了两个兄弟手推车,并且只在其中一个兄弟值里删掉了它,那么被删除的项目会重新出现在兄弟的并集中【37】。为了防止这个问题,一个项目在删除时不能简单地从数据库中删除;相反,系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为**墓碑(tombstone)**。 (我们之前在“[哈希索引”](ch3.md#哈希索引)中的日志压缩的上下文中看到了墓碑。)
|
||||
然而,如果你想让人们也可以从他们的购物车中**删除**东西,而不是仅仅添加东西,那么把兄弟求并集可能不会产生正确的结果:如果你合并了两个兄弟购物车,并且只在其中一个兄弟值里删掉了它,那么被删除的项目会重新出现在并集终值中【37】。为了防止这个问题,一个项目在删除时不能简单地从数据库中删除;相反,系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为**墓碑(tombstone)**。 (我们之前在“[哈希索引”](ch3.md#哈希索引)中的日志压缩的上下文中看到了墓碑。)
|
||||
|
||||
因为在应用程序代码中合并兄弟是复杂且容易出错的,所以有一些数据结构被设计出来用于自动执行这种合并,如“[自动冲突解决](#自动冲突解决)”中讨论的。例如,Riak的数据类型支持使用称为CRDT的数据结构家族【38,39,55】可以以合理的方式自动合并兄弟,包括保留删除。
|
||||
|
||||
|
5
ch6.md
5
ch6.md
@ -117,7 +117,7 @@
|
||||
|
||||
到目前为止,我们讨论的分区方案依赖于键值数据模型。如果只通过主键访问记录,我们可以从该键确定分区,并使用它来将读写请求路由到负责该键的分区。
|
||||
|
||||
如果涉及次级索引,情况会变得更加复杂(参考“[其他索引结构](ch3.md#其他索引结构)”)。辅助索引通常并不能唯一地标识记录,而是一种搜索记录中出现特定值的方式:查找用户123的所有操作,查找包含词语`hogwash`的所有文章,查找所有颜色为红色的车辆等等。
|
||||
如果涉及次级索引,情况会变得更加复杂(参考“[其他索引结构](ch3.md#其他索引结构)”)。次级索引通常并不能唯一地标识记录,而是一种搜索记录中出现特定值的方式:查找用户123的所有操作,查找包含词语`hogwash`的所有文章,查找所有颜色为红色的车辆等等。
|
||||
|
||||
次级索引是关系型数据库的基础,并且在文档数据库中也很普遍。许多键值存储(如HBase和Volde-mort)为了减少实现的复杂度而放弃了次级索引,但是一些(如Riak)已经开始添加它们,因为它们对于数据模型实在是太有用了。并且次级索引也是Solr和Elasticsearch等搜索服务器的基石。
|
||||
|
||||
@ -268,7 +268,7 @@
|
||||
|
||||
**图6-7 将请求路由到正确节点的三种不同方式。**
|
||||
|
||||
这是一个具有挑战性的问题,因为重要的是所有参与者都同意 - 否则请求将被发送到错误的节点,得不到正确的处理。 在分布式系统中有达成共识的协议,但很难正确地实现(见[第九章](ch9.md))。
|
||||
这是一个具有挑战性的问题,因为重要的是所有参与者都达成共识 - 否则请求将被发送到错误的节点,得不到正确的处理。 在分布式系统中有达成共识的协议,但很难正确地实现(见[第九章](ch9.md))。
|
||||
|
||||
许多分布式数据系统都依赖于一个独立的协调服务,比如ZooKeeper来跟踪集群元数据,如[图6-8](img/fig6-8.png)所示。 每个节点在ZooKeeper中注册自己,ZooKeeper维护分区到节点的可靠映射。 其他参与者(如路由层或分区感知客户端)可以在ZooKeeper中订阅此信息。 只要分区分配发生了改变,或者集群中添加或删除了一个节点,ZooKeeper就会通知路由层使路由信息保持最新状态。
|
||||
|
||||
@ -407,4 +407,3 @@
|
||||
| :--------------------: | :-----------------------------: | :--------------------: |
|
||||
| [第五章:复制](ch5.md) | [设计数据密集型应用](README.md) | [第七章:事务](ch7.md) |
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user