多主节点备份

使用单主节点有一个天然的缺陷:当主节点不可用时,就瘫痪了。

所以有了多主节点的模型:每个主节点都要将数据变化的过程告诉其他所有节点(包括其他主节点)

主主备份的使用场景

主主备份通常用在多数据中心的场景。

多数据中心操作

在多数据中心场景下,如果只有一个主节点,那么所有的写操作都会路由到主节点所在的数据中心。这显然不合理。

主主备份 模型下,每个数据中心都有一个主节点。如下图所示:

在每个数据中心中,使用的是常规的 主从模型,数据中心之间通过主节点复制数据。

在读数据中心的场景下,主从 vs. 主主

  • 性能
    主从模型中,选择主节点的路由时间会严重影响性能。而主主模型对用户的平均时间更短。
  • 崩溃恢复
    主从模型中:主节点崩溃后从节点会被选举违心的主节点;主主模型中:一样的。
  • 网络问题恢复
    主从模型:强烈依赖内网的可靠性;主主模型:对公网的崩溃容忍性更强。

如上图所示,主主模型中有一个大问题:多个主节点的同一份数据被同时修改了,也就是途中的conflict resolution

主主备份通常来说存在很多危险的问题:自增主键,触发器,数据完备性等,所以轻易不要用。

离线操作客户端

当数据中心之间的数据是离线的时候,或者说副本延迟不那么敏感的时候。多客户端的数据同步就是用的主主模型。

协作编辑

和离线客户端类似,是多写的场景,区别是对实时性有要求。某个用户的修改会立即异步地在 Server 和其他客户端上修改。

这种情况对冲突处理要求很高。

处理写冲突

主主备份中最麻烦的就是冲突处理

如下图所示:

冲突避免

最简单的冲突处理方法是——避免冲突: 所有向同一份记录的写操作都经由一个主节点,冲突自然就避免了。

但是总不能一直不处理吧。

收敛一致性状态

单主节点的写操作的序列的:最后一个写操作决定了某个记录的最终值。

多主节点的写操作没有绝对的顺序。

系统必须保证每个副本上的数据的最终一致性。

因此,数据库必须以一种 收敛 的方式保证数据在所有副本的最终一致。

有几种收敛冲突解决的办法:

  • 给每个写操作一个唯一的 ID (UUID 之类的)。只保留最后一个写。但是有很大的风险会丢失数据;
  • 用某种手段把写的值合并到一起:比如直接拼接在一起。
  • 把所有的冲突都显示地保留下来,让用户自己解决。

自定义冲突解决逻辑

在应用代码中自定义解决冲突的办法:

  • 写时解决
    数据库一旦检测到不同副本之间有冲突,就在后台中立立即调用冲突解决方法解决掉了;用户无感知;

  • 读时解决
    有冲突时,全部记录下来,读的时候再自动解决冲突或者让用户解决。

自动冲突解决方案:

直接上图吧,不想翻译了。

多主节点副本拓扑结构

副本拓扑是多个主节点通信的路径的结构。

如上图所示,最常见的拓扑结构是 c all-to-all

环形拓扑和星形拓扑的每个节点使用唯一的标示来表明自己以避免重复写入。这种结构有这个缺陷:单点错误(不用多说了吧)。All-to-all结构可以避免这个,但是它有其他的问题:

为了解决这个问题,一种叫做 版本向量 的技术被使用了。

仔细阅读数据库文档!

无主副本

Amazon 的 Dynamo 使用的是 无主副本 模式,Riak, Cassandra 也一样。

两种大概的实现:

  1. 客户端直接向多个副本写数据
  2. 中间有一个协调器,协调器不强制顺序。

当有一个节点不可用时向数据库写数据

如图所示:

图片表示得很明显了,当有两个 ok 时客户端就认为已经 ok 了,不理会没回复的 down 掉的节点。

为了解决 down 掉的节点的过时数据问题,读请求也同时发给了多个副本。当得到的数据不一样时,使用类似 版本 的方法来解决冲突。

读修复和反熵(anti-entropy)

副本机制应该保证最终所有副本上的数据都一样,在无主模型的副本机制下,有两种方法:

  • 读修复(Read repair)
    使用类似 数据版本 的方法,当检测到有过时数据时更新这个副本上的数据。这种方法适用于读频繁的场景

  • 反熵(anti-entropy)进程
    一个后台进程持续地关注副本间的数据差并且不停地复制这些不同。和有主副本不同的是,反熵进程不以一定的顺序来复制数据,因此,也存在数据延迟的可能。

读和写的规定数量(Quorums)

假设有 n 个副本节点,当写入的时候要求至少有 w 个副本回复 ok,读的时候至少读 r 个节点,那么必须满足:

w + r > n

很明显,不再赘述。通常的配置是:

n 是一个奇数,w = r = (n+1)/2

当然也可以变: 比如是读频繁的操作的话可以: w=n, r=1

法定数量一致性的局限

有很多情况会造成即使满足了上述公式也会返回过时数据的问题。

过时监测

从运维角度来看,监测数据库是否返回过时数据是很重要的事情。

有主副本中,要监测副本数据的延迟量很容易,只需要用主节点的 log position - 副节点的 log Posion 就知道了每个副本节点的延迟量。

无主副本 模式中,就不那么好办了,特别是如果这种模式只使用了 读修复 而没有 反熵进程

(TO BE CONTINUED…)