07_1、【Java面试-分布式篇】(上)

95 阅读11分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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 之间做出选择

不一致的产生

  1. client 向 Node1 写入新值 v1

image-20210902144323586.png

  1. 写入成功 Node1 更新成 v1

image-20210902144332846.png

  1. Node1 在没有将变更同步到 Node2 时,就向客户端返回了应答

image-20210902144346469.png

  1. client 发起向 Node2 的读操作

image-20210902144357711.png

  1. 返回了旧值 v0(不一致)的结果

image-20210902144405372.png

保证一致性

  1. client 向 Node1 写入新值 v1

image-20210902144821346.png

  1. 写入成功 Node1 更新成 v1,此时不能立刻向 client 返回应答,而是需要将 v1 同步到 Node2

image-20210902144917135.png

  1. 同步 v1 成功

image-20210902145113734.png

  1. 此时才能向 client 返回应答

image-20210902145138926.png

  1. 如果此时 client 再去访问 Node2,不会出现不一致的情况

保 CP 失 A

  1. 当发生了网络分区,Node1 与 Node2 已经失去了联系,这时仍想对外提供服务(保 P)

image-20210902145433075.png

  1. client 向 Node1 写入新值 v1

image-20210902145513466.png

  1. 写入 Node1 成功,但无法同步至 Node2

image-20210902145616905.png

  1. 这时为了保证一致性,Node1 不能向 client 返回应答,造成操作挂起无法完成(失去可用性)

保 AP 失 C

  1. 当发生了网络分区,Node1 与 Node2 已经失去了联系,这时仍想对外提供服务(保 P)

image-20210902145433075.png

  1. client 向 Node1 写入新值 v1

image-20210902145513466.png

  1. 写入 Node1 成功,但无法同步至 Node2

image-20210902145616905.png

  1. 为了保证可用性,向 client 返回了应答(但牺牲了一致性)

image-20210902150437928.png

一致性级别

CP 和 AP 之间需要做权衡,其实根据需求不同,也可以将一致性划分成几个级别,在这些级别里做一个权衡。

  • 强一致性:系统写入什么,读出来的也会是什么,但实现起来往往对性能影响较大,例如之前 CP 的例子

  * 例如:火车站售票,有就是有,没有就是没有,不能出现不一致的情况

  * 典型算法:Paxos、Raft、ZAB

  • 弱一致性:系统写入成功后,不承诺立刻可以读到写入的值,也不承诺具体多久后数据能达到一致,还可以细分为:

  * 会话一致性,同一个客户端会话中可以保证一致,其它会话不能保证

  * 用户一致性,同一个用户中可以保证一致,其它用户不能保证

  * 例如:网上购物,在商品详情页看到库存量还有好多,下单的瞬间才被提示“库存量不足”,实际上商品详情页展示的库存并不是最新的数据,只是在某个流程上才会做准确的检查

  • 最终一致性:是弱一致性的特例,保证在一定时间内,能够达到一个一致的状态

  * 例如:转账,转账完成后,会有一个提示,您的转账会在 24 小时内到账,一般用户也能接受,但最终必须是一致的

  * 典型协议:Gossip

2. Paxos 算法

要求

  • 理解 Paxos 产生背景

  • 掌握 Basic Paxos 算法

问题提出

  1. 集群中有 N 个节点,如果一个节点写入后要求同步到剩余 N-1 个节点后再向客户端返回 ok,虽然看起来最保险,但其中任意一个节点同步失败,势必造成整个集群不可用,能否在此基础上稍微提高可用性呢?

image-20210902150851559.png

  1. 答案是 ** (写)多数派**,集群节点设置为奇数,同步超过集群中 N/2 个节点成功,则向客户端返回 ok,但存在顺序性问题,如 3 描述

  2. 多数派写操作成功后的读一致性暂不考虑,思考下图中的两项操作,都满足了多数派通过,但 S3 这台服务器并没有与 S1,S2 达成一致,要达到多数派内部一致性

image-20210902151020777.png

Paxos

Paxos 是一种共识算法,目的是解决之前提到的写多数派时的顺序性问题

