大数据杂谈(3):复制技术之多主节点复制

1,278 阅读12分钟

单个主节点的复制架构存在一个明显的缺点,系统只有一个主节点,所有的写入都必须经过该主节点。如果该主节点无法连接,则会影响所有写入操作。对主从复制模型进行自然地拓展,则可以配置多个主节点,每个主节点还是其它主节点的从节点。有一些工具可以让多主配置更容易,如CouchDB就是为这种操作模式及而设计的

适用场景

在一个数据中心内使用多个主节点意义不大,其复杂性已经超过所带来的好处,在以下场景是合理的

多数据中心

image.png

有时候出于提高稳定性或更接近用户的考虑,将数据库的副本横跨多个数据中心。有了多主节点复制模型,可以在每个数据中心都配置主节点。如上图所示,在每个数据中心内,采用常规的主从复制方案,数据中心之间则由各个主节点负责个数据中心的数据交换

在多数据中心场景下,单主节点和多主节点之间存在以下差异:

  • 性能:对于单主节点,每个写请求都要经广域网发送至主节点所在的数据中心,会大大增加延迟并且背离了就近访问的初衷。对于多主节点模型来说,每个写操作都可以得到快速的响应,终端体验更好
  • 容忍数据中心失效:对于主从复制,如果主节点所在的数据中心发生故障,则必须切换到另一个数据中心并设置一个新的主节点。多主节点模型中,每个数据中心都可以独立于其它数据中心运行
  • 容忍网络问题:数据中心之间通常是经过广域网,网络稳定性更差。对于主从复制来说写请求是同步操作,更依赖网络稳定性,相反多主节点模型采用异步复制,对网络容忍度更高

有些数据库已经内嵌或借助工具实现了多主复制,如MySQLPostgreSQLOracle

尽管多主节点模型有上述优势,但缺点也较为明显:不同的数据中心可能会同时修改相同的数据,因而必须解决好潜在的写冲突,后面我们会有更详细的介绍

很多数据库中对多主节点模型的支持要晚于单节点模型,顾稳定性可能会差一些,这点应该保持谨慎

离线客户端操作

希望应用在断网后还能继续工作,例如手机等设备上的日历程序,在断网状态下依然可以查看和编辑会议,等到设备上线时会与服务器自动做同步。这种情况下每一个设备都是等同于充当了一个主节点,然后在所有设备之间采用异步的方式同步这些主节点上的副本,滞后时间可能是几小时或者数天

协作编辑

实时协作编辑应用允许多个用户同时编辑文档。我们通常不会将协作编辑等价于数据库复制问题,但二者非常相似。当一个用户编辑文档时,所做的更改会立即应用到本地副本,然后异步复制到服务器以及编辑同一文档的其他用户。如果要确保不会发生编辑冲突,则应用程序必须先将文档锁定,然后才能对其进行编辑。如果另一个用户想要编辑同一个文档, 首先必须等到第一个用户提交修改并释放锁。这种协作模式相当于主从复制模型下在主节点上执行事务操作

处理写冲突

多主复制的最大问题是可能发生写冲突,这意味着必须有方案来解决冲突

例如两个用户同时更改标题,用户1将标题从A改为B,用户2将标题从A改为C。每个用户的更改者R顺利地提交到本地主节点,但当更改被异步复制到对方时却发现存在冲突

image.png

同步与异步冲突检测

如果是主从复制,第二个写请求要么被阻塞直到第一个写完成,要么被终止(用户必须重试)。然而在多主节点复制模型下,这两个写请求都是成功的,井且只能在稍后的时间点上才能异步检测到冲突,那时再要求用户层来解决冲突为时已晚。

理论上也可以做到同步冲突检测,即等待完成对所有副本的同步后再通知用户写入成功。但是这样做将会失去多主节点的主要优势:允许每个主节点独立接受写请求。如果确实想要同步方式冲突检测,或许应该考虑采用单主节点的主从复 制模型

避免冲突

处理冲突最理想的策略是避免发生冲突,如果应用层可以保证对特定记录的写请求总是通过同一个主节点,这样就不会发生写冲突。现实中由于不少多主节点复制模型所实现的冲突解决方案存在瑕疵,因此避免冲突反而成为大家普遍推荐的首选方案

但是,有时可能需要改变事先指定的主节点,例如由于该数据中心发生故障,不得不将流量重新路由到其他数据中心,或者是因为用户已经漫游到另一个位置,因而更靠近新数据中心。此时冲突避免方式不再有效,必须有措施来处理同时写入冲突的可能性

收敛于一致状态

对干主从复制模型,数据更新符合顺序性原则,即如果同-个字段有多个更新,则最后一个写操作将决定该字段的最终值

对于多主节点复制模型,由于不存在这样的写入顺序,所以最终值也会变得不确定。如果每个副本都只是按照它所看到写入的顺序执行,那么数据库最终将处于不一致状态,而这是不可接受的。因此数据库必须以一种收敛趋同的方式来解决冲突,这也意味着当所有更改最终被复制、同步之后,所有副本的最终值是相同的

