「阅读」数据密集型系统设计 第五章 复制

第五章 复制

5.1 概述

微服务架构下,为了用户体验、可用性等考量,数据往往存储在多个机器上。为了保证数据的一致性,数据之间一定会存在「复制」 操作。

复制是解决一致性的一个手段,但是在当前微服务架构中,一致性已经不是一个 yes or no 的问题,一致性也存在着不同程度。在微服务中通常只是要求最终一致性,一般来说最终一致性有以下五种:

  1. 因果一致性
  2. 读自己所写
  3. 会话一致性
  4. 单调读一致性
  5. 单调写一致性

那么如何理解「复制」?

  1. 复制中涉及到的实体有哪些?
    1. 复制的源头和目标实体:复制的源头和目标显然都是数据库,根据架构不同,存在主库、从库之分或者所有库均可读写
    2. 复制的数据粒度:数据页?数据行?命令行?
  2. 复制过程中,数据同步的时效性
    1. 复制的同步异步
    2. 一致性级别
  3. 异常处理
    1. 可用性角度
      1. 写入节点出现故障,如何实现故障转移?
      2. 节点重连后,如何恢复?
    2. 一致性角度
      1. 复制过程中的写入延迟
      2. 写入信息之间的冲突如何解决

本章节中通过如下三种主流的变更方式来讨论以上问题

  • 单主节点复制
  • 多主节点复制
  • 无主节点复制

5.2 变更复制方式

5.2.1 单主节点复制模式

该模式:一个主节点,多个从节点。基本工作方式:

  1. 指定一个副本为主节点,其余为从节点
  2. 所有写操作必须在主节点完成,读操作可以在任意节点完成
  3. 主库写入本地存储时,从库会收到变更信息,并更新本地数据

5.2.2 多主复制复制模式

介绍

单主复制模式的结构中所有写入必须通过一个主库。

多主模式,允许向多个主库中的任何一个进行写入,每个主库的变动会通知其他所有库(包括其他主库&&从库)。如果说单主结构是一个树形结构,那么多主结构显然是一个图形结构。

在单个数据中心内部使用多个主库的配置意义不大,因为其导致的复杂性已经超过了它的优势。多主复制模式更多用于存在多个数据中心的场景。

![[多主复制模式.png]]

多主复制拓扑

![[多主复制拓扑.png]]

最常见的拓扑结构是 all-to-all。

MySql 仅支持环形拓扑,每个节点只能从一个节点接收写入,并将写入转发到另一个节点中

容错性:

  • 环形 && 星型:一个节点故障,其他节点遭殃。
  • all-to-all 结构:可以避免单点故障

拓扑问题:一致性问题更加复杂。

5.2.3 无主复制模式

所有节点均可以写入,可以进行读取,也就是所有节点是主库的同时,也是从库。例如 亚马逊的 Dynamo 系统。

在无主复制的也存在不同的实现

  1. 直接将写入发送到任意节点中
  2. 通过协调者代表客户端写入。

在无主模式中,如果希望读取到的值是最新的值,每次从一个副本中获取是有问题的,因此在该模式下,每次读取需要读取多个副本(读写 Quorum)。

模式对比 && 总结

单主复制多主复制无主复制
性能(前提存在多个数据中心)每次写入必须通过主库的数据中心,写入时间稍差每次写入可以在距离最近的数据中心写入,性能相对较好同多主复制
容忍数据中心停机主库所在数据中心出现故障,必须从另外数据中心中提升一个节点为主库每个数据中心独立且拥有主库,同多主复制
容忍网络问题对主库所在的数据中心之间的连接问题很敏感每个数据中心均有主库,容忍能力较强同多主复制

从主从的角度来理解,三种模式的区别在于对主节点的数量限制,三个模式对主节点的个数限制分别为:1个、部分、全部。

从微服务的 CAP 角度理解,三种不同的模式对可用性、一致性的程度显然是不同的,从单主到无主的过程中,数据一致性的难度越来越大,但是服务提供的可用性越来越强,一切都是 trade-off。

5.3.4 复制模式中需要解决什么问题?

1. 同步复制 or 异步复制?

同步复制:从库复制完后,返回请求成功
异步复制:主库更新完后,直接返回成功,从库异步执行
半同步复制:主库更新完成&&存在一个从库更新完成后,直接返回成功,其他从库异步执行

这个问题是三种模式下的共性问题(对于无主复制模式,当前写入操作的库看作主库,其余库看作写库),一般出于性能或者说用户体验的角度考虑,主要考虑异步复制/半同步复制。

2. 新增从库节点,如何同步?

从库获取某个时刻主库的一致性快照,并在本地执行
从库连接主库后,获取快照之后的变更数据,并在本地执行,同步完成

