> For the complete documentation index, see [llms.txt](https://clloud.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://clloud.gitbook.io/notes/fen-bu-shi-xi-tong.md).

# 分布式系统

## 一、分布式系统

分布式系统是一个硬件或软件组件分布在不同的网络计算机上，彼此之间仅仅通过消息传递进行通信和协调的系统。

### 1. CAP定理

分布式系统不可能同时满足一致性、可用性和分区容忍性，最多只能同时满足其中两项。

#### 一致性 (Consistency)

一致性指的是数据在多个副之间能否保持一致的特性，在一致性的条件下，系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。

一致性有以下几个级别：

**强一致性**

要求系统在某节点写入数据后，从其他节点能读出一致的数据，实现起来对系统的性能影响较大。

**弱一致性**

系统在成功写入数据后，不承诺可以立即读到写入的值，也不承诺多久之后数据能够达到一致，但会尽可能保证到某个时间后，数据能够达到一致性状态。弱一致性可以分为：

* 会话一致性：只保证对于写入的值，在同一个客户端会话中可以读到一致的值，但其他会话不能保证
* 用户一致性：只保证对于写入的值，在同一个用户中可以读到一致的值，但其他用户不能保证

**最终一致性**

最终一致性是弱一致性的特例，是一种非常重要的一致性模型，即系统会保证在一定时间内，能够达到一个数据一致的状态。

#### 可用性 (Availability)

可用性指系统提供的服务必须一直处于可用的状态，对于用户的每一个操作请求总是能够在**有限的时间内返回结果**。

#### 分区容忍性 (Partition Tolerance)

网络分区指分布式系统中的节点被划分为多个区域，每个区域内部可以通信，但是区域之间无法通信。

在分区容忍性条件下，分布式系统在遇到任何网络分区故障的时候，仍然需要能对外提供一致性和可用性的服务，除非是整个网络环境都发生了故障。

#### 权衡

在分布式系统中，分区容忍性必不可少，因为需要总是假设网络是不可靠的。因此，CAP 理论实际上是要在可用性和一致性之间做权衡。

可用性和一致性往往是冲突的，很难使它们同时满足。在多个节点之间进行数据同步时，

* 为了保证一致性（CP），不能访问未同步完成的节点，也就失去了部分可用性
* 为了保证可用性（AP），允许读取所有节点的数据，但是数据可能不一致

### 2. BASE理论

BASE 是基本可用（Basically Available）、软状态（Soft State）和最终一致性（Eventually Consistent）三个短语的缩写。BASE理论面向的是大型高可用可扩展的分布式系统，与传统事务的ACID的强一致性模型相比，允许牺牲强一致性来获得可用性，允许数据在一段时间内不一致，但最终达到一致状态。

BASE 理论是对 CAP 中一致性和可用性权衡的结果，其核心思想是：即使无法做到强一致性，但每个应用都可以根据自身业务特点，采用适当的方式来使系统达到最终一致性。

#### 基本可用

指分布式系统在出现故障的时候，保证核心可用，允许损失部分可用性。例如，允许网站的响应时间略微变慢。

#### 软状态

指允许系统中的数据存在中间状态，并认为该中间状态不会影响系统整体可用性，即允许系统不同节点的数据副本之间进行同步的过程存在时延。

#### 最终一致性

最终一致性强调的是系统中所有的数据副本，在经过一段时间的同步后，最终能达到一致的状态。

### 3. 分布式事务

#### 概念

[深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)

事务的操作位于不同的节点上，需要保证事务的 ACID 特性。

分布式事务可以保证不同数据库的数据一致性。一个分布式事务可以看作是由多个分布式的操作序列组成的，这些子事务分布在不同的节点上，分布式事务需要保证这些子事务要么全部成功，要么全部失败。

例如，买家在电商平台下单，需要扣库存和更新订单数据，如果库存数据库和订单数据库不在同一个节点上，就涉及分布式事务。

#### 实现方式

分布式事务是对多个数据库的事务进行统一控制，按照控制力度可以分为：

* 不控制：保证可用性，但不保证数据一致性
* 部分控制：采用两阶段提交等策略，兼顾可用性和一致性
* 完全控制：牺牲可用性，保证数据的强一致性

### 4. 一致性协议

为了解决分布式一致性问题，业界提出了以下几个经典的解决方案：

#### 两阶段提交 (2PC)

两阶段提交 (Two-phase Commit) ，通过引入协调者 (Coordinator) 来协调参与者的行为，并最终决定这些参与者是否要真正执行事务。

**运行过程**

**投票阶段**

协调者询问参与者事务是否执行成功，参与者发回事务执行结果。询问可以看成一种投票，需要参与者都同意才能执行。

![img](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/Gyf94hANSKqbLN1X.png)

**提交阶段**

如果事务在每个参与者上都执行成功，事务协调者发送通知让参与者提交事务；否则，协调者发送通知让参与者回滚事务。

需要注意的是，在投票阶段，参与者执行了事务，但是还未提交。只有在提交阶段接收到协调者发来的通知后，才进行提交或者回滚。

![img](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/mztT5lHvH3tenC64.png)

**存在的问题**

* **同步阻塞（最主要的问题）** 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞等待状态，无法进行其它操作。
* **单点问题** 协调者在 2PC 中起到非常大的作用，发生故障将会造成很大影响。特别是在提交阶段发生故障，所有参与者会一直同步阻塞等待，无法完成其它操作。
* **数据不一致** 在提交阶段，如果协调者只发送了部分 Commit 消息，此时网络发生异常，那么只有部分参与者接收到 Commit 消息，也就是说只有部分参与者提交了事务，使得系统数据不一致。
* **太过保守** 任意一个节点失败就会导致整个事务失败，没有完善的容错机制。

#### 三阶段提交 (3PC)

三阶段提交协议把两阶段提交协议的第一阶段**分成了两步**: 询问能否执行事务 + 预执行事务，降低了参与者的阻塞范围；在协调者和参与者中引入**超时机制**，能够在出现单点故障后继续达成一致。

**运行过程**

**canCommit 阶段**

协调者向参与者发送canCommit请求，参与者如果可以执行事务提交就返回yes，否则返回no.

![image-20200522165102531](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/uvrLek5d9NBsonXd.png)

**preCommit 阶段**

* 协调者从所有参与者得到的反馈都是yes

  协调者向所有参与者发送preCommit请求，参与者接收到preCommit请求后会执行事务操作，成功执行了事务的参与者会返回ack.

![image-20200522170421065](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/MPVzBBo5SP1Hj6ft.png)

* 协调者从所有参与者得到的反馈有一个是no，或等待超时之后没收到响应

  协调者向所有的参与者发送abort请求。参与者在收到来自协调者的abort请求或超时后仍未收到协调者请求，执行事务中断。

  ![image-20200522170856583](e:/Files/Notes/images/4pUR3RgxXUxRs9Z5.png)

**doCommit 阶段**

* 协调者从所有参与者得到了ack的反馈

  协调者向所有参与者发送doCommit请求。参与者接收到doCommit请求后，执行正式的事务提交。

  ![image-20200522172017894](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/0Sa1EpHvvGN7azix.png)
* 协调者从所有参与者得到的反馈有一个是no，或等待超时之后没收到ack响应

  协调者向所有参与者发送abort请求，参与者执行事务回滚。

  ![image-20200522170856583](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/4pUR3RgxXUxRs9Z5.png)

#### Paxos算法

[Paxos算法原理与推导](https://www.cnblogs.com/linbingdong/p/6253479.html)

[Paxos by Example](https://courses.cs.washington.edu/courses/cse452/13sp/resources/cse452_sec3.pdf)

目前公认的解决分布式一致性问题最有效的算法之一，即对多个节点产生的值，该算法能保证只选出唯一一个值。

主要有三类节点：

* 提议者（Proposer）：提议一个值
* 接受者（Acceptor）：对每个提议进行投票
* 告知者（Learner）：被告知投票的结果，不参与投票过程

![image-20200523102801989](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/lwcwT1CJVlv7AnzS.png)

**运行过程**

**Prepare阶段**

1. **Prepare Request**

   Proposer选择一个提案编号N，向半数以上的Acceptor发送值为V的Prepare请求 \[N, V]

![image-20200523111243031](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/2HUyqMJ24lIPJHdm.png)

1. **Prepare Response**

   当 Acceptor 接收到一个 Prepare 请求，包含的提议为 \[N, V]，并且之前还未接收过 Prepare 请求，那么发送一个 Prepare 响应，设置当前接收到的提案为 \[N, V]，并且保证以后不会再接受序号小于 N 的提案。

![image-20200523111638395](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/T6wfPExW2auSO8lf.png)

![image-20200523111747967](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/s0hPFpzVBUo90EYO.png)

**Accept阶段**

1. **Accept Request**

   当Proposer收到半数以上Acceptor对其发出的编号为N的Prepare请求的响应，那么它就会发送一个针对 \[N, V] 提案的Accept请求给半数以上的Acceptor，V是收到的响应中编号最大的提案的value。

![image-20200523112008016](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/6zooNbDOKbb2zCL2.png)

1. **Accepted**

   当Acceptor收到一个针对编号为N的提案的Accept请求，若该Acceptor没有对编号大于N的Prepare请求做出过响应，它就接受该提案，并发送 Learn 提案给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议，那么该提议的提议值就被 Paxos 选择出来。

![image-20200523112116722](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/M0KYNmU9IGRFn4fM.png)

## 二、ZooKeeper

[《Paxos到Zookeeper：分布式一致性原理与实践》](https://book.douban.com/subject/26292004/)

Zookeeper 是一个分布式的、开源的分布式应用程序协调服务，为分布式应用提供一致性服务：配置维护、域名服务、分布式同步、组服务等。

### 1. ZAB协议

[Zookeeper一致性协议ZAB](https://www.jianshu.com/p/2bceacd60b8a)

ZAB (Zookeeper Atomic Broadcast)表示Zookeeper原子广播，Zookeeper 使用 ZAB 协议来保证分布式事务的最终一致性。基于该协议，Zookeeper实现了一种主备模式的系统架构来保证集群中各数据副本的一致性，主服务器（Leader）负责处理客户端的所有事务请求，并将数据状态的变更同步到其他从服务器（Follower）

ZAB协议包含两种基本模式，分别是消息广播和崩溃恢复。

#### 消息广播

ZAB协议的消息广播过程采用的是一个原子广播协议，类似二阶段提交过程：

* Leader服务器将客户端的事务请求转换成proposal，通过队列发送给Follewer服务器
* Follower服务器执行事务，成功执行后向Leader服务器返回ACK反馈
* Leader服务器收到半数以上Follower服务器的ACK后，向所有Follower服务器发送Commit请求
* Follower服务器接收到Commit请求后，最终提交事务

![image-20200523163527297](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/W2IzHyfNfBd9UHhL.png)

#### 崩溃恢复

Zookeeper在启动过程中，或是Leader服务器出现异常，或是多数Follower服务器出现异常时，ZAB协议会进入崩溃恢复模式并通过选举产生新的Leader服务器。

**专业术语**

* **SID (Server ID)**

  SID是一个数字，用来唯一标识一台Zookeeper集群中的机器，每台机器不能重复，和myid的值一致。
* **ZXID (Zookeeper Transaction ID)**

  ZXID是一个事务ID，用来唯一标识一次服务状态的变更。ZXID是一个64位的数字，高32位表示当前集群所处的年代，记为epoch，每次Leader变更之后会在 epoch 的基础上加1；低32位是一个单增计数器，对客户端的每一个事务请求，Leader 在产生新的 Proposal 时，会对该计数器加1。

崩溃恢复分为以下两个阶段：

**Leader选举**

`FastLeaderElection`算法会选出数据最新（ZXID最大）的服务器作为Leader，这个Leader一定具有所有已经提交的提案。

1. 节点在选举开始时默认投票给自己，接收到其他节点的选票时，会根据以下的Leader选举条件判断并且更改自己的选票，然后重新发送选票给其他节点：
   * 选择 epoch 最大的节点
   * 若 epoch 相等，选择 ZXID 最大的节点
   * 若 epoch 和 ZXID 相等，选择SID最大的节点
2. 当有一个节点的得票超过半数，该节点会设置自己的状态为 Leading ，其他节点会设置自己的状态为 Following.
3. 选举产生的新Leader会将自身最大的ZXID中的epoch加1，并将低32位清零，重新开始生成ZXID.

TODO 时序图

**数据同步**

当选举产生了新的Leader服务器，同时集群中已经有过半的机器与该Leader服务器完成了状态同步，ZAB协议才会退出崩溃恢复模式。

为了确保恢复过程中数据的一致性，ZAB 协议需要保证以下两点：

1. **已经在 Leader 服务器上提交的事务最终被所有的服务器提交**

   Leader服务器会为每一个Follower服务器准备一个队列，将那些没有被各Follower服务器同步的事务以Proposal消息的形式发送给各个Follower服务器，并在每个Proposal消息后再发送一个Commit 消息，表示该事务已经提交。

   ![image-20200524152412898](https://github.com/Clloud/Notes/tree/80bfb41f1d7824cc02e47a2dc6517ceb1754d26e/images/PvmhquE5OziOdGp5.png)
2. **只在 Leader 服务器上被提出而没有被提交的事务要被丢弃**

   之前已经提到，ZAB 协议可以通过 epoch 编号来区分 Leader 变化周期。

   当 Follower服务器连接上新的 Leader 服务器时，Leader 服务器会对比自己最后提交的事务和 Follower 服务器最后提交的事务。若Follower服务器包含了**上一个 Leader 周期**中尚未提交过的事务，Leader服务器会要求 Follower 服务器进行回退操作，回退到一个已经被集群中过半机器提交的最新事务的状态。

### 2. 使用场景

TODO

## 三、RPC

[RPC简介及框架选择](https://www.jianshu.com/p/b0343bfd216e)

RPC(Remote Process Call)，即远程过程调用，允许一台机器调用另一台机器上的函数或方法并得到返回结果。

RPC采用请求响应模型，可以隐藏底层的通讯细节。

![](https://1039526258-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M86IkrsPVdsCNy0ubap%2Fsync%2Fb8b1552567c305d12d536bcba3d1a400471de5ac.png?generation=1590374605151123\&alt=media)

### RPC与REST

两者都是网络交互的协议规范，通常用于多个微服务之间的通信。

|      | RPC                      | REST                   |
| ---- | ------------------------ | ---------------------- |
| 通信协议 | 一般采用TCP                  | HTTP                   |
| 性能   | 采用自定义协议，头部字段精简，通信效率高，性能高 | HTTP头部冗余字段多，通信效率低，性能较低 |
| 约束性  | 约束强                      | 约束弱，需要通过文档来交互          |
| 应用场景 | 系统内部通信                   | 对外提供服务                 |

### RPC框架

RPC框架需要解决以下三个问题：

* **服务端如何确定客户端要调用的函数？**

  在RPC中，所有的函数都必须有一个`Call ID`，这个`Call ID`在所有进程中都是唯一的。 客户端和服务端分别维护一个`Call ID <--> 函数名`的对应表。 客户端进行远程过程调用时附上`Call ID`，服务端通过查表来确定客户端需要调用的函数，然后执行相应函数。
* **如何进行序列化和反序列化？**

  客户端和服务端交互时需要将参数或结果转化为字节流在网络中传输。将对象数据转化为字节流称为序列化，将字节流转换成特定格式的对象称为反序列化。常见的序列化协议有XML、JSON、Protocol Buffers、Thrift等。
* **如何传输数据？**

  多数RPC框架选择TCP作为传输协议，也有部分选择HTTP.

## 四、Dubbo

[RPC和Dubbo简介](https://www.jianshu.com/p/8ba4b7b834aa)

## 微服务
