DDIA读书笔记 | 第五章:复制

问题引出

一、共享架构

垂直伸缩、向上伸缩

1. 共享内存架构

  • 特点:
  • 所有的组件都可以看作一台单独的机器,许多处理器,内存和磁盘可以在同一个操作系统下相互连接,快速的相互连接允许任意处理器访问内存或磁盘的任意部分

  • 分区:
  • 在大型机中,尽管任意处理器都可以访问内存的任意部分,但总有一些内存区域与一些处理器更接近(称为 非均匀内存访问)。 为了有效利用这种架构特性,需要对处理进行细分,以便每个处理器主要访问临近的内存,分区(partitioning) 仍然是必要的。

  • 存在的问题
  • 成本增长速度快于线性增长

    2. 共享磁盘架构
    多台具有独立处理器和内存的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接

    这种架构用于某些数据仓库,但竞争和锁定的开销限制了共享磁盘方法的可伸缩性

    二、无共享内存架构

    水平伸缩,向外伸缩
    运行数据库软件的每台机器 / 虚拟机都称为 节点(node)。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。

    虽然分布式无共享架构有许多优点,但它通常也会给应用带来额外的复杂度,有时也会限制你可用数据模型的表达力。

    文章目录

      • 问题引出
        • 一、共享架构
        • 二、无共享内存架构
    • 前言
        • 复制的原因:
    • 一、领导者与追随者?
        • 1.0 基于领导者的复制 / 主从复制
        • 1.1 同步复制与异步复制
        • 1.2 关于复制的研究
        • 1.3 设置新从库
        • 1.4 处理节点宕机
        • 1.5 复制日志的实现
    • 二、复制延迟问题
    • 异步从库读取异常的例子
      • 2.0 读己之写一致性(读写一致性)
          • 基于领导者的复制系统中读写一致性的实现:
      • 2.1 单调读
      • 2.2 一致前缀读
      • 2.3 复制延迟的解决方案
    • 三、多主复制
        • 3.1 应用场景
        • 3.1.1 运维多个数据中心
        • 3.1.2 需要离线操作的客户端
        • 3.1.3 协同编辑
        • 3.2 处理写入冲突
        • 3.2.1 同步与异步冲突检测
        • 3.2.2 避免冲突
        • 3.2.3 收敛至一致的状态
        • 3.2.4 自定义冲突解决逻辑:
        • 3.3 多主复制拓扑
    • 四、无主复制
        • 4.1 当节点故障时写入数据库
        • 4.1.1 读修复和反熵
        • 4.1.2 读写的法定人数
        • 4.1.2 法定人数一致性的局限性
        • 4.1.3 监控陈旧度
        • 4.2 宽松的法定人数与提示移交
    • 总结
      • 一、复制可以用于几个目的:
      • 二、复制的三种主要方法
      • 三、复制的同步与异步
      • 四、复制的同步与异步


    前言

    复制的原因:

  • 容忍节点故障(提高可用性)
  • 伸缩可以接受读请求的机器数量(提高读取吞吐量)
  • 降低延迟(让副本在地理位置上更接近用户)
  • 1. 复制的难点: 在于处理复制数据的 变更(change)

    2. 流行的变更复制算法:

  • 单领导者(single leader)
  • 多领导者(multi leader)
  • 无领导者(leaderless)
  • 3. 复制时的问题:
    使用同步复制还是异步复制?
    如何处理失败的副本?


    一、领导者与追随者?

    存储数据库副本的每个节点称为 副本(replica)

    1.0 基于领导者的复制 / 主从复制

    副本之一被指定为 领导者或者主库 。当客户端要向数据库写入时,它必须将请求发送给 领导者领导者将新数据写入其本地存储。

    1. 其他副本被称为 追随者,亦称为 只读副本,从库/ 备库/热备。每当领导者将新数据写入本地存储时,它也会将数据变更发送给所有的追随者,称之为 复制日志(replication log) 记录或 变更流(change stream)。
    2. 每个跟随者从领导者拉取日志,并相应更新其本地数据库副本,方法是按照领导者处理的相同顺序应用所有写入。
    3. 当客户想要从数据库中读取数据时,它可以向领导者或追随者查询。 但只有领导者才能接受写操作(从客户端的角度来看从库都是只读的)。

    1.1 同步复制与异步复制

    同步复制:
    一个 跟随者是同步的,而其他的则是异步的。如果同步从库变得不可用或缓慢,则使一个异步从库同步。这保证你至少在两个节点上拥有最新的数据副本:主库和同步从库。 这种配置有时也被称为 半同步

    完全异步复制:
    通常情况下,基于领导者的复制都配置为完全异步。 在这种情况下,如果主库失效且不可恢复,则任何尚未复制给从库的写入都会丢失。 这意味着即使已经向客户端确认成功,写入也不能保证 持久(Durable)。

  • 优点:
  • 即使所有的从库都落后了,主库也可以**继续处理写入**。

    1.2 关于复制的研究

    对于异步复制系统而言,主库故障时有可能丢失数据。链式复制是同步复制的一种变体,主要研究不丢失数据但仍能提供良好性能和可用性的复制方法。

    1.3 设置新从库

    也许是为了增加副本的数量,或替换失败的节点。如何确保新的从库拥有主库数据的精确副本?

    可以通过锁定数据库(使其不可用于写入)来使磁盘上的文件保持一致,但是这会违背高可用的目标。幸运的是,拉起新的从库通常并不需要停机。
    过程如下:

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

    1.4 处理节点宕机

    对运维而言,能在系统不中断服务的情况下重启单个节点好处多多。我们的目标是,即使个别节点失效,也能保持整个系统运行,并尽可能控制节点停机带来的影响。

    如何通过基于主库的复制实现高可用?

  • 从库失效:追赶恢复
  • 从库可以从日志中知道,在发生故障之前处理的最后一个事务。因此,从库可以连接到主库,并请求在从库断开期间发生的所有数据变更。当应用完所有这些变化后,它就赶上了主库,并可以像以前一样继续接收数据变更流。
  • 主库失效:故障切换
  • 其中一个从库需要被提升为新的主库,需要重新配置客户端,以将它们的写操作发送给新的主库,其他从库需要开始拉取来自新主库的数据变更。

    故障切换可以手动进行(通知管理员主库挂了,并采取必要的步骤来创建新的主库)或自动进行。
    自动故障切换步骤

    1. 确认主库失效。一般通过超时判断
    2. 选择一个新主库。主库的最佳人选通常是拥有旧主库最新数据副本的从库(最小化数据损失)。
    3. 重新配置系统以启用新的主库。客户端现在需要将它们的写请求发送给新主库(请求路由的章节实现)。系统需要确保旧主库意识到新主库的存在,并成为一个从库。

    自动故障切换遇到的问题

  • 如果使用异步复制,则新主库可能没有收到老主库宕机前最后的写入操作。 在选出新主库后,如果老主库重新加入集群,新主库在此期间可能会收到冲突的写入,那这些写入该如何处理?常用的是直接丢弃老主库未复制的写入,这很可能打破客户对于数据持久性的期望。
  • 如果数据库需要和其他外部存储相协调,那么丢弃写入内容是极其危险的操作。
  • 两个节点都以为自己是主库的情况。
  • 这种情况称为 **脑裂 (split brain)**,如果两个主库都可以接受写操作,数据就可能丢失或损坏。 安全防范措施:当检测到两个主库节点同时存在时会关闭其中一个节点。

    这种机制称为 屏蔽(fencing),充满感情的术语是:爆彼之头

  • 主库被宣告死亡之前的正确超时应该怎么配置?
  • 在主库失效的情况下,超时时间越长,意味着恢复时间也越长。但是如果超时设置太短,又可能会出现不必要的故障切换。例如,临时负载峰值可能导致节点的响应时间超时,或网络故障可能导致数据包延迟。如果系统已经处于高负载或网络问题的困扰之中,那么不必要的故障切换可能会让情况变得更糟糕。

    节点故障、不可靠的网络、对副本一致性,持久性,可用性和延迟的权衡,这些问题实际上是分布式系统中的基本问题

    1.5 复制日志的实现

    1. 基于语句的复制
    在最简单的情况下,主库记录下它执行的每个写入请求(语句,即 statement)并将该语句日志发送给其从库

    遇到的问题:

  • 任何调用非确定性函数的语句,可能会在每个副本上生成不同的值。例如,使用 NOW() 获取当前日期时间,或使用 RAND() 获取一个随机数。
  • 如果语句使用了 自增列或者依赖于数据库中的现有数据(例如,UPDATE ... WHERE <某些条件>),则必须在每个副本上按照完全相同的顺序执行它们,这限制了并发性!
  • 基于语句的复制在 5.1 版本前的 MySQL 中使用。但要求事务必须是确定性的,以此来保证安全
  • 2. 逻辑日志复制(基于行)

  • 复制和存储逻辑解耦。向后兼容,方便异构数据。以行粒度进行操作。
  • 包含行前后值。记录因果关系,处理并发。
  • 3. 传输预写式日志(WAL)

  • 对于覆写单个磁盘块的 B 树,每次修改都会先写入 预写式日志(Write Ahead Log, WAL),以便崩溃后索引可以恢复到一个一致的状态。
  • 在任何一种情况下,日志都是包含所有数据库写入的仅追加字节序列。
  • 缺点:
    WAL 包含哪些磁盘块中的哪些字节发生了更改。
    机制:

  • 如果复制协议允许从库使用比主库更新的软件版本,则可以先升级从库,然后执行故障切换,使升级后的节点之一成为新的主库,从而执行数据库软件的零停机升级。
  • 如果复制协议不允许版本不匹配(传输 WAL 经常出现这种情况),则此类升级需要停机。
  • 4. 基于触发器的复制

  • 应用程序层实现
  • 开销大,比数据库的内置复制更容易出错,也有很多限制。然而由于其灵活性,仍然是很有用的。

  • 二、复制延迟问题

    问题:
    读多写少
    解决:
    创建很多从库,并将读请求分散到所有的从库上去
    优点:

  • 减小主库的负载
  • 并允许向最近的副本发送读请求(降低延迟)。 **缺点:**
  • 只适用异步复制!!!
  • 同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。
  • 不一致性——当应用程序从异步从库读取时,如果从库落后,它可能会看到过时的信息。
  • 最终一致性: 这种不一致只是一个暂时的状态 ,如果停止写入数据库并等待一段时间,从库最终会赶上并与主库保持一致。

    异步从库读取异常的例子

    2.0 读己之写一致性(读写一致性)

    保证用户自己的写输入已被正确保存,其他用户的更新可能稍等才会看到。

    基于领导者的复制系统中读写一致性的实现:

  • 读用户 可能已经修改过 的内容时,都从主库读; 例如:从主库读取用户自己的档案,在从库读取其他用户的档案。
  • 如果应用中的大部分内容都可能被用户编辑,那么大部分内容都必须从主库读取(扩容读基本失效)。 我们可以跟踪上次更新的时间,在上次更新后的一分钟内,从主库读。还可以监控从库的复制延迟,防止对任意比主库滞后超过一分钟的从库发出查询。
  • 客户端可以记住最近一次写入的时间戳,系统需要确保从库为该用户提供任何查询时,该时间戳前的变更都已经传播到了本从库中。如果当前从库不够新,则可以从另一个从库读,或者等待从库追赶上来。
  • 时间戳可以是逻辑时间戳(例如日志序列号)或实际系统时钟

  • 如果你的副本分布在多个数据中心(出于可用性目的与用户尽量在地理上接近),则会增加复杂性。任何需要由领导者提供服务的请求都必须路由到包含主库的数据中心。
  • 用户从多设备请求服务,这样导致记住用户上次更新时间戳的方法变得更加困难,元数据需要一个中心存储。
  • 如果副本分布在不同的数据中心,很难保证来自不同设备的连接会路由到同一数据中心。
  • 2.1 单调读

    异步从库读取第二个异常例子是,用户可能会遇到 时光倒流(写入。然后读一个延迟小的从库可以看到写入的内容,然后再读一个延迟更大的从库,写入内容不见了)

    单调读(Monotonic reads)保证这种异常不会发生。这是一个比 强一致性 更弱,但比 最终一致性 更强的保证。

    实现机制:是确保用户从同一个副本进行读取

    2.2 一致前缀读

    按照顺序写入进行顺序读取。

    违反因果律,先从一个从库查到了结果,又从一个从库查到了原因。

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

    2.3 复制延迟的解决方案

    1. 明明是异步复制却假设复制是同步的,这是很多麻烦的根源。

    2. 如果应用程序开发人员不必担心微妙的复制问题,并可以信赖他们的数据库 “做了正确的事情”,那该多好呀。这就是 **事务(**transaction) 存在的原因:数据库通过事务提供强大的保证,所以应用程序可以更加简单。

    3. 单节点事务已经存在了很长时间。然而在走向分布式(复制和分区)数据库时,许多系统放弃了事务**。声称事务在性能和可用性上的代价太高,并断言在可伸缩系统中最终一致性是不可避免的。

    三、多主复制

    如果数据库被分区,每个分区都有一个领导者。 不同的分区可能在不同的节点上有其领导者,但是每个分区必须有一个领导者节点。

    基于领导者的复制模型的自然延伸是允许多个节点接受写入,每个领导者同时扮演其他领导者的追随者。

    3.1 应用场景

    在单个数据中心内部使用多个主库没有太大意义。

    3.1.1 运维多个数据中心

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

    DDIA读书笔记 | 第五章:复制_第1张图片

  • 性能。
  • 单主配置中,写入时间可能会增长。在多主配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。

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

  • 容忍网络问题
  • 数据中心之间的通信通常穿过公共互联网,这可能不如数据中心内的本地网络可靠。单主配置对这数据中心间的连接问题非常敏感,因为通过这个连接进行的写操作是同步的。采用异步复制功能的多主配置通常能更好地承受网络问题:临时的网络中断并不会妨碍正在处理的写入。

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

    3.1.2 需要离线操作的客户端

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

    等价于单节点数据中心+极不可靠的网络。

    3.1.3 协同编辑

    3.2 处理写入冲突

    单主数据库中不会出现写入冲突的问题。
    多领导者复制的最大问题是可能发生写冲突。

    3.2.1 同步与异步冲突检测

    在多主配置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突
    单主程序复制可以实现同步冲突检测。

    3.2.2 避免冲突

    如果应用程序可以确保特定记录的所有写入都通过同一个领导者,那么冲突就不会发生。

    3.2.3 收敛至一致的状态

    每个复制方案都必须确保数据在所有副本中最终都是相同的。也即所有副本必须在所有变更复制完成时收敛至一个相同的最终值

    实现冲突合并解决有多种途径:

  • 给每个写入一个唯一的 ID,最高 ID 的写入作为胜利者,并丢弃其他写入。如果是时间戳,则称之为最后写入胜利(LWW, last write wins)
  • 为每个副本分配一个唯一的 ID,ID 编号更高的写入具有更高的优先级
  • 合并并发写入,依赖应用层(自动)、用户(手动)解决冲突。
  • 版本号标记操作的因果关系
  • 3.2.4 自定义冲突解决逻辑:

    大多数多主复制工具允许使用应用程序代码编写冲突解决逻辑

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

  • 读时执行
  • 所有冲突写入被存储,应用程序可能会提示用户或自动解决冲突

    3.3 多主复制拓扑

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

  • 环形拓扑
  • 有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。

  • 星形拓扑
  • 有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。

  • 全部到全部的拓扑
  • 容错性更好,但是写入顺序可能会不太一致。

    四、无主复制

    允许任何副本直接接受来自客户端的写入

    4.1 当节点故障时写入数据库

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

    当一个客户端从数据库中读取数据时,读请求也被并行地发送到多个节点。客户可能会从不同的节点获得不同的响应。版本号用于确定哪个值更新。

    4.1.1 读修复和反熵

    复制方案应确保最终将所有数据复制到每个副本。在一个不可用的节点重新联机之后,它如何赶上它错过的写入?

  • 读修复 读取到旧版本回写新版本。
  • 反熵 后台进程check更新数据版本。
  • 4.1.2 读写的法定人数

    究竟多少个副本完成才可以认为写成功?

    如果有 n 个副本,每个写入必须由 w 节点确认才能被认为是成功的,并且我们必须至少为每个读取查询 r 个节点。只要 w + r > n ,我们期望在读取时获得最新的值,因为 r 个读取中至少有一个节点是最新的。(假如:n=3,w=2,r=2)

    遵循这些 r 值,w 值的读写称为 法定人数的读和写

    4.1.2 法定人数一致性的局限性

  • 通常,r 和 w 被选为多数(超过 n/2 )节点,因为这确保了 w + r> n,同时仍然容忍多达 n/2 个节点故障。

  • 你也可以将 w 和 r 设置为较小的数字,以使 w + r ≤ n(即法定条件不满足)。在这种情况下,读取和写入操作仍将被发送到 n 个节点,但**操作成功只需要少量的成功响应**。
    1. 允许更低的延迟和更高的可用性。
    2. 当可达副本的数量低于 w 或 r 时,数据库才分别变得不可用于写入或读取。

    4.1.3 监控陈旧度

    4.2 宽松的法定人数与提示移交

  • 合理配置的法定人数可以使**数据库无需故障切换即可容忍个别节点的故障**。也可以**容忍个别节点变慢**,因为请求不必等待所有 n 个节点响应
  • 对于需要**高可用、低延时、且能够容忍偶尔读到陈旧值**的应用场景来说,这些特性使无主复制的数据库很有吸引力。

  • 总结

    一、复制可以用于几个目的:

  • 高可用性
  • 即使在一台机器(或多台机器,或整个数据中心)停机的情况下也能保持系统正常运行

  • 断开连接的操作
  • 允许应用程序在网络中断时继续工作

  • 延迟
  • 将数据放置在距离用户较近的地方,以便用户能够更快地与其交互

  • 可伸缩性
  • 通过在副本上读,能够处理比单机更大的读取量

    二、复制的三种主要方法

  • 单主复制
  • 客户端将所有写入操作发送到单个节点(领导者),该节点将数据更改事件流发送到其他副本(追随者)。读取可以在任何副本上执行,但从追随者读取可能是陈旧的。

  • 多主复制
  • 客户端发送每个写入到几个领导节点之一,其中任何一个都可以接受写入。领导者将数据更改事件流发送给彼此以及任何跟随者节点。

  • 无主复制
  • 客户端发送每个写入到几个节点,并从多个节点并行读取,以检测和纠正具有陈旧数据的节点。

    单主复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但以更难以推理并仅提供非常弱的一致性保证为代价。

    三、复制的同步与异步

    复制可以是同步的,也可以是异步的,这在发生故障时对系统行为有深远的影响。如果一个领导者失败了,并且你提升了一个异步更新的追随者成为新的领导者,那么最近提交的数据可能会丢失。

    四、复制的同步与异步

    有助于决定应用程序在复制滞后时的行为的一致性模型:

  • 写后读
  • 用户应该总是看到自己提交的数据。

  • 单调读
  • 用户在看到某一个时间点的数据后,他们不应该再看到某个更早时间点的数据。

  • 一致前缀读
  • 用户应该看到数据处于一种具有因果意义的状态:例如,按正确的顺序看到一个问题和对应的回答。

你可能感兴趣的:(DDIA读书总结,数据库,分布式,架构)