分布式系统中的副本操作详解!基于Quorum机制实现副本的更新提交

343 阅读7分钟

这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战

基本约定

  • 更新操作write是一系列顺序的过程
  • 通过其余机制确定更新操作的顺序. 比如由primary-secondary架构中的primary决定顺序
  • 每次更新操作记为wi. 其中i为更新操作单调递增的序号
  • 每个wi执行成功后副本数据都会发生变化,称为不同的数据版本,记为vi
  • 假设每个副本都保存了所有历史版本的数据

write-all-read-one

  • write-all-read-one: WARO
    • 是一种最简单的副本控制规则
    • 在更新时写所有的副本,只有在所有的副本更新成功,才认为更新成功,从而保证所有的副本一致
    • 在读取数据时可以读取任一副本上的数据
  • 对于更新服务,尽管有N个副本,但系统无法容忍任何一个副本异常:
    • 由于更新操作需要在所有的N个副本上都成功,更新操作才能成功,所以一旦有一个副本异常,更新操作失败,更新服务不可用
  • 对于读服务 ,N个副本中只要有一个副本正常,系统就可以提供读服务:
    • 当有N个副本时,系统可以容忍N-1个副本异常
  • 由此可见 ,WARO的读服务的可用性较高,但是更新服务的可用性不高.尽管使用了副本,但是更新服务的可用性等效于没有副本

Quorum定义

  • Quorum机制下,当某次更新操作wi如果在所有N个副本中的W个副本上都成功,就认为该更新操作为 “成功提交的更新操作.” 对应的数据为 “成功提交的数据”
  • R > N-W, 由于更新操作wi仅在W个副本上成功,由于W+R > N, 任意R个副本组成的集合一定与成功的W个副本组成的集合有交集,所以读取R个副本一定能读取到wi更新后的数据vi
  • Quorum机制的可用性分析:
    • 限制Quorum参数为W+R=N+1
    • 对于更新服务,由于更新操作需要在W个副本上都成功,更新操作才能成功,所以如果N-W+1个副本异常,更新操作始终无法在W个副本上成功,更新服务不可用
    • 对于读服务,如果N-R+1个副本异常,那么无法保证一定可以读到与W个副本有交集的副本合集,因此读服务的一致性下降
  • 仅仅依赖Quorum机制是无法保证强一致性的:
    • 仅有Quorum机制时无法确定最新已成功提交的版本号
    • 除非将最新已提交的版本号作为元数据由特定的元数据服务器或者元数据集群管理,否则很难确定最新成功提交的版本号
  • Quorum机制的三个系统参数N,W,R控制了系统的可用性,也是系统对用户的服务承诺:
    • 数据最多有N个副本,但是数据更新成功W个副本即返回更新成功
    • 对于一致性较高的Quorum系统,系统还要承诺任何时候不读取未成功提交的数据,即读取到的数据都是曾经在W个副本上成功的数据

读取最新成功提交的数据

  • Quorum机制只需成功更新N个副本中的W个,在读取R个副本时,一定可以读到最新的成功提交的数据
  • 由于有不成功的更新情况存在,仅仅读取R个副本却不一定能确定是哪个版本的数据是最新已提交的数据
  • 对于一个强一致性Quorum系统:
    • 若成功读取的数据少于W个,假设为X个,则继续读取其余副本,直到成功读取到W个该版本的副本,则该数据为最新的成功提交的数据
    • 如果在所有副本中该数据的个数肯定不满足W个,则R中版本号第二大的为最新成功提交的副本
    • 示例:
      • 在读取到 (v2, v1, v1) 时,继续读取剩余的副本:
        • 若读取到剩余的两个副本为 (v2, v2), 那么v2是最新的成功提交的副本
        • 若读取到剩余的两个副本为 (v2, v1) 或者 (v1, v1), 那么v1是最新成功提交的版本
        • 若读取后续两个副本有任一超时或者失败,那么无法判断哪个版本是最新的成功提交的副本
  • 仅仅使用Quorum机制时,如果要确定最新的成功提交的版本,最多需要读取R+(W-R-1)=N个副本,当出现任一副本异常时,读最新的成功提交的版本这一功能都可能不可用
  • 在工程实践中,应该尽量避免通过Quorum机制读取最新的成功提交的版本,可以通过Quorum机制与primary-secondary控制协议结合使用,通过读取primary的方式读取到最新的已提交的副本

基于Quorum机制选择primary副本

  • 读取数据时依照一致性要求的不同可以有不同的做法:
    • 如果要求强一致性的立刻读取到最新的成功提交的数据:
      • 可以直接只读取primary副本上的数据
      • 也可以单纯使用Quorum机制读取最新的成功提交的版本数据
    • 如果要求会话一致性:
      • 可以根据之前已经读取的数据版本号在各个副本上进行选择性读取
    • 如果只需要弱一致性:
      • 可以选择任意副本读取
  • 在primary-secondary协议中:
    • primary异常时,需要选出一个新的primary, 之后secondary副本与primary同步数据
    • 通常情况下,选择新的primary的工作是由一个中心节点完成
  • 基于Quorum机制的primary-secondary协议:
    • 常用的primary选择方式与读取数据方式类似: 中心节点读取R个副本,选择R个副本中版本号最高的副本作为新的primary
    • primary与至少W个副本完成数据同步后作为新的primary提供读写服务:
      • R个副本中版本号最高的副本一定包含最新成功提交的数据
      • 虽然不能确定最高版本号的数是否为一个成功提交的数据,但是新的primary在随后与secondary同步数据,使得该版本的副本个数达到W, 从而使得该版本的数据成为成功提交的数据
  • 示例:
    • N=5, W=3, R=3的系统中,某时刻副本最大版本号为 (v2,v2, v1, v1, v1), 此时v1是系统的最新的成功提交的数据 ,v2是一个处于中间状态未成功提交的数据
    • 假设此刻primary副本异常,中心节点进行primary切换工作
    • 这类 “中间态” 数据是作为 “脏数据” 删除,还是作为新的数据被同步后成为生效的数据,完全取决于这个数据能否参与新的primary的选举:
      • 如果中心节点与其中3个副本通信成功,读取到的版本号为 (v1, v1, v1), 则任选一个副本作为primary, 新的primaryv1作为最新的成功提交的版本并与其余副本同步
        • 当与第1个和第2个副本同步数据时,由于第1个和第2个副本的版本号大于primary, 属于 脏数据, 处理脏数据,可以设计基于undo日志的方式删除脏数据
        • 工程实践中,新的primary也可能会与后面两个副本完成同步后就提供数据服务,随后自身版本号也更新到v2. 如果系统不能保证之后的v2与之前的v2完全一样,则新的primary在与第1个和第2个副本同步数据时不但要比较版本号,还需要比较更新操作的具体内容是否一样
      • 如果中心节点与其中3个副本通信成功,读取到的版本号为 (v2, v1, v1), 则选取版本号为v2的副本作为新的primary
      • 如果新的primary与其余2个副本完成数据同步,则符合v2的副本个数达到W个,成为最新的成功提交的副本,新的primary可以提供正常的读写服务