分布式笔记

1、分布式理论基础

CAP理论

C 表示 一致性(Consistency):客户端任何时候看到的各节点数据都是一致的

A 表示 可用性(Availability):任何时候非故障结点都可以提供读写服务

P 表示 分区容错性(Partition tolerance):当有某些结点故障时,系统仍可以继续工作

但是一个web应用只能同时满足其中两个,同时当网络分区故障,则只能选择CP 和 AP。

为啥不可能选择 CA 架构呢? 举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。

总结:如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA 。

BASE理论

BASE 是 Basically Available(基本可用)Soft-state(软状态)Eventually Consistent(最终一致性) 三个短语的缩写。

BASE 理论是对 CAP 理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。

  • Basically Available(基本可用):分布式系统在出现不可预知故障的时候,允许损失部分可用性;
  • Soft state(软状态):允许系统中的数据存在中间状态,且不会影响系统整体可用性;
  • Eventually consistent(最终一致性):强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。

2、一致性算法

Paxos算法

Paxos 算法主要包含 2 个部分:

  • Basic Paxos算法:描述的是多节点之间如何就某个值(提案 Value)达成一致。
  • Multi-Paxos算法:描述的是执行多个 Basic Paxos 实例,就一系列值达成一致。Multi-Paxos 说白了就是执行多次 Basic Paxos ,核心还是 Basic Paxos 。

Basic Paxos算法

Basic Paxos 中存在 3 个重要的角色:

  • 提议者(Proposer):提议者负责接受客户端发起的提议,然后尝试让接受者接受该提议,同时保证即使多个提议者的提议之间产生了冲突,那么算法都能进行下去;
  • 接受者(Acceptor):负责对提议者的提议投票,同时需要记住自己的投票历史;
  • 学习者(Learner):如果有超过半数接受者就某个提议达成了共识,那么学习者就需要接受这个提议,并就该提议作出运算,然后将运算结果返回给客户端。

三个角色工作在三个阶段:

  • 第一阶段:Prepare阶段

    Proposer向Acceptors发出Prepare请求,Acceptors针对收到的Prepare请求进行Promise承诺。

  • 第二阶段:Accept阶段

    Proposer收到多数Acceptors承诺的Promise后,向Acceptors发出Propose请求,Acceptors针对收到的Propose请求进行Accept处理。

  • 第三阶段:Learn阶段

    Proposer在收到多数Acceptors的Accept之后,标志着本次Accept成功,决议形成,将形成的决议发送给所有Learners。

Raft算法

不同于Paxos算法直接从分布式一致性问题出发推导出来,Raft算法则是从多副本状态机的角度提出,用于管理多副本状态机的日志复制。

Raft算法将一致性分解为多个子问题: Leader选举(Leader election)、日志同步(Log replication)、安全性(Safety)、日志压缩(Log compaction)、成员变更(Membership change)等。

Raft算法三个角色:

  • Leader:接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志。
  • Candidate:Leader选举过程中的临时角色。
  • Follower:接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志。

Leader选举

Raft算法使用heartbeat方式,触发Leader选举。赢得最多票数的会成为Leader

日志同步

Leader选出后,就开始接收客户端的请求。Leader把请求作为日志条目(Log entries)加入到它的日志中,然后并行的向其他服务器复制日志条目,实现日志同步。

日志压缩

在实际的系统中,不能让日志无限增长,否则系统重启时需要花很长的时间进行回放,从而影响可用性。Raft采用对整个系统进行snapshot来解决,snapshot之前的日志都可以丢弃。

3、分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

两阶段提交(2PC)

将整个事务流程分为两个阶段,准备阶段(Prepare phase)提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。

  • 准备阶段:事务协调者给每个本地事务参与者发送 prepare 消息,告知事务参与者准备执行事务了,然后事务参与者返回 yes 或者 no消息给事务协调者
  • 提交阶段:
    • 若事务参与者都返回yes,则事务协调者发送 commit消息,每个事务参与者执行提交事务操作
    • 若有参与者返回 no,则事务协调者发送 rollback消息,对本地事务执行回滚操作。

