
SpringCloud Alibaba系列——3Nacos数据一致性协议
作者 | 宇木木兮
来源 |今日头条
学习目标
- 理解一致性协议的概念
- Nacos中的一致性协议有哪几种以及各自的特点
- Distro协议的流程
- Raft协议的流程
第1章 一致性概述
当前实际应用中的一致性模型分类大体分为弱一致性和强一致性两类。
1.1 弱一致性
也叫最终一致性
- DNS(Domain Name System)
- Gossip(Cassandra、Redis的通信协议)
1.2 强一致性
大体可分为两类:
1.主从同步
- 基本思想:主从同步复制
Master接受写请求
Master复制日志至slave
Master等待,直到所有从库返回
- 存在的问题: 一个节点失败,Master阻塞,导致整集群不可用,保证了一致性,可用性大大降低
2.多数派 - 基本思想:每次写都保证写入大于N/2个节点,每次读保证从大于N/2个节点中读。
- 相关算法:
Paxos
Raft(multi-paxos)
ZAB(multi-paxos)
1.3 Paxos
Paxos算法是莱斯利·兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法。Paxos的发展分类:Basic Paxos、Multi Paxos、Fast Paxos
1.3.1 Basic Paxos
1.角色介绍
Client: 系统外部角色,请求发起者。像民众
Proposer: 接受Client请求,向集群提出提议(propose),并在冲突发生时,起到冲突调解的作用。像议员,替民众提出议案
Acceptor:提议投票和接受者,只有在形成法定人数(Quorum,一般即为majority-多数派)时,提议才会最终被接受。像国会
Learner:提议接受者,backup-备份,对集群一致性没什么影响。像记录员
2.步骤、阶段(phases)
Phase 1a:Prepare proposer提出一个提议,编号为N,此N大于这个proposer之前提出的提案编号。请求acceptors的quorum接受。
Phase 1b:Promise 如果N大于此acceptor之前接受的任何提案编号则接受,否则拒绝。
Phase 2a:Accept 如果达到了多数派,proposer会发出 accept请求,此请求包含提案编号N,以及提案内容。
Phase 2b:Accepted 如果此acceptor在此期间没有收到任何编号大于N的提案,则接受此提案内容,否则忽略。
3.基本流程
正常流程
- there is 1 Client, 1 Proposer, 3 Acceptors (i.e. the Quorum size is 3) and 2 Learners (represented by the 2 vertical lines).
- 一个Acceptor宕机
- 一个Learner宕机
- 一个Proposer宕机
4.潜在问题
- 活锁(livelock)或决斗(dueling)
活锁发生的流程:
解决办法:如果发生冲突,则Proposer等待一个Random的Timeout(一般几秒)再提交自己的提议。
- 难实现、效率低(2轮RTT)
Basic Paxos的难度是较为出名的,且不易理解; 提交提议、提交提案(日志)内容进行了两轮RTT操作,效率较低。
1.3.2 Multi Paxos
1.角色介绍
减少角色,简化步骤: 由于Basic Paxos存在活锁问题,而且根因是多个Proposer导致的。Multi Paxos则提出了一个新的概念——Leader,由于Basic Paxos存在两轮RTT导致的效率低下问题,Multi Paxos则通过Leader角色 + 在消息中增加一个随机的I(the round number I is included along with each value which is incremented in each round by the same Leader),使得两轮RTT只在竞选Leader时出现,其余情况只进行一轮RTT
Leader:唯一的Proposer,所有请求都需经过此Leader
2.基本流程
- 选主流程
1、从Basic Paxos Protocol的角色关系出发:
- 正常请求操作流程
1、从Basic Paxos Protocol的角色关系出发:
1.4 ZAB
ZAB的全称是Zookeeper atomic broadcast protocol,是Zookeeper内部用到的一致性协议。基本与Raft相同。在一些名词的叫法上有些区别:如ZAB将某一个leader的周期称为epoch,而Raft则称为Term。实现上也有些许不同:Raft保证日志连续性,心跳方向为Leader至Follower。ZAB则相反。
第2章 Nacos一致性协议
对于非临时数据,Nacos采用的是Raft协议,而临时数据Nacos采用的是Distro协议。
2.1 Raft
演示:
http://thesecretlivesofdata.com/raft/Spring Cloud Alibaba Nacos 在 1.0.0 正式支持 AP 和 CP 两种一致性协议,其中 CP一致性协议实现,是基于简化的 Raft 的 CP 一致性。
在1.4版本正式使用distro
Nacos支持集群模式,很显然。 而一旦涉及到集群,就涉及到主从,那么nacos是一种什么样的机制来实现的集群呢?
nacos的集群类似于zookeeper, 它分为leader角色和follower角色, 那么从这个角色的名字可以看出来,这个集群存在选举的机制。 因为如果自己不具备选举功能,角色的命名可能就是master/slave了, 当然这只是我基于这么多组件的命名的一个猜测。
Raft协议是一种强一致性、去中心化、高可用的分布式协议,它是用来解决分布式一致性问题的,相对于大名鼎鼎的Paxos协议,Raft协议更容易理解,并且在性能、可靠性、可用性方面是不输于Paxos协议的。许多中间件都是利用Raft协议来保证分布式一致性的,例如Redis的sentinel,CP模式的Nacos的leader选举都是通过Raft协议来实现的。因为Nacos的一致性协议是采用的Raft协议。
2.1.1 选举算法
Nacos集群采用raft算法来实现,它是相对zookeeper的选举算法较为简单的一种。 选举算法的核心在 RaftCore 中,包括数据的处理和数据同步
- 在Raft中,节点有三种角色:
Leader:负责接收客户端的请求
Candidate:用于选举Leader的一种角色
Follower:负责响应来自Leader或者Candidate的请求
- 选举分为两个时间点:
服务启动的时候
leader挂了的时候
所有节点启动的时候,都是follower状态。 如果在一段时间内如果没有收到leader的心跳(可能是没有 leader,也可能是leader挂了),那么follower会变成Candidate。然后发起选举,选举之前,会增加 term,这个term和zookeeper中的epoch的道理是一样的。
follower会投自己一票,并且给其他节点发送票据vote,等到其他节点回复
- 在这个过程中,可能出现几种情况
收到过半的票数通过,则成为leader
被告知其他节点已经成为leader,则自己切换为follower
一段时间内没有收到过半的投票,则重新发起选举
- 选举的几种情况:
第一种情况,赢得选举之后,leader会给所有节点发送消息,避免其他节点触发新的选举
第二种情况,比如有三个节点A B C。A B同时发起选举,而A的选举消息先到达C,C给A投了一 票,当B的消息到达C时,已经不能满足上面提到的第一个约束,即C不会给B投票,而A和B显然都不会给对方投票。A胜出之后,会给B,C发心跳消息,节点B发现节点A的term不低于自己的term, 知道有已经有Leader了,于是转换成follower。
第三种情况, 没有任何节点获得majority(超过半数的)投票,可能是平票的情况。加入总共有四个节点 (A/B/C/D),Node C、Node D同时成为了candidate,但Node A投了NodeD一票,NodeB投 了Node C一票,这就出现了平票 split vote的情况。这个时候大家都在等啊等,直到超时后重新发起选举。如果出现平票的情况,那么就延长了系统不可用的时间,因此raft引入了randomized election timeouts来尽量避免平票情况.
2.1.2 源码
2.1.2.1 RaftCore初始化
随着nacos服务启动就初始化
这里有几个核心概念或组件:
1.peer:代表每台nocas机器,记录着一台server的投票相关的元数据信息,比如本机的ip,投票给谁(votefor),AtomicLong类型的term,记录本地服务第几次发起的投票,状体(leader/follower),leader选举间隔时间等。
2.peers:是个RaftPeerSet类型,实际上记录了整个集群所有peer的信息。
3.notifier:一个线程,用作事件通知。
2.1.2.2 RaftCore.MasterElection
2.1.2.3 RaftController.vote
收到投票的票据
2.1.2.4 RaftCore.receivedVote
- 这个方法主要就是处理自己的选票的,当收到其他机器拉票的请求的时候,会比较term,如果自身的term大于全程请求机器的term,并且自己的选票没有还没投出去的时候,就把选票投给自己。
- 否则将选票投给远程请求的机器,并且把自己的状态设置为follower,并且把信息返回出去。
2.1.2.5 decideLeader
decideLeader,表示用来决策谁能成为leader
2.1.2.6 addInstance
比如我们在注册服务时,调用addInstance之后,最后会调用 consistencyService.put(key, instances); 这个方法,来实现数据一致性的同步。
InstanceController.register---->registerInstance----->addInstance------>consistencyService.put(key, instances);
2.1.2.7 RaftConsistencyServiceImpl.put
调用 consistencyService.put 用来发布类容,也就是实现数据的一致性同步。
2.1.2.8 RaftCore.signalPublish
2.1.2.9 onPublish
我们看其他节点在接受到leader请求时是如何处理的,我们查看/v1/ns/raft/datum/commit接口的代码
主要的核心在于raftConsistencyService.onPut(datum, source);我们进入到该方法中
