持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
1. CAP 定理
要求
-
理解 CAP 定理
-
知道常见的一致性级别
CAP 定理
- Consistency 一致性:访问分布式系统中任意节点,总能返回一致的结果
* Every read receives the most recent write or an error
- Availability 可用性:分布式系统总能向客户端返回响应
* Every request receives a (non-error) response, without the guarantee that it contains the most recent write
- Partition tolerance 分区容忍:当分布式系统节点间通信发生了消息丢失或消息延迟,仍然允许系统继续运行
* The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network between nodes
CAP 定理:最多三选二,无法兼得,通常在 CP 或者 AP 之间做出选择
不一致的产生
- client 向 Node1 写入新值 v1
- 写入成功 Node1 更新成 v1
- Node1 在没有将变更同步到 Node2 时,就向客户端返回了应答
- client 发起向 Node2 的读操作
- 返回了旧值 v0(不一致)的结果
保证一致性
- client 向 Node1 写入新值 v1
- 写入成功 Node1 更新成 v1,此时不能立刻向 client 返回应答,而是需要将 v1 同步到 Node2
- 同步 v1 成功
- 此时才能向 client 返回应答
- 如果此时 client 再去访问 Node2,不会出现不一致的情况
保 CP 失 A
- 当发生了网络分区,Node1 与 Node2 已经失去了联系,这时仍想对外提供服务(保 P)
- client 向 Node1 写入新值 v1
- 写入 Node1 成功,但无法同步至 Node2
- 这时为了保证一致性,Node1 不能向 client 返回应答,造成操作挂起无法完成(失去可用性)
保 AP 失 C
- 当发生了网络分区,Node1 与 Node2 已经失去了联系,这时仍想对外提供服务(保 P)
- client 向 Node1 写入新值 v1
- 写入 Node1 成功,但无法同步至 Node2
- 为了保证可用性,向 client 返回了应答(但牺牲了一致性)
一致性级别
CP 和 AP 之间需要做权衡,其实根据需求不同,也可以将一致性划分成几个级别,在这些级别里做一个权衡。
- 强一致性:系统写入什么,读出来的也会是什么,但实现起来往往对性能影响较大,例如之前 CP 的例子
* 例如:火车站售票,有就是有,没有就是没有,不能出现不一致的情况
* 典型算法:Paxos、Raft、ZAB
- 弱一致性:系统写入成功后,不承诺立刻可以读到写入的值,也不承诺具体多久后数据能达到一致,还可以细分为:
* 会话一致性,同一个客户端会话中可以保证一致,其它会话不能保证
* 用户一致性,同一个用户中可以保证一致,其它用户不能保证
* 例如:网上购物,在商品详情页看到库存量还有好多,下单的瞬间才被提示“库存量不足”,实际上商品详情页展示的库存并不是最新的数据,只是在某个流程上才会做准确的检查
- 最终一致性:是弱一致性的特例,保证在一定时间内,能够达到一个一致的状态
* 例如:转账,转账完成后,会有一个提示,您的转账会在 24 小时内到账,一般用户也能接受,但最终必须是一致的
* 典型协议:Gossip
2. Paxos 算法
要求
-
理解 Paxos 产生背景
-
掌握 Basic Paxos 算法
问题提出
- 集群中有 N 个节点,如果一个节点写入后要求同步到剩余 N-1 个节点后再向客户端返回 ok,虽然看起来最保险,但其中任意一个节点同步失败,势必造成整个集群不可用,能否在此基础上稍微提高可用性呢?
-
答案是 ** (写)多数派**,集群节点设置为奇数,同步超过集群中 N/2 个节点成功,则向客户端返回 ok,但存在顺序性问题,如 3 描述
-
多数派写操作成功后的读一致性暂不考虑,思考下图中的两项操作,都满足了多数派通过,但 S3 这台服务器并没有与 S1,S2 达成一致,要达到多数派内部一致性
Paxos
Paxos 是一种共识算法,目的是解决之前提到的写多数派时的顺序性问题
Paxos 角色划分:集群中的每个节点都可以充当
- Proposer:负责生成提案
* 注意:Paxos 算法允许有多个 Proposer 同时提案,但可能会引起活锁问题
- Acceptor:负责批准提案
* Acceptor 如果只有一个的话,存在单点问题,因此应当有多个
- Learner:负责获取提案,Acceptor 批准提案后,会将提案发送给所有 Learner
执行一个修改操作,不是一上来就能执行,分成两个阶段:
-
准备阶段:Proposer负责接收 client 请求并产生提案,必须由多数派 Acceptor 批准通过提案
-
接受阶段:提案通过后,再将要执行的修改操作广播给 Acceptor,这次仍然多数派通过,此修改才能生效,可以返回响应给客户端
算法要点:
- 整个算法分成两个阶段:预备阶段,前两个箭头,接受阶段,后两个箭头。
* 预备阶段的目的是:第一拦截掉旧的提案,第二找到最新的 acceptValue
- 对于 Proposer
* 预备阶段只发送提案号,接受阶段发送提案号 + 值
* 提案号 n 唯一且全局递增,大的提案号有更高优先级
* 如果见到最新已接受值,就会替换掉 Proposer 自己原来的值,保证一致性
- 对于 Acceptor 会持久化以下信息
* minN(最小提案号),会在预备阶段和接受阶段被更新为更大提案号,会用来决定 Proposer 是否能选中提案
* acceptN(已接受提案号)和 acceptValue(已接受值),会在接受阶段被更新,如果 minN > n 则不会更新
例1
-
P 广播提案号 1
-
有 3 个 A 接到提案,此时满足 n > minN,将 minN 更新为 1
-
3个 A 成功返回,P 收到的应答过半,但没有遇到更大的 acceptNo 和 acceptValue,因此使用自己的 value X
-
P 广播提案号和值 1:X
-
3 个 A 接到提案号和值,更新状态,返回 minN 值 1 给 P
-
P 收到过半应答,并检查发现没有出现 minN > 1,便选中提案值 X
例2
-
S1 广播提案号 1,想把值更新为 X
-
S5 广播提案号 2,想把值更新为 Y
-
S1、S2、S3 已经经历了 Accept 阶段并选中值 X
-
关键点,S3 也接到了 S5 的prepare 提案,这时是否会有不一致的情况呢?
-
此时 S3 状态已将 acceptN 和 acceptValue 分别更新为 1:X;再返回 S5 的 ack 时就会将 1:X 返回给 S5
-
S5 用返回的 X 替换掉了自己原有的值 Y,并执行后续流程,后续都会同步为 X
例3
-
S1 广播提案号 1,想把值更新为 X
-
S5 广播提案号 2,想把值更新为 Y
-
S1、S2、S3 已经经历了 Accept 阶段,与例2 不同的是,值 X 还未选中
-
关键点,S3 也接到了 S5 的prepare 提案,这时是否会有不一致的情况呢?
-
此时 S3 状态将 acceptN 和 acceptValue 分别更新为 1:X;再返回 S5 的 ack 时就会将 1:X 返回给 S5
-
S5 用返回的 X 替换掉了自己原有的值 Y,并执行后续流程,后续都会同步为 X
例4
-
S1 广播提案号 1,想把值更新为 X
-
S5 广播提案号 2,想把值更新为 Y
-
关键点,S3 还未经历 Accept 阶段时,就拿到了 S5 的 prepare 提案,这时是否会有不一致的情况呢?
-
S3 在接到 S1 的 accept 请求时,n>=minN 条件不成立,因此没有更新 acceptN 和 acceptValue,并且返回的 minN 是 2
-
对 S1 来说,S3 返回的 minN 大于 n,选中失败;想更新 X 需要发起新一轮提案
-
对 S5 来说,accept 阶段发送的是它自己的 2:Y,后续会把值同步为 Y
例5
回顾最早提到的顺序性问题,看 Paxos 能否解决它
下图演示了 Paxos 是如何解决顺序性问题的,分析步骤参考例3
Paxos 缺点
-
效率较低,两轮操作只能选中一个值
-
难于理解
-
活锁问题
- Paxos 是允许多个 Proposer 的,因此如果按上图所示运行,则后一个提案总会让前面提案选中失败,显然死循环
****** **参考资料 ****
- **www.youtube.com/watch?v=JEp… Raft 作者讲解 Paxos
3. Raft 算法
要求
- 理解 Raft 算法
Raft 算法
另一种共识算法,目的是比 Paxos 更易理解
整个 Raft 算法分解为三部分:
- Leader 选举
① 只有一个 Server 能作为 Leader
② 一旦此 Server 崩溃,选举新 Leader
- 执行操作,以日志复制为例(Log replication)
① 由 Leader 执行自己的日志记录
② 将日志复制到其它 Server,会覆盖掉不一致的部分
③ 多数派记录日志成功,Leader 才能执行命令,向客户端返回结果
- 确保安全
① 保证日志记录的一致性
② 拥有最新日志的 Server 才能成为 Leader
Leader 选举
-
Leader 会不断向选民发送 AppendEntries 请求,证明自己活着
-
选民收到 Leader AppendEntries 请求后会重置自己的 timeout 时间
- 选民收不到 AppendEntries 请求超时后,转换角色为候选者,并将任期加1,发送 RequestVote 请求,推选自己
- 选民收到第一个 RequestVote,会向该候选者投一票,这样即使有多个候选者,必定会选出一个 Leader,选票过半即当选,如果落选会变回选民
-
每一任期最多有一个 Leader,但也可能没有(选票都不过半的情况,需要再进行一轮投票,timeout 在 T~2T 间随机)
-
任期由各个 server 自己维护即可,无需全局维护,在超时后加1,在接收到任意消息时更新为更新的任期,遇到更旧的任期,视为错误
**执行操作(以日志复制为例) **
-
客户端发送命令至 Leader
-
Leader 将命令写入日志(S1虚框),并向所有选民发送 AppendEntries 请求
- 多数派通过后,执行命令(即提交,S1虚框变实),此时就可以向客户端返回结果
- 在后续的 AppendEntries 请求中通知选民,选民执行命令(即提交,S2,S3,S4,S5虚框变实)
- 如果选民挂了,则 Leader 会不断尝试,待到选民重启,会将其缺失的日志陆续补齐
确保安全
Leader 日志的完整性
-
Leader 被认为拥有最完整的日志
-
一旦 Leader 完成了某条命令提交,那么未来的 Leader 也必须存有该条命令提交信息
-
投票时,会将候选者最新的
<Term,Index>随 RequestVote 请求发送,如果候选者的日志还没选民的新,则投否决票
-
图中 S2 如果超时,发起选举请求,其它服务器只会对它投否决票,因为它的 Index 比其它人都旧
-
图中 S5 如果超时,发起选举请求,其它服务器也不会选它,因为他的 Term 太旧
选民日志的一致性
-
以 Leader 为准,对选民的日志进行补充或覆盖
-
AppendEntries 请求发送时会携带最新的
<Term,Index,Command>以及上一个的<Term,Index> -
如果选民发现上一个的
<Term,Index>能够对应上则成功,否则失败,继续携带更早的信息进行比对
- 图中 Leader 发送了
<3,4,Command>和<2,3>给 follower,follower 发现<2,3>能够与当前最新日志对应,这时直接执行<3,4,Command>即可
- 图中 Leader 发送了
<3,4,Command>和<2,3>给 follower,follower 发现<2,3>不能与当前最新日志对应,会央求 Leader 发送更早日志
- Leader 这次发送了
<3,4,Command>,<2,3,Command>,<1,2>给 follower,follower 发现<1,2>能够与当前最新日志对应,这时补全<3,4,Command>,<2,3,Command>即可
****** **参考资料 ****
- **www.youtube.com/watch?v=vYp… Raft 作者讲解 Raft
> * raft.github.io/ Raft 资源
- **raft.github.io/raftscope/i… Raft 可视化