最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

第五章:复制

来源:博客园

如果复制中的数据不会随时间而改变,那复制就很简单:将数据复制到每个节点一次就万事大吉。复制的困难之处在于处理复制数据的变更(change)

三种变更复制算法:单领导者(single leader)多领导者(multi leader)无领导者(leaderless)


(资料图片)

领导者与追随者

存储数据库副本的每个节点称为 副本(replica) 。当存在多个副本时,会不可避免的出现一个问题:如何确保所有数据都落在了所有的副本上?每一次向数据库的写入操作都需要传播到所有副本上,否则副本就会包含不一样的数据。

基于领导者的复制(leader-based replication)

  1. 副本之一被指定为 领导者(leader),也称为 主库(master|primary)。当客户端要向数据库写入时,它必须将请求发送给领导者,领导者会将新数据写入其本地存储。
  2. 其他副本被称为追随者(followers),亦称为只读副本(read replicas)从库(slaves)备库( sencondaries)热备(hot-standby)。每当领导者将新数据写入本地存储时,它也会将数据变更发送给所有的追随者,称之为复制日志(replication log)记录或变更流(change stream)
  3. 当客户想要从数据库中读取数据时,它可以向领导者或追随者查询。 但只有领导者才能接受写操作。

图 - 基于领导者(主-从)的复制

基于领导者的复制并不仅限于数据库:像Kafka 和RabbitMQ高可用队列这样的分布式消息代理也使用它。

同步复制与异步复制

复制系统的一个重要细节是:复制是同步(synchronously)发生还是异步(asynchronously)发生。

图 - 基于领导者的复制:一个同步从库和一个异步从库

从库1的复制是同步的:在向用户报告写入成功,并使结果对其他用户可见之前,主库需要等待从库1的确认,确保从库1已经收到写入操作。

从库2的复制是异步的:主库发送消息,但不等待从库的响应。

常情况下,复制的速度相当快:大多数数据库系统能在一秒向从库应用变更,但它们不能提供复制用时的保证。

同步复制

优点:从库保证有与主库一致的最新数据副本。

缺点:如果同步从库没有响应,主库就无法处理写入操作。

半同步(semi-synchronous):一个跟随者是同步的,而其他的则是异步的。

通常情况下,基于领导者的复制都配置为完全异步。缺点:写入也不能保证 持久(Durable)。优点:即使所有的从库都落后了,主库也可以继续处理写入。

设置新从库

有时候需要设置一个新的从库:也许是为了增加副本的数量,或替换失败的节点。

如何确保新的从库拥有主库数据的精确副本?

  1. 在某个时刻获取主库的一致性快照(如果可能),而不必锁定整个数据库。大多数数据库都具有这个功能,因为它是备份必需的。对于某些场景,可能需要第三方工具,例如MySQL的innobackupex 。
  2. 将快照复制到新的从库节点。
  3. 从库连接到主库,并拉取快照之后发生的所有数据变更。这要求快照与主库复制日志中的位置精确关联。该位置有不同的名称:例如,PostgreSQL将其称为 日志序列号(log sequence number, LSN),MySQL将其称为 二进制日志坐标(binlog coordinates)
  4. 当从库处理完快照之后积压的数据变更,我们说它赶上(caught up)了主库。现在它可以继续处理主库产生的数据变化了。

处理节点宕机

在其本地磁盘上,每个从库记录从主库收到的数据变更。从库可以从日志中知道,在发生故障之前处理的最后一个事务。

从库失效:追赶恢复

在其本地磁盘上,每个从库记录从主库收到的数据变更。从库可以从日志中知道,在发生故障之前处理的最后一个事务。

主库失效:故障切换

故障切换:需要一个从库被提升为新的主库,重新配置客户端,以将它们的写操作发送给新的主库,其他从库需要开始拉取来自新主库的数据变更。

故障切换可以手动进行或自动进行。

自动故障切换过程:

  1. 确认主库失效超时(Timeout):节点频繁地相互来回传递消息,并且如果一个节点在一段时间内(例如30秒)没有响应,就认为它挂了。
  2. 选择一个新的主库。这可以通过选举过程(主库由剩余副本以多数选举产生)来完成,或者可以由之前选定的控制器节点(controller node)来指定新的主库。主库的最佳人选通常是拥有旧主库最新数据副本的从库(最小化数据损失)。让所有的节点同意一个新的领导者,是一个共识问题。
  3. 重新配置系统以启用新的主库。客户端现在需要将它们的写请求发送给新主库。如果老领导回来,可能仍然认为自己是主库,没有意识到其他副本已经让它下台了。系统需要确保老领导认可新领导,成为一个从库。

