这是我参与「第四届青训营」笔记创作活动的第 10天。
一、分布式系统
1. 分布式系统面临的挑战
- 数据规模越来越大
- 服务的可用性要求越来越高
- 快速迭代的业务要求系统足够易用
2. 理想中的分布式系统
- 高性能:可拓展、低时延、高吞吐
- 正确:一致性、易于理解
- 可靠:容错、高可用
3.从HDFS开始
Datanode,Namenode,Client
4. 案例 - KV
-
从最简单机KV开始
-
接口:
- Get(key) -> value
- BatchPut([k1, k2, ...], [v1, v2, ...])
-
第一次实现
- RPC
- DB Engine
二、一致性与共识算法
1. 从复制开始
- 既然一台机器会挂
- 如果两个副本都能接受请求
2. 如何复制
- 主副本定期拷贝全量数据到从副本
- 主副本拷贝操作到从副本
3. 如何复制操作
- 主副本把所有的操作打包成Log
- 所有的Log写入都是持久化的,保存在磁盘上
- 应用包装成状态机,只接收Log作为Input
- 主副本确认Log已经成功写入到副本机器上,当状态机apply后,返回客户端
4. 关于读操作
-
方案一:直接读状态机,要求写操作进入状态机后再返回 client
-
方案二:写操作复制完成后直接返回,读操作 Block 等待所有 pending log 进入状态机
-
如果不遵循上述两周方案呢?
- 可能存在刚刚写入的值读不到的情况(在 Log 中)
5. 什么是一致性
-
对于我们的 KV
-
像操作─台机器─样
- 要读到最近写入的值
-
-
一致性是一种模型(或语义)
-
来约定一个分布式系统如何向外界(应用)提供服务
-
KV中常见的一致性模型
- 最终一致性:读取可能暂时读不到但是总会读到
- 线性一致性:最严格,线性执行
-
-
一致性的分类
- 经常与应用本身有关
-
Linearizability 是最理想的
6. 复制协议
6.1 当失效发生
当主副本失效
-
手动切换
-
容错?
- 不,我们的服务还是停了
-
高可用?
- 也许,取决于我们从发现到切换的过程的有多快
-
正确?
- 操作只从一台机器上发起
- 所有操作返回前都已经复制到另一台机器了
6.2 小结
-
当主副本失效时,为了使得算法简单
-
我们人肉切换,只要足够快
- 我们还是可以保证较高的可用性。
-
-
但是如何保证主副本是真的失效了呢?
- 在切换的过程中,主副本又开始接收client端的请求
- 两个主副本显然是不正确的,log会被覆盖写掉
- 我们希望算法能在这种场景下仍然保持正确
-
要是增加到三个节点呢?
-
每次都等其他节点操落盘性能较差
-
能不能允许少数节点挂了的情况下,仍然可以工作
- falut-tolerance
-
7. 共识算法
-
共识协议不等于一致性
-
应用层面不同的一致性,都可以用共识协议来实现
- 比如可以故意返回旧的值
-
简单的复制协议也可以提供线性一致性
-
-
一般讨论共识协议时提到的一致性,都指线性一致性
- 因为弱一致性往往可以使用相对简单的复制算法实现
三、一致性协议案例
1. Paxos
-
The Part-Time Parliamen by Lamport 1989
- 也就是人们提到的Paxos
- 基本上就是一致性协议的的同义词
- 该算法的正确性是经过证明的
那么问题解决了吗?
-
Paxos是出了名的难以理解,Lamport本人在01年又写了一篇Paxos Made Simple
- "The Paxos Algorighm, when presented in plain English, is very simple."
-
算法整体是以比较抽象的形式描述,工程实现时需要做一些修改
-
Google在实现Chubby的时候是这样描述的
- here are significant gaps between the description of the Paxos algorithm and the needs of a real-world system.... the final system will be based on an unproven protocol .
2. Raft
-
2014年发表
-
易于理解作为算法的设计目标
- 使用了RSM、Log、RPC的概念
- 直接使用RPC对算法进行了描述
- Strong Leader-based
- 使用了随机的方法减少约束
-
正确性
- 形式化验证
- 拥有大量成熟系统
2.1 角色
2.2 整体流程
2.3 日志复制
2.4 从节点失效
2.5 Term
- 每个 Leader 服务于一个 term
- 每个 term 至多只有一个 leader
- 每个节点存储当前的 term
- 每个节点 term 从一开始,只增不减
- 所有 rpc 的 request reponse 都携带 term
- 只 commit 本 term 内的 log
2.6 主节点失效
-
Leader 定期的发送 AppendEntries RPCs 给其余所有节点
-
如果 Follower 有一段时间没有收到 Leader的 AppendEntries, 则转换身份成为 Candidate
-
Candidate 自增自己的 term,同时使用 RequestVote RPCs 向剩余节点请求投票
- raft 在检查是否可以投票时,会检查 log 是否 outdated, 至少不比本身旧才会投给对应的 Candidate
-
如果多数派节点投给它,则成为该 term 的 leader
2.7 Leader failure
2.8 安全性
2.8.1 同 Term
-
对于 Term 内的安全性
-
目标:
- 对于所有已经的 commited 的 <term,index> 位置上至多只有一条 log
-
-
由于 Raft 的多数派选举,我们可以保证在一个 term 中只有一个 leader
- 我们可以证明一条更严格的声明:在任何 <term,index> 位置上,至多只有一条 log
2.8.2 跨 Term
-
对于跨 Term 的安全性
-
目标:
- 如果一个log被标记 commited,那这个log一定会在未来所有的leader中出现 Leader completeness
-
-
可以证明这个 property
- Raft 选举时会检查 Log 的是否 outdated, 只有最新的才能当选 Leader
- 选举需要多数派投票,而 commited log 也已经在多数派中(必有overlap)
- 新 Leader 一定持有 commited log, 且 Leader 永远不会 overwrite log
3. 复制状态机(RSM)
-
RSM (replicated state machine)
- Raft中所有的consensus都是直接使用Log作为载体
-
Commited Index
- 一旦Raft更新Commited Index,意味着这个Index前的所有Log都可以提交给状态机了
- Commited Index是不持久化的,状态机也是volatile的,重启后从第一条Log开始
四、从Raft 入手
-
易于理解作为算法的设计目标
- 使用了RSM、Log、RPC的概念
- 直接使用RPC 对算法进行了描述
- Strong Leader-based
- 使用了随机的方法减少约束
-
正确性
- 形式化验证
- 拥有大量成熟系统
1.RSM(复制状态机)
状态机(State Machine): 一种编程架构,状态机只取决于当前的状态与的输入,确定下一个状态。
RSM (replicated state machine) : Raft中所有的consensus都是直接使用Log 作为载体。
Commited Index : 一旦Raft更新Commited Index,意味着这个Index前的所有Log都可以提交给状态机了,Commited Index是不持久化的,状态机也是 volatile 的,重启后从第一条Log开始。
2.Raft角色
- Follower
- Candidate
- Leader
3.Raft日志复制
1.正常情况下复制
2.从节点失效
4.Raft Trem
(1) 每个Leader服务于一个term
(2) 每个term至多只有一个leader
(3) 每个节点存储当前的term
(4) 每个节点term从一开始,只增不减
(5) 所有rpc的 request reponse都携带term
(6) 只commit本 term内的 log
5.Raft主节点失效
(1) Leader定期的发送AppendEntries RPCs给其余所有节点。
(2) 如果Follower有一段时间没有收到Leader 的 AppendEntries,则转换身份成为Candidate。
(3) Candidate自增自己的term,同时使用RequestVote RPCs向剩余节点请求投票,raft在检查是否可以投票时,会检查log是否outdated,至少不比本身旧才会投给对应的Candidate。
(4) 如果多数派节点投给它,则成为该term的leader。
6.Raft安全性
1.同Term
对于Term内的安全性目标: 对于所有已经的commited的<term, index>位置上至多只有一条 log。
由于Raft的多数派选举,我们可以保证在一个term中只有一个 leader,我们可以证明一条更严格的声明:在任何<term,index>位置上,至多只有一条log。
2.跨Term
对于跨Term的安全性目标: 如果一个log被标记commited,那这个log一定会在未来所有的 leader中出现Leader completeness。
可以证明这个property
- Raft选举时会检查Log的是否outdated,只有最新的才能当选Leader
- 选举需要多数派投票,而commited log 也已经在多数派中(必有overlap)
- 新Leader 一定持有commited log,且 Leader永远不会overwrite log
3.安全验证
Raft使用TLA+进行了验证。
形式验证(Formal Method):以数学的形式对算法进行表达,由计算机程序对算法所有的状态进行遍历。
五、实现细节以及未来
1.KV
用Raft算法,重新打造KV。
一致性读写的定义
- 方案一:写log被commit 了,返回客户端成功,读操作也写入一条log,状态机 apply时返回client,增加了Log量。
- 方案二:写log被commit 了,返回客户端成功,读操作先等待所有commited log apply,再读取状态机,优化了写时延。
- 方案三:写Log被状态机 apply,返回给client,读操作直接读状态机,优化了读时延。
46
Raft 不保证一直有一个leader,只保证一个term至多有一个leader可能存在多个term的leader。
确定合法的Leadership
方案—:通过一轮 Heartbeat 确认Leadership(获取多数派的响应)。
方案二:通过上一次Heartbeat时间来保证接下来的有段时间内follower不会timeout,同时follower在这段时间内不进行投票,如果多数follower满足条件,那么在这段时间内则保证不会有新的Leader产生。
结合实际情况选择方案二或方案三:取决于raft的实现程度以及读写的情况。
- 多个副本只有单个副本可以提供服务:服务无法水平拓展;
- 增加更多Raft组:如果操作跨Raft组。
2.共识算法
Raft :关于Log
- 论文中就给出的方案,当过多的Log占用后,启动snapshot,替换掉Log
- 如果对于持久化的状态机,如何快速的产生Snapshot
- 多组 Raft的应用中,Log如何合流
关于configuration change:论文中给出的joint-consensus以及单一节点变更两种方案
多节点提交(Leaderless)
- 节点跨地域,导致节点间的RTT(Round Trip Time)很大
- EPaxos[1]
- 使用了冲突图的方式来允许并行Commit
- 不冲突的情况下RTT提交时间
3.共识算法的未来
(1)Raft Paxos相互移植
- Raft有很多成熟的实现
- 研究主要关注在Paxos 上
- 关联两种算法
(2)共识算法作为一个系统
- 多数分布式系统都选择共识算法作为底座
- 不同一致性协议有不同的特性
- Virtual consensus in delos:对外暴露一致性的LOG作为借口,内部对于LOG可以选择不同的实现