Quorum机制


基本约定


  • 更新操作​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,​ 新的​primary​以​v1​作为最新的成功提交的版本并与其余副本同步

  • 当与第​1​个和第​2​个副本同步数据时,由于第​1​个和第​2​个副本的版本号大于​primary,​ 属于 ​脏数据,​ 处理脏数据,可以设计基于​undo​日志的方式删除脏数据
  • 工程实践中,新的​primary​也可能会与后面两个副本完成同步后就提供数据服务,随后自身版本号也更新到​v2.​ 如果系统不能保证之后的​v2​与之前的​v2​完全一样,则新的​primary​在与第​1​个和第​2​个副本同步数据时不但要比较版本号,还需要比较更新操作的具体内容是否一样

  • 如果中心节点与其中​3​个副本通信成功,读取到的版本号为 ​(v2, v1, v1),​ 则选取版本号为​v2​的副本作为新的​primary
  • 如果新的​primary​与其余​2​个副本完成数据同步,则符合​v2​的副本个数达到​W​个,成为最新的成功提交的副本,新的​primary​可以提供正常的读写服务