故障切换会出现的问题:

  • 如果使用异步复制,则新主库可能没有收到老主库宕机前最后的写入操作。在选出新主库后,如果老主库重新加入集群,新主库在此期间可能会收到冲突的写入,那这些写入该如何处理?最常见的解决方案是简单丢弃老主库未复制的写入。
  • 如果数据库需要和其他外部存储相协调,那么丢弃写入内容是极其危险的操作。例如:一个过时的MySQL从库被提升为主库。数据库使用自增ID作为主键,因为新主库的计数器落后于老主库的计数器,所以新主库重新分配了一些已经被老主库分配掉的ID作为主键。这些主键也在Redis中使用,主键重用使得MySQL和Redis中数据产生不一致,最后导致一些私有数据泄漏到错误的用户手中。
  • 发生某些故障时可能会出现两个节点都以为自己是主库的情况。这种情况称为 脑裂(split brain),非常危险:如果两个主库都可以接受写操作,却没有冲突解决机制,那么数据就可能丢失或损坏。一些系统采取了安全防范措施:当检测到两个主库节点同时存在时会关闭其中一个节点,但设计粗糙的机制可能最后会导致两个节点都被关闭。
  • 主库被宣告死亡之前的正确超时应该怎么配置?在主库失效的情况下,超时时间越长,意味着恢复时间也越长。但是如果超时设置太短,又可能会出现不必要的故障切换。

复制日志的实现

主库记录下它执行的每个写入请求(语句(statement))并将该语句日志发送给其从库。

问题:

  • 任何调用非确定性函数(nondeterministic)的语句,可能会在每个副本上生成不同的值。例如,使用NOW()获取当前日期时间,或使用RAND()获取一个随机数。
  • 如果语句使用了自增列(auto increment),或者依赖于数据库中的现有数据(例如,UPDATE ... WHERE <某些条件>),则必须在每个副本上按照完全相同的顺序执行它们,否则可能会产生不同的效果。当有多个并发执行的事务时,这可能成为一个限制。
  • 有副作用的语句(例如,触发器,存储过程,用户定义的函数)可能会在每个副本上产生不同的副作用,除非副作用是绝对确定的。

基于语句的复制在5.1版本前的MySQL中使用。因为它相当紧凑,现在有时候也还在用。但现在在默认情况下,如果语句中存在任何不确定性,MySQL会切换到基于行的复制。

传输预写式日志(WAL)

写操作追加到日志中:

  • 对于日志结构存储引擎,日志是主要的存储位置。日志段在后台压缩,并进行垃圾回收。
  • 对于覆写单个磁盘块的B树,每次修改都会先写入 预写式日志(Write Ahead Log, WAL),以便崩溃后索引可以恢复到一个一致的状态。

日志包含所有数据库写入的仅追加字节序列。

可以使用完全相同的日志在另一个节点上构建副本:除了将日志写入磁盘之外,主库还可以通过网络将其发送给其从库。

主要缺点是日志记录的数据非常底层:WAL包含哪些磁盘块中的哪些字节发生了更改。这使复制与存储引擎紧密耦合。如果数据库将其存储格式从一个版本更改为另一个版本,通常不可能在主库和从库上运行不同版本的数据库软件。

逻辑日志复制(基于行)

逻辑日志:复制和存储引擎使用不同的日志格式

逻辑日志以行的粒度描述对数据库表的写入的记录序列:

  • 对于插入的行,日志包含所有列的新值。
  • 对于删除的行,日志包含足够的信息来唯一标识已删除的行。通常是主键,但是如果表上没有主键,则需要记录所有列的旧值。
  • 对于更新的行,日志包含足够的信息来唯一标识更新的行,以及所有列的新值(或至少所有已更改的列的新值)。

修改多行的事务会生成多个这样的日志记录,后面跟着一条记录,指出事务已经提交。

基于触发器的复制

触发器允许您注册在数据库系统中发生数据更改(写入事务)时自动执行的自定义应用程序代码。

基于触发器的复制通常比其他复制方法具有更高的开销,并且比数据库的内置复制更容易出错,也有很多限制。然而由于其灵活性,仍然是很有用的。