Paxos 角色划分:集群中的每个节点都可以充当

  1. Proposer:负责生成提案

   * 注意:Paxos 算法允许有多个 Proposer 同时提案,但可能会引起活锁问题

  1. Acceptor:负责批准提案

   * Acceptor 如果只有一个的话,存在单点问题,因此应当有多个

  1. Learner:负责获取提案,Acceptor 批准提案后,会将提案发送给所有 Learner

执行一个修改操作,不是一上来就能执行,分成两个阶段:

  1. 准备阶段:Proposer负责接收 client 请求并产生提案,必须由多数派 Acceptor 批准通过提案

  2. 接受阶段:提案通过后,再将要执行的修改操作广播给 Acceptor,这次仍然多数派通过,此修改才能生效,可以返回响应给客户端

image-20210902151606775.png

算法要点:

  • 整个算法分成两个阶段:预备阶段,前两个箭头,接受阶段,后两个箭头。

  * 预备阶段的目的是:第一拦截掉旧的提案,第二找到最新的 acceptValue

  • 对于 Proposer

  * 预备阶段只发送提案号,接受阶段发送提案号 + 值

  * 提案号 n 唯一且全局递增,大的提案号有更高优先级

  * 如果见到最新已接受值,就会替换掉 Proposer 自己原来的值,保证一致性

  • 对于 Acceptor 会持久化以下信息

  * minN(最小提案号),会在预备阶段和接受阶段被更新为更大提案号,会用来决定 Proposer 是否能选中提案

  * acceptN(已接受提案号)和 acceptValue(已接受值),会在接受阶段被更新,如果 minN > n 则不会更新

例1

image-20210902152006734.png

  1. P 广播提案号 1

  2. 有 3 个 A 接到提案,此时满足 n > minN,将 minN 更新为 1

  3. 3个 A 成功返回,P 收到的应答过半,但没有遇到更大的 acceptNo 和 acceptValue,因此使用自己的 value X

  4. P 广播提案号和值 1:X

  5. 3 个 A 接到提案号和值,更新状态,返回 minN 值 1 给 P

  6. P 收到过半应答,并检查发现没有出现 minN > 1,便选中提案值 X

例2

image-20210902152121752.png

  1. S1 广播提案号 1,想把值更新为 X

  2. S5 广播提案号 2,想把值更新为 Y

  3. S1、S2、S3 已经经历了 Accept 阶段并选中值 X

  4. 关键点,S3 也接到了 S5 的prepare 提案,这时是否会有不一致的情况呢?

  5. 此时 S3 状态已将 acceptN 和 acceptValue 分别更新为 1:X;再返回 S5 的 ack 时就会将 1:X 返回给 S5

  6. S5 用返回的 X 替换掉了自己原有的值 Y,并执行后续流程,后续都会同步为 X

例3

image-20210902152345674.png

  1. S1 广播提案号 1,想把值更新为 X

  2. S5 广播提案号 2,想把值更新为 Y

  3. S1、S2、S3 已经经历了 Accept 阶段,与例2 不同的是,值 X 还未选中

  4. 关键点,S3 也接到了 S5 的prepare 提案,这时是否会有不一致的情况呢?

  5. 此时 S3 状态将 acceptN 和 acceptValue 分别更新为 1:X;再返回 S5 的 ack 时就会将 1:X 返回给 S5

  6. S5 用返回的 X 替换掉了自己原有的值 Y,并执行后续流程,后续都会同步为 X

例4

image-20210902152544031.png

  1. S1 广播提案号 1,想把值更新为 X

  2. S5 广播提案号 2,想把值更新为 Y

  3. 关键点,S3 还未经历 Accept 阶段时,就拿到了 S5 的 prepare 提案,这时是否会有不一致的情况呢?

  4. S3 在接到 S1 的 accept 请求时,n>=minN 条件不成立,因此没有更新 acceptN 和 acceptValue,并且返回的 minN 是 2

  5. 对 S1 来说,S3 返回的 minN 大于 n,选中失败;想更新 X 需要发起新一轮提案

  6. 对 S5 来说,accept 阶段发送的是它自己的 2:Y,后续会把值同步为 Y

例5

回顾最早提到的顺序性问题,看 Paxos 能否解决它

image-20210902152742816.png

下图演示了 Paxos 是如何解决顺序性问题的,分析步骤参考例3

image-20210902152753028.png