存在问题:

  1. 执行完准备阶段后,本地事务中的资源处于锁定状态,若此时协调者挂了,会导致本地资源无法释放,影响其他业务,且本地事务参与者会一直等待;

  2. 在第二阶段,若协调者或参与者宕机了,会造成数据不一致问题。

解决方案:干等待问题 ===> 3PC

三阶段提交(3PC)

3PC 相比于 2PC,又多了一个准备阶段,即分别为CanCommit、PreCommit、DoCommit

  • CanCommit:协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。
  • PreCommit:协调者向所有参与者发送PreCommit命令,询问是否可以进行事务的预提交操作。此阶段事务协调者和事务参与者都引入了超时机制。若所有事务参与者返回 Yes则进入下一阶段,否则协调者发送 abort消息,中断事务。
  • DoCommit:若上一阶段所有参与者返回 Yes,则提交事务;否则回滚。

存在问题:

  1. 虽然解决了2PC中参与者长时间阻塞的问题(资源长时间无法释放的问题),但是并没有解决一致性的问题。

解决方案:TCC

补偿事务(TCC)

两大角色:

  • 事务管理器
  • 分支事务

TCC包含3种操作,分别是

  • Try:预处理
  • Confirm:业务确认
  • Cancel:执行失败,回滚

TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,注意倒序执行,即try的反序;

若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。

4、分布式锁

当多个进程不在同一个系统中(比如分布式系统中控制共享资源访问),用分布式锁控制多个进程对资源的访问。

常见分布式锁实现方案:

  • 基于数据库实现分布式锁
  • 基于 redis实现分布式锁
  • 基于 zookeeper实现分布式锁

基于数据库实现分布式锁

锁表

最简单的方式,维护一张缩表,记录了资源的锁定状态。

当我们想要获得锁的时候,就可以在该表中增加一条记录,想要释放锁的时候就删除这条记录。

悲观锁

在对任意记录进行修改前,先尝试为该记录加上排他锁。如果加锁失败,那么当前查询可能要等待或者抛出异常;如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

Mysql 使用 select…for update 的方式,实现悲观锁

乐观锁

假设多用户并发的事务在处理时不会彼此互相影响。在提交数据更新之前,如果出现错误,则回滚,否则就提交。

Mysql 数据库使用版本号实现乐观锁,每次对数据的更新操作都对版本号执行+1操作,并判断当前版本号是不是该数据的最新的版本号。

基于redis实现分布式锁

简单实现

加锁:

setnx key value

当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败;

解锁:直接删除对应的key

del key

为了防止误删除操作,采用 Lua脚本,通过 key 对应的 value(唯一值)来判断,同时也可以保证解锁操作的原子性。

锁超时:

expire key 30

为了保证 setnx 和 expire 原子性,使用一条命令:

SET productId:lock 0xx9p03001 NX PX 30000

存在问题:

在 redis 集群数据同步时,redis主节点获取到锁后还没有同步到其他节点时,主节点宕机了,此时新的 redis 主节点依然可以获取锁,这时多个应用服务都获取到锁。针对这个问题,解决方案:Redlock

Redlock

Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败,则依次删除锁。

Redlock 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。

常用的是基于 Zookeeper 来做分布式锁。

5、RPC

RPC(Remote Procedure Call)远程过程调用,指当服务提供的方法部署在两台计算机上,由于不在一个内存空间中,因此需要网络编程的方式传递参数和结果,这时就需要RPC。

5个角色:

  • 客户端
  • 客户端 Stub:负责封装调用方法、类、参数等
  • 网络传输
  • 服务端 Stub:接收参数信息,执行指定方法并返回结果
  • 服务端

原理:

image-20220907111038443
  1. 客户端以本地调用方式调用远程服务
  2. 客户端 Stub接收到方法参数等信息后,序列化封装成能够网络传输的消息体
  3. 网络通信将消息发送给服务端
  4. 服务端 Stub将接收消息反序列化恢复
  5. 服务端根据消息执行本地方法,得到执行结果,序列化以相同方式发送给客户端。
posted @ 2022-11-28 17:34  柯文先生  阅读(86)  评论(0)    收藏  举报