复制延迟问题

容忍节点故障只是需要复制的一个原因。另一个原因是可扩展性(处理比单个机器更多的请求)和延迟(让副本在地理位置上更接近用户)。

基于主库的复制要求所有写入都由单个节点处理,但只读查询可以由任何副本处理。在这种扩展体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制——如果尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。

当应用程序从异步从库读取时,如果从库落后,会看到过时的信息。这会导致数据库中出现明显的不一致:同时对主库和从库执行相同的查询,可能得到不同的结果,因为并非所有的写入都反映在从库中。这种不一致只是一个暂时的状态——如果停止写入数据库并等待一段时间,从库最终会赶上并与主库保持一致。出于这个原因,这种效应被称为 最终一致性(eventually consistency)。

读己之写

但对于异步复制,如果用户在写入后马上就查看数据,则新数据可能尚未到达副本。看起来好像是刚提交的数据丢失了。

图 - 用户写入后从旧副本中读取数据。需要写后读(read-after-write)的一致性来防止这种异常

读己之写一致性(读写一致性):用户重新加载页面,他们总会看到他们自己提交的任何更新。它不会对其他用户的写入做出承诺:其他用户的更新可能稍等才会看到。

实现技术:

  • 读用户可能已经修改过的内容时,都从主库读;从主库读取用户自己的档案,在从库读取其他用户的档案。
  • 如果应用中的大部分内容都可能被用户编辑,那么大部分内容都必须从主库读取(扩容读就没效果了)。可以使用其他标准来决定是否从主库读取。可以跟踪上次更新的时间,在上次更新后的一分钟内,从主库读。
  • 客户端可以记住最近一次写入的时间戳,系统需要确保从库为该用户提供任何查询时,该时间戳前的变更都已经传播到了本从库中。时间戳可以是逻辑时间戳(指示写入顺序的东西,例如日志序列号)或实际系统时钟。

另一种复杂的情况是:如果用户在某个设备上输入了一些信息,然后在另一个设备上查看,则应该看到他们刚输入的信息。

  • 记住用户上次更新时间戳的方法变得更加困难,因为一台设备上运行的程序不知道另一台设备上发生了什么。元数据需要一个中心存储。
  • 如果副本分布在不同的数据中心,很难保证来自不同设备的连接会路由到同一数据中心。

单调读

如果用户从不同从库进行多次读取,就可能发生时光倒流(moving backward in time)

图 - 用户首先从新副本读取,然后从旧副本读取。时光倒流。为了防止这种异常,我们需要单调的读取。

用户2345两次进行相同的查询,首先查询了一个延迟很小的从库,然后是一个延迟较大的从库。 (如果用户刷新网页,而每个请求被路由到一个随机的服务器,这种情况是很有可能的。)第一个查询返回最近由用户1234添加的评论,但是第二个查询不返回任何东西,因为滞后的从库还没有拉取写入内容。

单调读(Monotonic reads):强一致性(strong consistency)更弱,但比最终一致性(eventually consistency)更强的保证。如果先读取到较新的数据,后续读取不会得到更旧的数据。

确保每个用户总是从同一个副本进行读取,可以基于用户ID的散列来选择副本,而不是随机选择副本。但是,如果该副本失败,用户的查询将需要重新路由到另一个副本。

一致前缀读

图 - 如果某些分区的复制速度慢于其他分区,那么观察者在看到问题之前可能会看到答案。

一致前缀读(consistent prefix reads):如果一系列写入按某个顺序发生,那么任何人读取这些写入时,也会看见它们以同样的顺序出现。

许多分布式数据库中,不同的分区独立运行,因此不存在全局写入顺序:当用户从数据库中读取数据时,可能会看到数据库的某些部分处于较旧的状态,而某些处于较新的状态。

一种解决方案是,确保任何因果相关的写入都写入相同的分区。对于某些无法高效完成这种操作的应用,还有一些显式跟踪因果依赖关系的算法。

复制延迟的解决方案

在使用最终一致的系统时,如果复制延迟增加到几分钟甚至几小时,则应该考虑应用程序的行为。但如果结果对于用户来说是不好体验,那么设计系统来提供更强的保证是很重要的,例如写后读

事务(transaction)存在的原因:数据库通过事务提供强大的保证,所以应用程序可以更加简单。