Paxos 缺点

  1. 效率较低,两轮操作只能选中一个值

  2. 难于理解

  3. 活锁问题

image-20210902153136877.png

  • Paxos 是允许多个 Proposer 的,因此如果按上图所示运行,则后一个提案总会让前面提案选中失败,显然死循环

****** **参考资料 ****

3. Raft 算法

要求

  • 理解 Raft 算法

Raft 算法

另一种共识算法,目的是比 Paxos 更易理解

整个 Raft 算法分解为三部分:

  1. Leader 选举

   ① 只有一个 Server 能作为 Leader

   ② 一旦此 Server 崩溃,选举新 Leader

  1. 执行操作,以日志复制为例(Log replication)

   ① 由 Leader 执行自己的日志记录

   ② 将日志复制到其它 Server,会覆盖掉不一致的部分

   ③ 多数派记录日志成功,Leader 才能执行命令,向客户端返回结果

  1. 确保安全

   ① 保证日志记录的一致性

   ② 拥有最新日志的 Server 才能成为 Leader

Leader 选举

  1. Leader 会不断向选民发送 AppendEntries 请求,证明自己活着

  2. 选民收到 Leader AppendEntries 请求后会重置自己的 timeout 时间

image-20210902154049390.png

  1. 选民收不到 AppendEntries 请求超时后,转换角色为候选者,并将任期加1,发送 RequestVote 请求,推选自己

image-20210902154114306.png

  1. 选民收到第一个 RequestVote,会向该候选者投一票,这样即使有多个候选者,必定会选出一个 Leader,选票过半即当选,如果落选会变回选民

image-20210902154133262.png

  1. 每一任期最多有一个 Leader,但也可能没有(选票都不过半的情况,需要再进行一轮投票,timeout 在 T~2T 间随机)

  2. 任期由各个 server 自己维护即可,无需全局维护,在超时后加1,在接收到任意消息时更新为更新的任期,遇到更旧的任期,视为错误

**执行操作(以日志复制为例) **

  1. 客户端发送命令至 Leader

  2. Leader 将命令写入日志(S1虚框),并向所有选民发送 AppendEntries 请求

image-20210902154733468.png

  1. 多数派通过后,执行命令(即提交,S1虚框变实),此时就可以向客户端返回结果

image-20210902154843978.png

  1. 在后续的 AppendEntries 请求中通知选民选民执行命令(即提交,S2,S3,S4,S5虚框变实)

image-20210902155110401.png

  1. 如果选民挂了,则 Leader 会不断尝试,待到选民重启,会将其缺失的日志陆续补齐

确保安全

Leader 日志的完整性

  1. Leader 被认为拥有最完整的日志

  2. 一旦 Leader 完成了某条命令提交,那么未来的 Leader 也必须存有该条命令提交信息

  3. 投票时,会将候选者最新的 <Term,Index> 随 RequestVote 请求发送,如果候选者的日志还没选民的新,则投否决票

image-20210902155344700.png

  • 图中 S2 如果超时,发起选举请求,其它服务器只会对它投否决票,因为它的 Index 比其它人都旧

  • 图中 S5 如果超时,发起选举请求,其它服务器也不会选它,因为他的 Term 太旧

选民日志的一致性

  1. 以 Leader 为准,对选民的日志进行补充或覆盖

  2. AppendEntries 请求发送时会携带最新的 <Term,Index,Command> 以及上一个的 <Term,Index>

  3. 如果选民发现上一个的 <Term,Index> 能够对应上则成功,否则失败,继续携带更早的信息进行比对

image-20210902155724436.png

  • 图中 Leader 发送了 <3,4,Command><2,3> 给 follower,follower 发现 <2,3> 能够与当前最新日志对应,这时直接执行 <3,4,Command> 即可

image-20210902155837273.png

  • 图中 Leader 发送了 <3,4,Command><2,3> 给 follower,follower 发现 <2,3> 不能与当前最新日志对应,会央求 Leader 发送更早日志

image-20210902160222512.png

  • Leader 这次发送了 <3,4,Command><2,3,Command><1,2> 给 follower,follower 发现 <1,2> 能够与当前最新日志对应,这时补全 <3,4,Command><2,3,Command>  即可

****** **参考资料 ****

> * raft.github.io/ Raft 资源