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

116 阅读9分钟

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

1.分布式系统

①分布式系统面临的挑战

  • 数据规模越来越大
  • 服务的可用性要求越来越高
  • 快速迭代的业务要求系统足够易用

②理想中的分布式系统

  • 高性能:可扩展、低时延、高吞吐
  • 正确:一致性、易于理解
  • 可靠:容错、高可用

③从HDFS开始

Namenode管理元信息,用户端希望Namenode是一个类似于单机的易用系统,而Namenode还负责管理Datanodes,因此如何设计Namenode是一个重要的问题

④案例-KV

image.png

  • 从最简单的单机KV开始

  • 接口

    • Get(key) -> value
    • BatchPut([k1,k2,...],[v1,v2,...])
  • 第一次实现

    • RPC:一个抽象的远程过程调用,一般在工业中都会使用RPC来包装服务,使用RPC将网络抽象起来

    • DB Engine

    • 评估

      • 可靠性:容错?高可用?
      • 正确性:只有一个进程,所有操作顺序执行

⑤小结

  • 背景:数据规模的不断增加,我们需要大规模分布式系统
  • 维度:对于一个分布式系统,希望有哪些特征
  • 从KV入手,看看我们如何满足分布式系统的要求

2.一致性与共识算法

①从复制开始

  • 既然一台机器会挂,考虑增加一台相同的机器,如何同步副本的状态?

image.png

  • 如果两个副本都能接受请求

image.png 会很复杂,最好只有一个能接受请求

②如何复制

  • 主副本定期拷贝全量数据到从副本(还没拷贝完主副本就挂了怎么办,而且代价太高,不可取)

  • 主副本拷贝操作到从副本

③如何复制操作

  • 主副本把所有的操作打包成Log,再通过RPC把Log同步到Backup上

    • 所有的Log写入都是持久的,保存在磁盘上
  • 应用包装成状态机,只接收Log作为Input

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

④关于读操作

  • 读操作

    • 方案一:直接读状态机,要求写操作进入状态机后再返回Client
    • 方案二:写操作复制完成后直接返回,读操作Block等待所有pending log进入状态机
  • 如果不遵循上述两种方案

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

⑤什么是一致性

  • 对于我们的KV

    • 像操作一台机器一样,要读入最近写入的值
  • 一致性是一种模型(或语义)

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

    • KV中常见的一致性模型

      • 最终一致性:读取可能暂时读不到但是总会读到(读操作方案二)
      • 线性一致性:最严格,线性执行(读操作方案一)
  • 一致性的分类

    • 经常与应用本身有关
  • Linearizability是最理想的

⑥复制协议-当失效发生

当主副本失效

  • 手动切换

    • 容错?不,服务还是停了
    • 高可用?也许,取决于我们从发现到切换的过程有多快
    • 正确?操作只从一台机器上发起,所有操作返回前都已经复制到另一台机器了

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

  • 人肉切换,只要足够快,还是可以保证较高的可用性

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

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

要是增加到三个节点呢

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

⑦共识算法

  • 什么是共识算法

    • 简而言之一个值一旦确定,所有人都认同
  • 有文章证明是一个不可能的任务(FLP impossibility)

  • 错误总是发生:Non-Byzantine fault

  • 错误类型很多

    • 网络断开,分区,缓慢,重传,乱序
    • CPU\IO都机会停住
  • 容错(fault-tolerance)

共识协议不等于一致性

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

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

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

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

⑧小结

  • 使用副本的方式设计了KV
  • 了解了什么是一致性
  • 了解了什么是共识算法
  • 设计一个正确且容错的一致性是一个难题

3.共识算法案例:Raft

①Paxos

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

那么问题解决了吗

  • Paxos是出了名的难以理解
  • 算法整体是以比较抽象的形式描述,工程实现时需要做一些修改

②Raft

  • 2014年发表

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

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

    • 形式化验证
    • 拥有大量的成熟系统(KV,etcd,Cockroach DB)

③复制状态机(RSM)

  • RSM(replicated state machine)

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

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

④Raft角色

image.png

⑤Raft整体流程