多主复制

基于领导者的复制有一个主要的缺点:只有一个主库,而所有的写入都必须通过它。如果出于任何原因无法连接到主库, 就无法向数据库写入。

多领导者配置(也称多主、多活复制):处理写入的每个节点都必须将该数据更改转发给所有其他节点。

多主复制的应用场景

运维多个数据中心

多领导者配置中可以在每个数据中心都有主库。

图 - 跨多个数据中心的多主复制

在每个数据中心内使用常规的主从复制;在数据中心之间,每个数据中心的主库都会将其更改复制到其他数据中心的主库中。

运维多个数据中心时,单主和多主的适应情况:

性能

单活配置:每个写入都必须穿过互联网,进入主库所在的数据中心。

多活配置:每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。

容忍数据中心停机

单主配置:如果主库所在的数据中心发生故障,故障切换可以使另一个数据中心里的追随者成为领导者。多活配置:每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心归队时,复制会自动赶上。

容忍网络问题

单主配置:对数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。

多活配置:能更好地承受网络问题,临时的网络中断并不会妨碍正在处理的写入。

多主复制的缺点:两个不同的数据中心可能会同时修改相同的数据,写冲突是必须解决的。

多主复制往往被认为是危险的领域,应尽可能避免。

需要离线操作的客户端

应用程序在断网之后仍然需要继续工作。

从架构的角度来看,这种设置实际上与数据中心之间的多领导者复制类似,每个设备都是一个“数据中心”,而它们之间的网络连接是极度不可靠的。

有一些工具旨在使这种多领导者配置更容易。例如,CouchDB就是为这种操作模式而设计的。

协同编辑

实时协作编辑应用程序允许多个人同时编辑文档。

要保证不会发生编辑冲突,则应用程序必须先取得文档的锁定,然后用户才能对其进行编辑。如果另一个用户想要编辑同一个文档,他们首先必须等到第一个用户提交修改并释放锁定。这种协作模式相当于在领导者上进行交易的单领导者复制。

为了加速协作,您可能希望将更改的单位设置得非常小,并避免锁定。这种方法允许多个用户同时进行编辑,但同时也带来了多领导者复制的所有挑战,包括需要解决冲突。

处理写入冲突

多领导者复制的最大问题是可能发生写冲突,这意味着需要解决冲突。

图 - 两个主库同时更新同一记录引起的写入冲突

同步与异步冲突检测

可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,将失去多主复制的主要优点:允许每个副本独立接受写入。如果想要同步冲突检测,那么可以使用单主程序复制。

避免冲突

处理冲突的最简单的策略就是避免它们:如果应用程序可以确保特定记录的所有写入都通过同一个领导者,那么冲突就不会发生。

收敛至一致的状态

单主数据库按顺序应用写操作:如果同一个字段有多个更新,则最后一个写操作将确定该字段的最终值。

在多主配置中,写入顺序没有定义,所以最终值应该是什么并不清楚。这是不可接受的,每个复制方案都必须确保数据在所有副本中最终都是相同的。因此,数据库必须以一种收敛(convergent)的方式解决冲突,这意味着所有副本必须在所有变更复制完成时收敛至一个相同的最终值。

实现冲突合并解决途径:

  • 给每个写入一个唯一的ID,挑选最高ID的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为最后写入胜利(LWW, last write wins)
  • 为每个副本分配一个唯一的ID,ID编号更高的写入具有更高的优先级。这种方法也意味着数据丢失。
  • 以某种方式将这些值合并在一起。
  • 在保留所有信息的显式数据结构中记录冲突,并编写解决冲突的应用程序代码(也许通过提示用户的方式)。

自定义冲突解决逻辑

作为解决冲突最合适的方法可能取决于应用程序,大多数多主复制工具允许使用应用程序代码编写冲突解决逻辑。

写时执行

只要数据库系统检测到复制更改日志中存在冲突,就会调用冲突处理程序。

读时执行

当检测到冲突时,所有冲突写入被存储。下一次读取数据时,会将这些多个版本的数据返回给应用程序。应用程序可能会提示用户或自动解决冲突,并将结果写回数据库。

什么是冲突?

有些冲突是显而易见的。两个写操作并发地修改了同一条记录中的同一个字段,并将其设置为两个不同的值。其他类型的冲突可能更为微妙,难以发现。

多主复制拓扑

