浅谈分布式一致性协议 | 青训营笔记

58 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第15天

1. 分布式系统

1.1 KV案例

从最简单机KV开始

接口:

  • Get(key)-value
  • BatchPut([k1,k2,...][v1,v2,...]

第一次实现

  • RPC
  • DB Engine

image.png

没有可靠性,有正确性

2. 一致性和共识算法

2.1 从复制开始

弄多一台机器image.png是否让两个副本都接受用户请求image.png

2.2 如何复制

主副本定期拷贝全量数据到从副本

主副本拷贝操作到从副本image.png

2.3 如何复制操作

主副本把所有的操作打包成Log

  • 所有的Log写入都是持久化的,保存在磁盘上

应用包装成状态机,只接收Log作为Input

主副本确认Log已经成功写入到副本机器上,当状态机apply后,返回客户端

image.png

2.4 如何读操作

读操作

  • 方案一:直接读状态机,要求写操作进入状态机后再返回client
  • 方案二:写操作复制完成后直接返回,读操作Block等待所有pending log进入状态机

如果不遵循上述两周方案呢?

  • 可能存在刚刚写入的值读不到的情况(在Log中)

image.png

2.5 什么是一致性

一致性是一种模型(或语义》

  • 来约定一个分布式系统如何向外界(应用)提供服务

  • KV中常见的一致性模型

    • 最终一致性:读取可能暂时读不到但是总会读到
    • 线性一致性:最严格,线性执行

一致性的分类

经常与应用本身有关

Linearizability是最理想的

image.png

2.6 复制协议-当失效发生

当主副本失效时,为了使得算法简单

  • 我们人肉切换,只要足够快

    • 我们还是可以保证较高的可用性。

但是如何保证主副本是真的失效了呢?

  • 在切换的过程中,主副本又开始接收client端的请求
  • 两个主副本显然是不正确的,og会被覆盖写掉
  • 我们希望算法能在这种场景下仍然保持正确

要是增加到三个节点呢?

  • 能不能允许少数节点挂了的情况下,仍然可以工作

    • falut-tolerance

2.7 共识算法

一个值一旦确定,所有人都认同

错误总是发生

  • Non-Byzantine fault

错误类型很多

  • 网络断开,分区,缓慢,重传,乱序
  • CPU、IO都机会停住

容错(falute--tolerance)

共识协议不等于一致性

  • 应用层面不同的一致性,都可以用共识协议来实现

    • 比如可以故意返回旧的值
  • 简单的复制协议也可以提供线性一致性

一般讨论共识协议时提到的一致性,都指线性一致性

  • 因为弱一致性往往可以使用相对简单的复制算法实现

3. 共识算法案例:Raft

3.1 Paxos

The Part-Time Parliamen by Lamport 1989

  • 也就是人们提到的Paxos
  • 基本上就是一致性协议的的同义词
  • 该算法的正确性是经过证明的

3.2 Raft

易于理解作为算法的设计目标

  • 使用了RSM、Log、RPC的概念
  • 直接使用RPC对算法进行了描述
  • Strong Leader-based
  • 使用了随机的方法减少约束

正确性

  • 形式化验证
  • 拥有大量成熟系统

3.3 复制状态机

RSM (replicated state machine)

  • Raft中所有的consensus都是直接使用Log作为载体

Commited Index

  • 一旦Raft更新Commited Index,意味着这个Index前的所有Log都可以提交给状态机了
  • Commited Index是不持久化的,状态机也是volatile的,重启后从第一条Log开始

image.png

3.4 Raft角色

image.png

3.5 Raft的整体流程

S2为leader,Client发送请求,s2将请求在raft中用log形式发送给其他副本,但多数副本完成复制并返回完成后提交给Client完成。

image.png

s2平时会定期发送心跳给其他副本告诉他们自己还活着,s2挂掉后,其他人会尝试成为leader,当一个副本收到多少副本的同意后就成为leaderimage.png

3.5 Raft日志复制

image.png

3.6 Raft从节点失效

image.png

3.7 Raft Term

  • 每个Leader服务于一个term
  • 每个term至多只有一个leader
  • 每个节点存储当前的term
  • 每个节点term从一开始,只增不减
  • 所有rpc的request reponse都携带term
  • 只commit本term内的log

image.png

3.8 Raft 主节点失效

  • Leader定期的发送AppendEntries RPCs给其余所有节点

  • 如果Follower有一段时间没有收到Leader的AppendEntries,则转换身份成为Candidate

  • Candidate自增自己的term,同时使用RequestVote RPCs向剩余节点请求投票

    • raft在检查是否可以投票时,会检查log是否outdated,至少不比本身l旧才会投给对应的Candidate
  • 如果多数派节点投给它,则成为该term的leader

3.9 Raft leader failure

image.png

3.10 Raft安全性---同Term

对于Term内的安全性

  • 目标:

    • 对于所有已经的commited的<term,index>位置上至多只有一条log

由于Raft的多数派选举,我们可以保证在一个term中只有一个leader

  • 我们可以证明一条更严格的声明:在任何<term,index:>位置上,至多只有一条log

3.11 Raft安全性---跨Term

对于跨Term的安全性

  • 目标:

    • 如果一个log被标记commited,那这个log一定会在未来所有的leader中出现Leader completeness

可以证明这个property

  • Raft选举时会检查Log的是否outdated,只有最新的才能当选Leader
  • 选举需要多数派投票,而commited log也已经在多数派中(必有overlap)
  • 新Leader一定持有commited log,且Leader永远不会overwrite log

3.12 Raft安全性验证

Raft使用TLA+进行了验证

  • 形式验证(Formal Method

以数学的形式对算法进行表达,由计算机程序对算法所有的状态进行遍历image.png