互联网时代里面讨论最多的便是分布式服务,而每一个分布式服务必然有一个分布式锁解决并发问题。分布式锁则就像是一个文件锁,例如:在单机服务中我想要写一个文件就要对其加锁,简单的应用级别的实现(当然linux系统有文件锁的支持)则是写之前获取一把锁,比如fileName.lock,创建一个锁文件,声明当前文件被锁定。写完之后释放锁则删除该文件。分布式系统中也同样需要类似的机制实现。如果分布式中每个节点服务器读到锁文件存在多个版本那就起不到排他的作用。所以分布式一致性算法的重要性不再多说。让我们开始我们的分布式一致性算法的学习之旅
Paxos
Paxos算法可以说是如其网上大家聊得一样。最难理解的一个算法
单Acceptor实现
如果分布式应用中只有一个acceptor。其余机器均为proposer。那么一致性自然就很简单。这种实现方式可以说是最为简单的实现。因为只有一台机器对于所有proposer的提案进行决策。当然问题也很明显就是一旦这台唯一的acceptor宕机。则整合集群则处于一种不可用状态
多Acceptor实现
一个acceptor必须批准它收到的第一个提案
那么问题来了,如下图,所有proposer同时提出不同的提案要修改同一个值,则四个节点则会出现四个结果,严重的不一致,那么需要再增加一些约束
为每个提案增加一个版本号,如果一个提案被选定版本号最小的为最终选定值。
如果其中proposer节点因为种种原因例如网络断开延迟等等。导致没有收到proposer2的提案。会出现两个不一致的值。而此时需要另外一个角色同步监听Listener在acceptor1与其他节点恢复通信时对本地陈旧的提案进行修复即可。也就是达成最终一致性
ZAB原子消息广播协议(Zookeeper Atomic Broadcast)
zk没有完全采用Paxos算法,而是使用自己的一套原子消息广播协议保证其数据一致性
- 移除了2PC的中断逻辑,要么正常反馈leader,要么抛弃
- 引入“过半”概念,过半即可进行广播commit提交
- Leader崩溃会造成数据不一致,引入崩溃恢复模式解决此问题
- 消息广播采用TCP协议,FIFO特性保证顺序性
ZAB事务基本特性
- ZAB协议需要确保那些在leader服务器上提交的事务最终被所有服务器提交
- ZAB协议需要确保丢弃那些只在leader服务器上提出的事务(未提交)
选举
基于ZAB两个原则,就决定了ZAB协议的leader选举算法:能够确保提交已经被leader提交的事务Proposal,同时丢弃已经被跳过的事务Proposal。针对这些要求,如果让leader选举算法能够保证新选举出来的leader服务器拥有集群中最高的事务编号(ZXID)的事务Proposal,那么就可以确定新选举出来的leader具有所有已提交的事务,更重要的是可以省去leader服务器检查proposal的提交和丢弃工作这一步骤。
leader选举
- 每个服务器发出一个投票
- 接收来自各个服务器的投票
- 首先会判断是否是本轮投票
- 是否来自looking状态的服务器
- 处理投票,接收到其他服务器投票后,针对每一个投票进行PK,规则如下
- 优先检查ZXID,较大的优先作为leader
- ZXID相同的情况下,myid较大的优先作为leader
- 统计投票,过半的被选举出来
- 改变状态,leader状态更新为leading,其余为following
数据同步
- 事务同步:Leader为每一个follower维护一个队列,将那些没有被follower服务器同步的事务以消息的形式发送到follower服务器,每一个proposal消息之后紧接着发送一个commit消息,待完成同步后,leader再将follower服务器加入真正可用的服务列表中
- 事务丢弃:ZXID是一个64位的数字,低32为是一个计数器,针对客户端的每一个事务请求,leader会对计数器进行加1操作;高32位代表leader周期epoch的编号,每当选举出一个新的leader服务器,就会从本地的事务日志中取出最大事务的ZXID,从中解析出epoch值,然后对其加1,最为新的ZXID。基于这样的策略,新连接的服务器如果包含上一epoch值的未提交的事务就一定不会被选为leader
疑问
- 为什么zk集群建议机器数为奇数?
- 为什么接受队列是一个,发送队列是多个?
- QuorumCnxManager每台机器监听一个选举的端口会有什么问题?
- 为什么PK时轮次小于外部投票轮次时要清空已接收到的投票?
疑问理解
- 5台机器最多宕掉2台,还可以继续使用,因为剩下3台大于5/2。说为什么最好为奇数个,是在以最大容错服务器个数的条件下,会节省资源,比如,最大容错为2的情况下,对应的zookeeper服务数,奇数为5,而偶数为6,也就是6个zookeeper服务的情况下最多能宕掉2个服务,所以从节约资源的角度看,没必要部署6(偶数)个zookeeper服务
- 如果接受队列是多个,选票PK时需要加同步锁处理
- 会出现重复建立连接,zk使用SID小的连接SID大的服务器来解决该问题,如果建立连接检查SID小于对方SID,则主动断开连接
- 因为投票轮次已经小于外部的,当前已接收的投票队列中可能全部都是旧批次的投票
RAFT
本段落参考:https://github.com/sofastack/sofa-jraft/wiki
RAFT 是一种新型易于理解的分布式一致性复制协议,由斯坦福大学的 Diego Ongaro 和 John Ousterhout 提出,作为 RAMCloud 项目中的中心协调组件。Raft 是一种 Leader-Based 的 Multi-Paxos 变种,相比 Paxos、Zab、View Stamped Replication 等协议提供了更完整更清晰的协议描述,并提供了清晰的节点增删描述。 Raft 作为复制状态机,是分布式系统中最核心最基础的组件,提供命令在多个节点之间有序复制和执行,当多个节点初始状态一致的时候,保证节点之间状态一致。系统只要多数节点存活就可以正常处理,它允许消息的延迟、丢弃和乱序,但是不允许消息的篡改(非拜占庭场景)。
Raft 可以解决分布式理论中的 CP,即一致性和分区容忍性,并不能解决 Available 的问题
CAP定理
CAP:Consistency(一致性),Availability(可用性),Partition tolerance(分区容错性)
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP定理:一个分布式系统不可能同时满足3个基本需求,最多只能同时满足两个
放弃C
数据存在多个备份。例如:主从跨机房部署+异步/半同步
- 一致性:异步方式数据同步期间存在短暂的不一致。半同步可能存在请求打到未完成同步的节点
- 高可用:主库异常,从库提供服务
- 分区容错:某个机房出现问题,可以切换至备用机房
放弃A
数据存在多个备份。例如:主从跨机房部署+同步复制/双写
- 一致性:同步方式数据同步(双写或者多写)保证数据一致性。
- 高可用:某个从库或者双写节点异常,导致服务不可用,必须等待全部写入成功才能恢复
- 分区容错:某个机房出现问题,可以切换至备用机房
放弃P
系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况
数据存在多个节点。类似于:主从同机房部署+同步复制/双写。
- 一致性:主从同步备份数据,主从属于同一网络分区无分区网络问题,一致性方案很多,例如:RAID10等方案
- 高可用:主库异常,从库提供服务
- 分区容错:机房出现问题直接寄了
BASE理论
BASE理论:Basically
Available(基本可用),Soft state(软状态),Eventually
consistent
(最终一致性)
基本可用
某个节点宕机时需要同步至其他可用节点后才可恢复该节点数据所提供的支持。部分机器宕机时,需要同步这些机器数据至可用机器上,同步期间服务存在响应时间长或者直接提示服务暂不可用
- 响应时间的损失:部分机器宕机时,响应时间变长
- 功能上的损失:部分机器宕机时,某些功能直接不可用
软状态
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql
replication的异步复制也是一种体现。
最终一致性
- 因果一致性(Causal Consistency):进程A更新数据后通知进程B,进程B如果要更新同一个数据,要基于进程A的最新数据,进程C没有因果关系则不需要受这样的限制
- 读己之所写(Read your writes):进程读取的值不会比自己上次更新的该值旧
- 会话一致性(Session consistency):同一个会话情景下的“读己之所写”
- 单调读一致性(Monotonic read consistency):进程从系统中读取一个数值后,系统中该进程之后的所有读该值的操作都不会比该值旧
- 单调写一致性(Monotonic write consistency):系统需要保证来自同一个进程的写操作被顺序的执行