复制拓扑描述写入从一个节点传播到另一个节点的通信路径。

图 - 三个可以设置多领导者复制的示例拓扑

最普遍的拓扑是全部到全部,其中每个领导者将其写入每个其他领导。

MySQL仅支持环形拓扑(circular topology),其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。

另一种流行的拓扑结构具有星形的形状。一个指定的根节点将写入转发给所有其他节点。星型拓扑可以推广到树。

在圆形和星形拓扑中,写入可能需要在到达所有副本之前通过多个节点。节点需要转发从其他节点收到的数据更改。为了防止无限复制循环,每个节点被赋予一个唯一的标识符,并且在复制日志中,每个写入都被标记了所有已经通过的节点的标识符。当一个节点收到用自己的标识符标记的数据更改时,该数据更改将被忽略,因为节点知道它已经被处理。

循环和星型拓扑的问题是,如果只有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。

拓扑结构可以重新配置为在发生故障的节点上工作,但在大多数部署中,这种重新配置必须手动完成。

图 - 使用多主程序复制时,可能会在某些副本中写入错误的顺序

客户端A向主库1的表中插入一行,客户端B在主库3上更新该行。主库2可以以不同的顺序接收写入:它可以首先接收更新,并且仅在稍后接收到相应的插入(其应该在更新之前)。

类似于我们在“一致前缀读”中看到的:更新取决于先前的插入,所以我们需要确保所有节点先处理插入,然后再处理更新。

要正确排序这些事件,可以使用一种称为版本向量(version vectors)的技术。

无主复制

单主复制、多主复制:客户端向一个主库发送写请求,而数据库系统负责将写入复制到其他副本。

主库决定写入的顺序,而从库按相同顺序应用主库的写入。

无领导者实现中,客户端直接将写入发送到到几个副本中,另一些情况下,一个协调者(coordinator)节点代表客户端进行写入。但与主库数据库不同,协调者不执行特定的写入顺序。

当节点故障时写入数据库

在无领导配置中,故障切换不存在。

图 - 仲裁写入,法定读取,并在节点中断后读修复

客户端(用户1234)并行发送写入到所有三个副本,并且两个可用副本接受写入,但是不可用副本错过了它。假设三个副本中的两个承认写入是足够的:在用户1234已经收到两个确定的响应之后,我们认为写入成功。客户简单地忽略了其中一个副本错过了写入的事实。

当一个客户端从数据库中读取数据时,它不仅仅发送它的请求到一个副本:读请求也被并行地发送到多个节点。

读修复和反熵

读修复(Read repair)

当客户端并行读取多个节点时,它可以检测到任何老旧的响应。

用户2345获得了来自Replica 3的版本6值和来自副本1和2的版本7值。客户端发现副本3具有陈旧值,并将新值写回复制品。这种方法适用于频繁阅读的值。

反熵过程(Anti-entropy process)

一些数据存储具有后台进程,该进程不断查找副本之间的数据差异,并将任何缺少的数据从一个副本复制到另一个副本。反熵过程不会以任何特定的顺序复制写入,并且在复制数据之前可能会有显著的延迟。

读写的法定人数

如果有n个副本,每个写入必须由w节点确认才能被认为是成功的,并且我们必须至少为每个读取查询r个节点。 (在我们的例子中,$n = 3,w = 2,r = 2$)。只要$w + r> n$,我们期望在读取时获得最新的值,因为r个读取中至少有一个节点是最新的。遵循这些r值,w值的读写称为法定人数(quorum)的读和写。你可以认为,r和w是有效读写所需的最低票数。

参数n,w和r通常是可配置的。一个常见的选择是使n为奇数(通常为3或5)并设置 $w = r =(n + 1)/ 2$(向上取整)。

仲裁条件$w + r> n$允许系统容忍不可用的节点,如下所示:

  • 如果$w
  • 如果$r
  • 对于$n = 3,w = 2,r = 2$,我们可以容忍一个不可用的节点。
  • 对于$n = 5,w = 3,r = 3$,我们可以容忍两个不可用的节点。
  • 通常,读取和写入操作始终并行发送到所有n个副本。 参数w和r决定我们等待多少个节点,即在我们认为读或写成功之前,有多少个节点需要报告成功。

图 - 如果$w + r > n$,读取r个副本,至少有一个r副本必然包含了最近的成功写入

如果少于所需的w或r节点可用,则写入或读取将返回错误。