有客户端时

image.png

无客户端时

image.png

  • leader定期向follower发送心跳

Leader失效时

image.png

⑥Raft日志复制

image.png

⑦Raft从节点失效

image.png

⑧Raft Term

  • 每个Leader服务于一个term
  • 每个term至多只有一个leader
  • 每个节点存储当前的term
  • 每个节点term从一开始,只增不减,如果有新的更高的term,那么会向其跟进
  • 所有RPC的request response都携带term
  • 只commit本term内的log

⑨Raft主节点失效

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

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

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

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

image.png

⑩Raft安全性

同Term

  • 对于Term内的安全性

    • 目标:对于所有已经commited的<term,index>位置上至多只有一条log
  • 由于Raft的多数派选举,我们可以保证在一个term中只有一个leader

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

跨Term

  • 对于跨Term的安全性

    • 目标:如果一个log被标记commited,那这个log一定会在未来所有的leader中出现Leader completeness
  • 可以证明这个property

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

Raft安全性验证

  • Raft使用TLA+进行了验证

    • 形式验证:以数学的形式对算法进行表达,由计算机程序对算法所有状态进行遍历

4.回到KV

①案例-KV

  • 利用Raft算法,重新打造我们的KV

回顾一下一致性读写的定义

  • 方案一

    • 写log被commit了,返回客户端成功
    • 读操作也写入一条log,状态机apply时返回client
    • 增加log量
  • 方案二

    • 写log被commit了,返回客户端成功
    • 读操作先等待所有的commited log apply,再读取状态机
    • 优化写时延
  • 方案三

    • 写Log被状态机apply,返回给client
    • 读操作直接读状态机
    • 优化读时延

问题

  • Raft不保证一直有一个leader

    • 只保证一个term至多有一个leader
    • 可能存在多个term的leader
    • KV3若突然断网,KV1和KV2之间会选举出一个新的leader,但是client仍然在和KV3交互,那么客户会拿到过时的数据
  • Split-brain

解决

  • 确定合法的Leadership

    • 方案一

      • 通过一轮Heartbeat确认Leadership(获取多数派的响应)
    • 方案二

      • 通过上一次Heartbeat时间来保证接下来的有段时间内follower不会timeout
      • 同时follower在这段时间内不会进行投票
      • 如果多数follower满足条件,那么在这段时间内保证不会有新的Leader产生
  • 结合实际情况选择方案二或方案三

    • 取决于raft的实现程度以及读写的情况

优化

  • 多个副本只有单个副本可以提供服务

    • 服务无法水平扩展
  • 增加更多Raft组

    • 如果操作跨Raft组(每个Raft组负责不同的key区间)

②回到共识算法

Raft:关于Log

  • 论文中就给出的方案,当过多的Log占用后,启动snapshot,替换掉Log
  • 如果对于持久化的状态机,如何快速产生Snapshot
  • 多组Raft的应用中,Log如何合流

关于configuration change

  • 论文中给出的joint-consecsus以及单一节点变更两种方案

  • 新增server时,不同term认为的server数不同,可能会出现两个leader

Raft是正确的,但是在工程世界呢?

  • 真实世界中不是所有的错误都是完美fail-stop的

  • cloudflare的case,etcd再partial netwoork下,outrage了6个小时

③共识算法的未来

  • 高性能

    • 数据中心网络100G,时延约为几个us

    • RDMA网卡以及programable switch的应用

    • 我们想要的是:us级别的共识,以及us级别的容错

  • 多节点提交

    • 节点跨地域,导致节点间的RTT很大

    • EPaxos

      • 使用了冲突图的方式来允许并行Commit
      • 不冲突的情况下1RTT提交时间
  • Raft Paxos相互移植

    • Raft有很多成熟的实现
    • 研究主要关注在Paxos上
    • 如何关联两种算法
  • 共识算法作为一个系统

    • 多数分布式系统都选择共识算法作为底座

    • 不同一致性协议有不同的特性

    • Virtual consensus in delos

      • 对外暴露一致性的log作为接口
      • 内部对于log可以选择不同的实现

image.png