实现收敛的冲突解决有以下可能的方式:

  • 给每个写入分配唯一的ID,例如一个时间戳、一个足够长的随机数、一个UUID或者一个基于键-值的哈希,挑选最高ID作为胜利者写入,并将其他写入丢弃。如果基于时间戳,这种技术被称为最后写入者获胜。虽然这种方法很流行,但缺点是很容易造成数据丢失
  • 为每个副本分配一个唯一的ID ,井制定规则,例如序号高的副本写入始终优先于序号低的副本,这种方法也可能会导致数据丢失
  • 以某种方式将这些值合并在一起。例如按字母顺序排序然后拼接在一起,合并的标题可能类似于“ B/C ”
  • 利用预定义好的格式来记录和保留冲突相关的所有信息,然后依靠应用层的逻辑,事后解决冲突(可能会提示用户)

自定义冲突解决逻辑

解决冲突最合适的方式可能还是依靠应用层,大多数多主节点复制模型都有工具来让用户编写应用代码来解决冲突,可以在写入时或在读取时执行这些代码逻辑:

  • 在写入时执行:只要数据库系统在复制变更日志时检测到冲突,就会调用应用层的冲突处理程序。例如Bucardo支持编写一段Perl代码。
  • 在读取时执行:当检测到冲突时,所有冲突写入值都会暂时保存下来。下一次读取数据时,会将数据的多个版本读返回给应用层。应用层可能会提示用户或自动解决冲突,井将最后的结果返回到数据库,CouchDB采用了这样的处理方式。

注意:冲突解决通常用于单个行或文档,而不是整个事务。因此如果有一个原子事务包含多个不同写请求,每个写请求仍然是分开考虑来解决冲突

什么是冲突

有些冲突是显而易见的。例如两个写操作同时修改同一个记录中的同一个字段,井将其设置为不同的值,那么毫无疑问这就是一个冲突

而其他类型的冲突可能会非常微妙,更难以发现。例如一个会议室预订系统,它主要记录哪个房间由哪个人在哪个时间段所预订。这个应用程序需要确保每个房间只能有一组人同时预定(即不得有相同房间的重复预订),如果为同一个房间创建两个不同的预订,可能会发生冲突。尽管应用在预订时会检查房间是否可用,但如果两个预订是在两个不同的主节点上进行,则还是存在冲突的可能。很遗憾,对此没有现成的答案。后面我们将对这个问题进行深入的剖析和介绍。我们将在后面谈到事务时介绍更多的冲突示例,后面还会讨论检测和解决冲突的可扩展方法

冲突解决的规则可能会变得越来越复杂,且自定义代码很容易出错。亚马逊是一个经常被引用的反面例子:有一段时间,购物的冲突解决逻辑依靠用户的购物车页面,后者保存了所有的物品,但顾客有时候会发现之前已经被拿掉的商品,再次出现在他们的购物车中

拓扑结构

复制的拓扑结构描述了写请求从一个节点的传播到其他节点的通信路径,如果有两个主节点,则只存在一个合理的拓扑结构:主节点l必须把所有的写同步到主节点2,反之亦然。但如果存在两个以上的主节点, 则会有多个可能的同步拓扑结构,如下图所示

image.png

最常见的拓扑结构是全部-至-全部,见上图(c),每个主节点将其写入同步到其他所有主节点。而其他一些拓扑结构也有普遍使用,例如,默认情况下MySQL只支持环形拓扑结构,其中的每个节点接收来自前序节点的写入,井将这些写入(加上自己的写入)转发给后序节点。另一种流行的拓扑是星形结构的: 一个指定的根节点将写入转发给所有其他节点。星形拓扑还可以推广到树状结构。

在环形和星形拓扑中,写请求需要通过多个节点才能到达所有的副本,即中间节点需要转发从其他节点收到的数据变更。为防止无限循环,每个节点需要赋予一个唯一的标识符,在复制日志中的每个写请求都标记了已通过的节点标识符。如果某个节点收到了包含自身标识符的数据更改,表明该请求已经被处理过,因此会忽略此变更请求,避免重复转发

环形和星形拓扑的问题是,如果某一个节点发生了故障,在修复之前,会影响其他节点之间复制日志的转发。可以采用重新配置拓扑结构的方法暂时排除掉故障节点。在大多数部署中,这种重新配置必须手动完成。而对于链接更密集的拓扑(如全部到全部),消息可以沿着不同的路径传播,避免了单点故障,因而有更好的容错性

但另一方面,全链接拓扑也存在一些自身的问题。主要是存在某些网络链路比其他链路更快的情况(例如由于不同网络拥塞),从而导致复制日志之间的覆盖,如图所示:

image.png

在上端A 向主节点l的表中首先插入一行,然后客户端B在主节点3上对该行记录进行更新。而在主节点2上,由于网络原因可能出现意外的写日志复制顺序,例如它先接收到了主节点3的更新日志(从主节点2的角度来看,这是对数据库中不存在行的更新操作),之后才接收到主节点1 的插入日志(按道理应该在更新日志之前到达)

这里涉及到一个因果关系问题,类似于在本章前面“前缀一致读”所看到的:更新操作一定是依赖于先前完成的插入,因此我们要确保所有节点上一定先接收插入日志,然后再处理更新。在每笔写日志里简单地添加时间戳还不够,主要因为无能确保时钟完全同步,因而无怯在主节点2上正确地排序所收到日志

为了使得日志消息正确有序,可以使用一种称为版本向量的技术,稍后将讨论这种技术(参见本章后面的“检测并发写入”)。需要指出,冲突检测技术在许多多主节点复制系统中的实现还不够完善

如果正在使用支持多主节点复制的系统,这些问题都值得注意。细查阅相关文档,详细测试这些数据库,以确保它确实提供所期望的功能