3. 如何处理节点失效?

  1. 从库节点失效
    1. 从库节点重新启动后,连接主库获取变更数据,重新执行进行恢复
  2. 主库节点失效:故障切换
    1. 步骤:
      1. 确认主库失效:通常是通过主库节点是否超时判断
      2. 选举新主库:共识问题,原则是选择拥有最完整数据的从库节点
      3. 重新配置
    2. 问题
      1. 异步复制,在新主库选举后,旧主库重新连接上的场景,如何处理旧主库中没有复制的数据?常规直接丢弃
      2. 脑裂:存在两个主库。
    3. 无节点复制模式不需要故障切换

4. 如何实现主从的复制?

简单总结一下,复制的粒度包括:执行语句、数据页、数据行。触发器方式和上述方式的区别在于灵活性。

  1. 基于语句复制
    1. 主库记录所有执行过的语句,从库同样执行这些语句
    2. 问题:数据具有不确定性
      1. 使用不确定性函数(Now 函数)时,不同从库数据不一致
  2. 传输日志 WAL
    1. 将写操作影响的数据追加到日志中(数据页维度)
    2. 问题:日志和存储引擎强相关(日志中记录的修改的数据页,这和存储引擎的数据结构有关)
  3. 逻辑日志复制
    1. 行的粒度描述数据库的写入信息
      1. 插入数据:包含所有列值
      2. 删除数据:包含能够确认这个行的信息即可
      3. 更新数据:包含标识行的信息 && 更新的行的新数据
  4. 触发器复制
    1. 通过将自定义代码注册到数据库系统中,当数据变更时复制数据到另一个系统中
    2. 问题:灵活但是容易出错

5. 复制延迟 && 一致性

复制延迟介绍
多个节点之间的数据复制是异步进行的场景下,存在从库数据同步落后的情况,此时对该从库的数据读取获得的将不是最新值。

简单来说,复制延迟是指主库写入完成到从库数据同步完成之间的延迟。

![[复制延迟问题.png]]

一致性级别

  1. 读己之写
    • 承诺:
      当前用户重新加载页面时,总是会看到自己提交的所有更新。对其他用户的更新不做如此承诺。
    • 方案:
      1. 对于用户可能修改的内容总是从主库读取。规则:当前用户读取自己的信息用主库,读取别人的信息用从库
      2. 如果应用中,用户看到的大部分内容都是当前用户自己编辑的。这种情况下使用其他标准来决定是否从主库读取,减少主库压力。
        • 跟踪上次更新时间,在上次更新的一分钟内,读取主库,反之读取从库。
        • 监控从库的复制延迟,防止任何滞后主库超过一分钟的从库发出查询。
        • 时间戳选择&&问题
          • 逻辑时间戳(日志序列号)、系统时钟(时钟同步问题)
        • 多个客户端请求服务时,如何处理跨端写后读一致性
          1. 不同设备之间对时间戳的同步
          2. 副本在不同数据中心,不同设备如何保证连接到同一个数据中心
  2. 单调读
    • 场景
      • 用户从不同从库中读取数据时,由于不同从库数据不一致,导致用户每次读取到的数据不同。
    • 承诺
      • 每个用户顺序进行多次读取,不会看到时间回退。
    • 实现方式
      • 保证每个用户从同一个副本中读取,基于用户 ID 选择副本,不是随机选择副本。
  3. 一致性前缀读
    • 保证
      • 如果一系列写入按照某个顺序进行,读取时同样可以按照这个顺序。
    • 方案
      • 任何带有因果的写入全部存储到一个分区中。

最终一致性 和用户体验之间的矛盾:分布式系统中明明是异步复制,却假设是同步的,这就是麻烦的根源。

在单体系统中,事务解决了很多问题,但是在分布式数据库中,事务会让性能和可用性付出一定的代价。

6. 写入冲突处理

介绍

写入冲突是多主复制模式带来的最大问题(无主模式中也有类似问题)。

已知:

  • 在多主结构下,允许多个主库分别对同一个数据执行写入操作
  • 所有数据最后需要收敛到一致的状态

当多个主节点对这条数的的更新存在不一致时,数据收敛出现问题,这就是写入冲突。

写入冲突解决方案

首先需要明确数据库之间合并的最终态如何选择:

  • 给每个写入一个唯一 ID,选择最高 ID 写入(最后写入胜利),会有数据丢失
  • 对副本设置唯一 ID,选择最高 ID 副本的数据作为最终结果,会有数据丢失
  • 保存所有信息,程序中通过代码解决冲突,或者将所有冲突之处也展示
    • 写入时解决冲突
      • 数据库系统检测到复制更改日志存在冲突,调用冲突处理程序,例如 Bucardo
    • 读取时解决冲突
      • 所有冲突都会写入,读取时将所有冲突都返回程序,用户程序自行解决,例如:CouchDB

其中,前两种方式大体上都可以认为是最终写入者胜利,第三种方式则将冲突解决放到了代码层面解决。

总结

这个章节总体上介绍了三种不同的变更复制方式以及面临的问题。有些问题是非常直观的,例如同步复制/异步复制,但是有些问题在理解上是非常困难的,例如复制延迟&&一致性。

在无主复制模式部分,还提到了读写 Quorum 等问题,对比不太感兴趣,因此没有描述,有兴趣的可以自行阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿德罗斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值