LOADING

加载过慢请开启缓存,浏览器默认开启

interview-distributed

分布式锁

1.锁的特点

名称 特点
互斥的 任何时候只能有一个线程持有锁
可重入 如果一个线程持有锁,也不会因为多次获取而死锁
安全 如果一个线程获得了锁,即使崩溃,也必须被释放

2.分布式锁

名称 特点
高性能 因为有很多的服务器来获取,所以需要能够高性能的释放和获取
高可用 不能因为某一个分布式锁获取的服务不可用,就导致所有服务都拿不到或释放锁
锁失效机制 某个应用获取到锁之后,一直没有来释放锁,可能服务本身已经挂掉了,不能一直不释放锁,导致其他服务一直得不到锁
非阻塞 若锁以及被获取了,别的服务不能一直等待

常见的分布式锁实现方式

方式 特点 缺点 优点 解决方案
基于数据库 基于mysql来实现:
1.基于唯一索引实现/行级表:写入方法名/线程名
2.insert ignore,释放的时候delete。和第一种差不多
3.select..for update的排他锁:释放锁用commit
1.唯一索引缺点:
a.没有线程唤醒
b.非常依赖数据库
c.可能导致数据库压力比较大
d.非公平锁,别的只能等待
3.排他锁方案:可能对数据库的并发要求比较高,瓶颈比较严重
基于缓存 通过Redis来实现分布式锁,setnx(如果不存在插入,如果存在不插入) lock_key 1,del lock_key(释放) 长时间可能拿着锁 1.配置过期时间,通过监听机制来延长过期时间(看门狗机制)
2.一般配置一个slave,来保证如果主节点不可用的情况。
3.红锁
基于Zookeeper 获取和释放是差不多的,在zookeeper里会有一个锁节点,在锁的节点下创建临时的顺序节点,若自身是整个节点中最小的节点,那么就可以加锁,否则就一直监听,前面的释放了锁,那么就可以开始拿锁 1.依赖性比较强,需要重新加一个环境,比缓存实现比较复杂。2.性能差一点 1.可以保证一致性,如果需要100%不出问题,使用zookeeper

什么是分布式锁,为什么需要分布式锁

随着业务发展,单机环境可能已经扛不住压力了,一般采用的方式是加机器,然后从单机到多机,也产生了很多分布式的问题,我们的服务可能在不同的进程里,也可能在不同的机器里,这个时候需要去解决这个问题。

分布式最大的问题在于多进程,这个时候需要分布式锁

分布式事务

分布式事务是指一个业务流程,跨越了多个分布式系统或服务的事务处理,需要保证多个参与者之间的数据一致性和原子性(电商平台中的付款、支付的数据一致性)

由于网络不靠谱,所以可能产生很多脏数据

两阶段提交协议的基本过程,以及应用 (2PC)

1.上游发起事务,然后事务管理器来去询问别的微服务是否已经准备好了。

2,如果都准备好了,管理器向所有的参与者都发送一个commit命令,如果有一个参与者返回了no,那么管理器向所有参与者都发送回滚命令

优点:

1.能够利用数据库本地的事务来完成提交和回滚

2.
缺点:

1.会造成同步阻塞,可能等待时会导致阻塞

2.如果事务管理器挂了,那么就会出问题

3.协调器发送回滚/提交的命令的时候,可能会网络波动

4.必须等待一整个事务都结束了之后,才会释放资源

2PC协议怎么处理协调者和参与者之间的通信故障

首先,故障无法避免,那么出现了怎么保证一致性和可靠性呢?

1.超时时间,如果参与者响应大于了超时时间,那么就直接回滚

2.心跳机制,定期向参与者发送心跳,如果一段时间没有收到响应,那么认为参与者挂了

3.预备性提交,在第一个阶段,协调者告诉参与者,可以预备提交,如果第二阶段协调者无法收到消息,那么向该参与者发送回滚

4.备份协调器,两个节点

5.采用MQ,来发送消息

3PC协议

通过一个额外的阶段来减少同步阻塞和单点故障

1.在2PC只有协调者才有超市机制,现在都有了

2.分为conCommit(类似准备阶段) preCommit doCommit三个阶段

3.到第三阶段了,参与者还是会去提交,因为很可信了

avator

AT模式

补偿性事务
avator

1.尝试阶段,协调者向所有参与者发送一个参与请求,然后每个参与者先保存数据库的before image,然后修改,之后创建after imgae,然后加上行锁,如果都成功了,那么把这些生成的都删了

2.如果失败了,那么就通过before image和after image来恢复和回滚,最后再删除这些东西

脏写可能需要人工检验

TCC模式

avator
try:
先进行操作
confirm:
如果都confirm了,就成功了
cancel:
有一个没有confirm,那么就取消

仍旧为二阶段的协议,但是具有自我修复的能力的,执行逆操作

需要改造逻辑,开发成本高很多,可以很好的保证一致性

Saga模式

把复杂的事务拆分成小事务

avator

方式

基于事件:产生新事件,然后别的进行监听,回滚需要为服务提供补偿机制

优点:简单容易理解,个个参与方之间无直接沟通,完全解藕

缺点:微服务多了可能导致环形监听。

基于命令:
avator

优点:避免了业务方的环形依赖,可维护,减少了复杂度,不用监听不同服务的类型

共识算法

Raft

什么是Raft,相比于Paxos,Raft最大的特性就是易于理解,且状态简化

共区分为三个问题,分别为

1.领导者选举

2.日志复制

3.安全性

复制状态机

核心思想:相同的初始状态+相同的输入=相同的结束状态

多个节点上,从相同的初始状态,执行相同的一串命令,产生相同的最终状态

在Raft中,leader将客户端请求封装到一个个的log entry中,将这些log entries复制到所有的follower节点,然后大家按相同顺序应用log entries中的command,根据核心思想,那么大家结束状态肯定一一致

状态简化

在任何一个时刻,Raft的每一个服务器节点都处于 leader follower或者candidate这三个状态之一,只需要考虑状态的切换就可以了

avator

Raft把时间分成任意长度的任期,任期用连续的整数标记

每一段任期从一次选举开始,在某些情况下,这一任期会以没有leader(相同票数/都没超过一半)结束,一个新的任期很快会重新开始。Raft保证在任意一个任期内,最多只有一个leader

节点通信

Raft中服务器之间通过 RPC进行通信,且Raft中只有两种主要的RPC

1.请求投票

由candidate在选举期间发起

2.追加条目

由leader发起,用来复制日志和提供一种心跳机制

服务器在通信的时候会交换当前任期号,如果一个服务器的当前任期号比其他的小,那么会把任期号更新为较大的那一个

如果一个candidate或者leader发现自己的任期号过期了,会立即回到follower状态

如果一个节点接收到一个包含过期的任期号的请求,他会直接拒绝这个请求

1.领导者选举

Raft内部有一种心跳机制,如果存在leader,那么他就会周期性地向所有follower发送心跳,来维持自己的地位,如果follower一段时间内没有收到心跳,那么他就会认为系统中没有可用的leader了,那么就开始选举

开始一个选举过程后,follower先增加自己的当前任期号,并转换为candidate状态,然后投票给自己,并且并行的通过RPC给其他节点发送投票请求

对于一个节点来说最终会有三种结果:
1.它获得了超过半数选票赢得了选举 -> 成为主并开始发送心跳

2.其他节点赢得了选举 ->收到新的leader心跳后,如果新leader的任期号不小于自己当前的任期号,那么从candidate->follower

3.一段时间后没有任何获胜者,每个candidate在一个自己的超时时间后增加任期号并开始重新选举

avator
消息如图所示
avator

follower的投票采用先来先得的原则投出选票

2.日志复制

Leader被选举出后,开始为客户端请求提供服务,怎么知道谁是leader呢?

随机向某个节点发送请求:
如果为leader,ok。 如果为follower,那么通过心跳拿leader。 如果宕机了,那么重复

leader接收到客户端的指令后,会把指令作为一个新的条目追加到日志中去

一条日志需要有三个信息

1.状态机指令

2.leader的任期号

3.日志号(日志索引)

leader并行发送追加条目 RPC给follower,让他们复制该条目,当该条目被超过半数的follower复制后,leader就可以在本地执行该指令并把结果返回客户端

把本地提交指令,也就是leader应用日志与状态机这一步,称为提交

只需要超过半数就可以提交(包括leader)

那么如果出问题,Raft怎么样才能让出问题的节点获取到完整的日志呢?

这里有三种情况

1.follower没有响应(慢了)

leader不断地发送追加条目请求,哪怕leader已经回复了客户端(已经提交)

2.follower崩溃后恢复

Raft追加条目的一致性检查(leader发送每个追加条目的时候,会添加上前一个日志的索引位置还有任期号,如果follower在它的日志中找不到前一个日志,那么就拒收,然后leader再发送前一个日志,可以二分优化)生效,保证follower能够按序恢复

3.leader宕机

可能leader上有未提交的日志,对外来说是无所谓的,因为客户端会认为此次失败了,但是leader恢复后会出现问题,因为会发生冲突,老leader收到appendRPC,之后一致性检查出现问题,强制floower复制leader的日志

(没有提交,所以可以丢弃)

这样的日志复制,可以保证

1.只要过半服务器能正常运行,Raft就可以接受,复制并应用新的日志条目

2.在正常条件下,新的日志条目可以在一个RPC来回中被复制给集群中的过半机器

3.耽搁运行慢的follower不会影响整体的性能

追加条目的消息

avator

3.安全性

由于Raft追求易理解性,所以日志是没有空洞的,但是如果leader有问题,你的日志也可能是有问题的,所以Raft通过几个补充规则完善了这个算法

类型 表现 处理方式
leader宕机处理:选举限制 如果一个follower落后了leader若干条日志(但没有漏任意一个任期),那么下次选举中,它依旧有可能当选leader,但无法补上,造成状态机的不一致 保证被选举出来的leader包括所有的已经提交的日志条目。由request RPC的消息条目实现,由于请求投票的request中最后有candidate的最后一个日志号还有最后一个日志的任期,假设当前follower的日志比candidate要新,那么拒绝投票
leader宕机处理:新leader是否提交之前任期内的日志条目 前leader在提交某个日志条目前崩溃了 以后的leader会尝试完成该日志条目的”复制”(no 提交),即尝试复制,只有当前任期内的日志条目才通过计算副本数目的方式来提交,然后提交的时候会把之前的日志条目间接地提交
follower和candidate宕机 如果follower和candidate都崩溃了,那么后续发送给他们的RequestVote和AppendEntries都会失败,Raft通过无限的重试来处理这种失败,如果崩溃的机器重启了,那么这些RPC就会成功的完成
时间和可用性限制 平均故障时间>>选举超时时间(10ms-500ms)>>广播时间(0.5-20ms)(由硬件决定) Raft不依赖客观事件,哪怕因为网络或其他因素,造成后发的RPC先到,也不会影响Raft的正确性

Raft的提交:
单点提交(leader 提交)<->集群提交(全部,使用心跳包发送leader commit参数,告诉follower是否能提交)

4.集群成员变更

在需要改变集群配置(增减节点,替换宕机机器)的时候,Raft可以进行配置变更自动化

最大的问题是变更过程中不会出现同一任期的两个leader(脑裂问题:由于原配置可以出现leader,新配置也可以出现leader)

Raft采用一种两阶段的方法:

集群先切换到一个过渡的配置,称为联合一致

1.第一阶段,leader发起$C_{old,new}$使整个集群进入联合一致状态,此时,所有的RPC都要在新旧两个配置中都达到大多数才算成功

2.第二阶段,leader发起$C_{new}$使整个集群进入新配置状态,此时,所有RPC只要在新配置下到达大多数就算成功

一旦某个服务器将该新配置日志条目增加到自己的日志中,他就会用该配置来做出未来所有的决策(服务器总是使用它日志中最新的配置,无论该配置日志是否被提交)

大概有以下三种情况

情况 说明
leader在$C_{old,new}$未提交时宕机 不会出现问题,因为需要都满足才行
leader在$C{old,new}$已提交但$C{new}$未发起时宕机 已经符合这种情况了
leader在$C_{new}$已发起时宕机 也不会有问题,因为没有Cnew的节点选举成功后也会发送Cnew

Paxos

Basic Paxos

角色
|角色|功能|
|-|-|
|Client|请求发起者|
|Proposer|接受client请求,向集群发起提议,起到冲突调节的作用|
|Acceptor|提议投票和接受者,只有在行程法定人数时,提议才会被最终接受|
|Learner|提议接受者,备份,对集群一致性没有影响|

流程

1.Prepare

Proposer提出一个提案,编号为N,此N大于这个Proposer之前提出的提案编号,请求acceptor的quorum接受

2.Promise

如果N大于此acceptor之前接受的任何提案,则接受,否则拒绝

3.Accept

如果达到了多数派,proposer会发出accept请求,次请求包含提案编号N,以及内容

4.Accepted

如果此acceptor在此期间没有收到任何大于N的提案,则接受此提案内容,否则忽略

Multi Paxos

Fast Paxos