Hermes - Linearizable Replication

Hermes - 2020 年的论文.

是一个满足线性一致性的复制协议,即:保证读可以立即看到成功的写入。

replication是将数据放置在多个地方,从而可以提高数据可靠性,也提高系统的快速恢复能力,即提高可用性。

replication的方式可以由一共识(consensus)协议完成,即保证member将对replicated的数据保持一致的值,从而达到replication的效果。但是共识和replication是正交的概念。

Hermes协议提供了replication的能力,语义是:对于一个Key的Value,可保证线性一致性。

能力

  1. 多点写入 - 每个节点都可写入,都写入成功,写入被系统在多个副本上一致地串行化,最终是一个确定的值。比如,多个client写入a=2,a=3,a=4,最终每个副本上都会得到一个明确的(2,3,4)中的一个值。

2. 多点均可读,都读到系统最新写入的值 - 线性一致性语义。

实现

  1. 每次写同步失效所有副本
  2. 失效副本完成后,本地标记写完成,返回客户端。
  3. 读写发现失效状态,等待状态变为Valid后再继续
  4. 写冲突串行化,每个Key引入版本,每次写入对版本增加固定值。用NodeId的方式解版本相同情况下的冲突写。
  5. 写入点的失效处理。Invalid节点超时后变为coordinator发送Invalid给其它节点。MemberShipService将失效节点剔除,保证新成员都活跃,从而可以全部同步完成失效。
  6. MemberShipService 管理集群的member list,探测和剔除非活跃成员,加入新成员等操作。保证所有member是活跃的。

状态转换

Follower:

VALID --- INV----> INVALID (当timestamp大于本地, 否则状态不变)

INVALID --- VAL ----> VALID (follower收到coordinator的VALID,必须与自己记录的timestamp和cid一样)

INVALID --- INV ---> INVALD (当收到更大的timestamp+cid),此处应该仅会收到cid不同,timestamp应该一样

REPLAY --- INV --- TRANS (replay模式的节点收到了其它coordinator的INV,比自己大)

REPLAY --- RECV ACK --> VALID (收到了followers的ACKs)

Coordinator:

VALID --- SEND INV ----> WRITE (coordinator multicast INV给followers后)

WRITE --- RECV ACK ---> VALID (coordinator 收到 followers的所有ACK后)

WRITE --- INV ---> TRANS ( coordinator 收到了其它coordinator的INV,比自己大)

TRANS --- RECV ACK ---> INVALID (coordinator 收到自己INV对应的ACKs)

INVALID ---SEND INV -> REPLAY (MLT 时间后<message lost timeout> 假设ACK消息丢失,进入replay模式)

Reliable-Membership

与Majority的一致性协议不同,Reliable-MemberShip方式的复制,要求必须复制到所有成员,对于失效成员的处理,通过从memberShip中剔除来保证成员列表始终是活跃的。这种协议的友好之处是可以对每个副本进行一致性读取。此外对同时可用副本的数量也没有要求,因而相同副本数量下,容错性比 Majority类型的一致性协议的复制要高。比如3个副本可以损坏2个,依然可用。不好之处是需要同步写入所有副本,更容易受长尾效应影响。

此外,成员管理需要一个外置的MemberShip管理服务来进行成员管理。这个服务可以是一个通用服务,一些云上已经具备这样的公共服务。

成员管理

  1. MemberShip 使用Lease 进行更新。
  2. 每次成员变更,则产生新的view:使用新的 epoch_id。
  3. membership 更新过程中,未更新到新view的live 节点收到epoch_id的请求则忽略。
  4. 更新到新view的节点,相当于重新获得Lease,开始接收请求,可作为读写coordinator。
  5. 新节点加入,同样更新view,epoch_id 增加,在每个节点更新member过程中,同样遵循忽略来自epoch_id比自己大的请求。
view 更新过程中,写入将stall,直到view更新完全后才继续。
编辑于 04-12