仲裁一致性的局限性

如果你有n个副本,并且你选择w和r,使得$w + r> n$,你通常可以期望每个读取返回为一个键写的最近的值。情况就是这样,因为你写的节点集合和你读过的节点集合必须重叠。

较小的w和r更有可能会读取过时的数据,因为您的读取更有可能不包含具有最新值的节点。

在$w + r> n$的情况下,也可能存在返回陈旧值的边缘情况:

  • 如果使用松散的法定人数,w个写入和r个读取落在完全不同的节点上,因此r节点和w之间不再保证有重叠节点。
  • 如果两个写入同时发生,不清楚哪一个先发生。在这种情况下,唯一安全的解决方案是合并并发写入。如果根据时间戳(最后写入胜利)挑选出胜者,则由于时钟偏差,写入可能会丢失。
  • 如果写操作与读操作同时发生,写操作可能仅反映在某些副本上。在这种情况下,不确定读取是返回旧值还是新值。
  • 如果写操作在某些副本上成功,而在其他节点上失败(例如,因为某些节点上的磁盘已满),在小于w个副本上写入成功。所以整体判定写入失败,但整体写入失败并没有在写入成功的副本上回滚。这意味着如果一个写入虽然报告失败,后续的读取仍然可能会读取这次失败写入的值。
  • 如果携带新值的节点失败,需要读取其他带有旧值的副本。并且其数据从带有旧值的副本中恢复,则存储新值的副本数可能会低于w,从而打破法定人数条件。
  • 即使一切工作正常,有时也会不幸地出现关于时序(timing)的边缘情况。

监控陈旧度

对于基于领导者的复制,数据库通常会公开复制滞后的度量标准,可以将其提供给监视系统。

在无领导者复制的系统中,没有固定的写入顺序,这使得监控变得更加困难。

松散法定人数与带提示的接力

合理配置的法定人数可以使数据库无需故障切换即可容忍个别节点的故障。

法定人数并不像它们可能的那样具有容错性。网络中断可以很容易地将客户端从大量的数据库节点上切断。

松散的法定人数(sloppy quorum)【37】:写和读仍然需要w和r成功的响应,但是那些可能包括不在指定的n个“主”节点中的值。

带提示的接力(hinted handoff):一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“本地”节点。

松散法定人数对写入可用性的提高特别有用:只要有任何w节点可用,数据库就可以接受写入。

运维多个数据中心

多数据中心支持:副本的数量n包括所有数据中心的节点,在配置中,您可以指定每个数据中心中您想拥有的副本的数量。无论数据中心如何,每个来自客户端的写入都会发送到所有副本,但客户端通常只等待来自其本地数据中心内的法定节点的确认,从而不会受到跨数据中心链路延迟和中断的影响。对其他数据中心的高延迟写入通常被配置为异步发生,尽管配置有一定的灵活性。

检测并发写入

由于可变的网络延迟和部分故障,事件可能在不同的节点以不同的顺序到达。

图 - 并发写入Dynamo风格的数据存储:没有明确定义的顺序

两个客户机A和B同时写入三节点数据存储区中的键X:

  • 节点 1 接收来自 A 的写入,但由于暂时中断,从不接收来自 B 的写入。
  • 节点 2 首先接收来自 A 的写入,然后接收来自 B 的写入。
  • 节点 3 首先接收来自 B 的写入,然后从 A 写入。

如果每个节点只要接收到来自客户端的写入请求就简单地覆盖了某个键的值,那么节点就会永久地不一致,最终获取请求所示:节点2认为 X 的最终值是 B,而其他节点认为值是 A 。

最后写入胜利(丢弃并发写入)

实现最终融合的一种方法是声明每个副本只需要存储最“最近”的值,并允许“更旧”的值被覆盖和抛弃。

“最近”如何确定:写入是并发(concurrent)的,所以它们的顺序是不确定的。

即使写入没有自然的排序,我们也可以强制任意排序。

最后写入胜利(LWW, last write wins):为每个写入附加一个时间戳,挑选最“最近”的最大时间戳,并丢弃具有较早时间戳的任何写入。

LWW实现了最终收敛的目标,但以持久性为代价:如果同一个Key有多个并发写入,即使它们都被报告为客户端成功(因为它们被写入 w 个副本),但只有一个写入将存活,而其他写入将被静默丢弃。

关键词: