Raft之状态机并行性

转载请附本文链接:https://blog.csdn.net/maxlovezyy/article/details/97612700

文中思想具有普适性,和Raft无关,提到Raft主要是考虑到目前Raft作为一致性协议的情况比较多,因而就更多的说一下如何和Raft结合。

一致性协议需要保证的两个特性

  1. 保证复制组状态机的一致性
  2. 保证线性一致性

对于上述两个性质的保证,paper中标准做法是:majority replicas的log是一样的,是leader-base的。对于所有commit的日志,都按序应用到状态机,这样做的话显然能满足上述特性1和2,因为按序应用,状态机之间一定一致(这就囊括了redo时的一致性,因为follower都等价于redo),另外也一定能满足线性一致性(redo的结果一定一致,那么就能保证在切主后状态一致,那么必定就是保证线性一致性的)。

上述的paper标准做法有一个问题,那就是串行化应用日志,这就导致实现上无论如何必定等价于单线程apply日志到状态机,当WAL不是瓶颈的时候,状态机的应用就可能成为瓶颈,所以需要探索并行apply的可能性。下面分析一下并行apply的可能性。

状态机分析

KV状态机

对于某一个key来说,其导致状态机变更的动作无非就是Put(Update、CASPut)、Delete。每一个动作都需要落到日志中,CASPut只是做CAS check的时候需要加lock,过了之后一样是落日志,之后的操作和Put没区别。因此,现在分析对于日志的处理即可。

对于一个KV状态机来说,所有pairs在逻辑上都是平坦的,并且相互之间是没有关系的(不同于树形等不同elements有关系的状态机)。由于不同的KV之间没有任何关系,则完全可以不同的key之间并行apply,相当于把WAL按照key进行了partition,各个状态机多个并发处理各个partition,每个partition串行化apply,因为key之间没有任何关系,所以这是满足特性1和2的。那么这里就有一问题,同一个key可以并行吗?不行。因为显然同一个key的日志是有happens-before这种因果关系的,必须按需应用才能保证一致性。不过对于KV状态机来说,有一个可以取巧的地方,那就是对于一批日志entries buffer,可以先在内存里对各个keys的entries去重,每个key只保留log最新的一个条目,之后将最新的这个条目应用到状态机即可。为什么这样做可以?前面不是说key之间有因果关系吗?是的,正是因为有这样的关系,才只保留最后一条,而不是中间的某一条。因为对于KV状态机来说,其同一个key的entries间的唯一关系就是后面的覆盖前面的结果,所以保留最后一条就满足了因果关系下的redo结果,即覆盖,所以说KV状态机既有因果关系,又有覆盖关系

注:如果有batch write之类的非单key操作,则batch需要满足atomic apply的特性,这是唯一可能需要注意的点,不过这对于并行apply的整体思路影响不大。对于这种简单的关联,通过2PL来解决原子性,通过等待所有batch涉及的keys apply之后再返回RPC来解决一致性即可

Table状态机

在上面KV状态机的分析过程中,提出了两个概念,这里为了下面的讨论说明一下。

  • 因果关系:一系列的行为中具有时序性。
  • 覆盖关系:时序上的后一个覆盖前一个。

对于KV状态机,有的只是DML操作,完全不需要类似于mysql中gap lock的东西去保证一致性,即各个entries只有对单个row对DML操作,row operations之间唯一存在的关系就是同一个key之间的因果关系,所以只需要保证相同key分到同一个partition中去串行化处理即可。而对于Table状态机,因为存在DDL操作,而DDL操作会影响不止一个row,所以这里需要额外注意一些问题,下面来分析一下。

一个系统的WAL能否并行化apply,能并行化到什么程度,完全取决于整个状态机的关系模型。KV状态机很简单,不同key之间没有关系,同一个key的operation满足后来覆盖的原则。而Table状态机,不仅有DML操作,还有DDL操作。DML操作的rows之间依然是没有关系的,而DDL操作则会影响面比较广,即影响整个关系。比如一个DDL操作删除一个列,和一个DML操作插入一行,如果DML在前DDL在后,则都会成功;而如果DDL在前DML在后,则DML会失败,这里有强的因果关系。再比如一个DDL操作创建一个列和一个DML操作插入一个列,在前在后有着明显的不同结果,这里有强的因果关系。那么同一个key的DML和DML之间呢?这里具有因果关系。为什么不像KV那样具有覆盖关系?因为比如两个DML,一个是插入一行的A列,一个是插入同一行的B列,他们也都是独立的操作,后一个无法取代前一个。不同key的DML操作呢?因为他们也是平坦存储的,没有任何依赖关系。那同一个列的DDL操作之间呢?如果实现上创建删除等操作是幂等的,则有因果关系和覆盖关系。那不同列的DDL操作之间呢?同理,没有关系。所以,通过上述可以分析得出如下关系模型:

  • DDL和DML:因果关系。

  • DDL和DDL:
    同一列,因果关系和覆盖关系。
    不同列,没关系。

  • DML和DML:
    同一key,因果关系。
    不同key,没关系。

类似于KV状态机,需要一个WAL entries buffer window,基于窗口由前到后,由旧到新的扫描各个entries分析其operations。
1)DML操作,按key分partition并行处理。一旦遇到DDL操作则停止收集DML操作,处理完DML操作后转入2)DDL操作。
2)DDL操作,按列key分partition并行处理。一旦遇到DML操作则停止收集DDL操作,处理完DDL操作后转入1)DML操作。

其实可以看出,DDL和DML互为对方的barrier。

注:不同于KV状态机,Table状态机不支持KV状态机取同一个key窗口内最新操作覆盖的方式。因为同一个key的操作没有覆盖关系。

Tree状态机(TODO)

这种状态机相对会复杂一点,因为节点间可能存在父子关系,所以行为的因果分析相对会复杂一些。以后有时间再说吧,反正现在我没用这样的状态机。

Applied id的维护

由于在窗口内并行化apply,所以WAL的apply可能出现不连续、离散的情况。对于支持幂等语义的状态机引擎,只需要记录当前最大非中断点作为apply点即可(非幂等的如果错误码比较全可以模仿幂等行为)。其他的状态机引擎则相对麻烦一点,但相较于得到的apply能力,某些场景还是很值得的,需要做一个trade-off。

下一篇Raft之WAL并行性我们会聊一下WAL的并行思路。

原文:https://blog.csdn.net/maxlovezyy/article/details/97612700

你可能感兴趣的:(分布式)