ODL控制器的分布式集群底层采用raft协议实现,为啥使用raft协议还没有明确说明,但是我们看内存数据库redis的集群也是才raft协议,因为其能够保证redis的高可用性,也许是ODL的架构师从redis使用经验中总结出来的,毕竟redis内存数据库经过了市场的考验与认可。本章主要是结合自己在研究ODL分布式集群中的一些总结,有错误理解的地方还请网友指正。
一、Raft协议入门
Raft大概将整个过程分为三个阶段,leader election,log replication和commit(safety)。
工作流程如下:
1) 复制日志到所有follower结点(replicate entry)
2) 大部分结点响应时才提交日志
3) 通知所有follower结点日志已提交
4) 所有follower也提交日志
5) 现在整个系统处于一致的状态
当follower在选举超时时间(election timeout)内未收到leader的心跳消息(append entries),则变成candidate状态。为了避免选举冲突,这个超时时间是一个150~300ms之间的随机数。
1.1 Consensus一致性
Consensus一致性是指多个服务器在状态达成一致,但是在一个分布式系统中,因为各种意外可能,有的服务器可能会崩溃或变得不可靠,它就不能和其他服务器达成一致状态。这样就需要一种Consensus协议,一致性协议是为了确保容错性,也就是即使系统中有一两个服务器宕机,也不会影响其处理过程。
为了以容错方式达成一致,不可能要求所有服务器100%都达成一致状态,只要超过半数的大多数服务器达成一致就可以了(只要保证n/2+1节点正常就能够提供服务),假设有N台服务器,N/2 +1 就超过半数,代表大多数了。
因此,假设有3台ODL服务器做集群,必须保证有2台以上(包括2台)ODL服务器达成一致,也就是说必须要有2台ODL服务器还活着,否则整个集群将变得不可用。所以3台服务器组成的集群可以容错1台(也就是只能容许1台服务器挂掉),5台服务器组成的集群可以容错2台,等等。
ODL控制器集群采用Raft一致性算法进行Leader的选举。每台控制器都有3种状态,分别为Follower、Candidate、Leader,各个角色如下:
◆ Leader: 处理所有客户端交互,比如日志复制等,一般一次只有一个Leader
◆ Follower: 类似选民,完全被动
◆ Candidate候选人: 类似Proposer律师,可以被选为一个新的*
1.2 控制器Leader选举过程
1. Raft的选举由定时器来触发,每个节点的选举定时器时间都是不一样的,开始时状态都为Follower,任何一个服务器都可以成为一个候选者Candidate,它向其他服务器Follower发出要求选举自己的请求:
2. 其他服务器同意了,发出OK。
注意如果在这个过程中,有一个Follower宕机,没有收到请求选举的要求,因此候选者可以自己选自己(每个候选人都是选自己的),只要达到N/2 + 1 的大多数票,候选人还是可以成为Leader的。
3. 这样这个候选者就成为了Leader*,它可以向选民也就是Follower们发出指令,比如进行日志复制。
4. 以后通过心跳进行日志复制的通知
5. 如果一旦这个Leader当机崩溃了,那么Follower中有一个成为候选者,发出邀票选举。
6. Follower同意后,其成为Leader,继续承担日志复制等指导工作:
7.如果同时又两个Follower变成Candidate参与Leader的竞选,则进入分裂选举(Split Vote)。
二、日志复制
一致性算法都采用同步操作日志进行数据同步:将数据的操作写在日志中,同步日志之后,数据节点根据操作日志中的操作顺序进行数据操作,从而保证数据节点的数据一致性。
假设Leader*已经选出,这时客户端发出增加一个日志的要求,比如日志是"sally":
Leader要求Followe遵从他的指令,都将这个新的日志内容追加到他们各自日志中:
大多数follower服务器将日志写入磁盘文件后,确认追加成功,发出Commited Ok:
在下一个心跳heartbeat中,Leader会通知所有Follwer更新commited 项目。对于每个新的日志记录,重复上述过程。
如果在这一过程中,发生了网络分区或者网络通信故障,使得Leader不能访问大多数Follwers了,那么Leader只能正常更新它能访问的那些Follower服务器,而大多数的服务器Follower因为没有了Leader,他们重新选举一个候选者作为Leader,然后这个Leader作为代表于外界打交道,如果外界要求其添加新的日志,这个新的Leader就按上述步骤通知大多数Followers,如果这时网络故障修复了,那么原先的Leader就变成Follower,在失联阶段这个老Leader的任何更新都不能算commit,都回滚,接受新的Leader的新的更新。
日志复制(Log Replication)主要作用是用于保证节点的一致性,这阶段所做的操作也是为了保证一致性与高可用性;当Leader选举出来后便开始负责客户端的请求,所有事务(更新操作)请求都必须先经过Leader处理,这些事务请求或说成命令也就是这里说的日志,我们都知道要保证节点的一致性就要保证每个节点都按顺序执行相同的操作序列,日志复制(Log Replication)就是为了保证执行相同的操作序列所做的工作;在Raft中当接收到客户端的日志(事务请求)后先把该日志追加到本地的Log中,然后通过heartbeat把该Entry同步给其他Follower,Follower接收到日志后记录日志然后向Leader发送ACK,当Leader收到大多数(n/2+1)Follower的ACK信息后将该日志设置为已提交并追加到本地磁盘中,通知客户端并在下个heartbeat中Leader将通知所有的Follower将该日志存储在自己的本地磁盘中。
三、Raft状态机
Raft把集群中的节点分为三种状态:Leader、 Follower 、Candidate,理所当然每种状态负责的任务也是不一样的,Raft运行时提供服务的时候只存在Leader与Follower两种状态;
◆ Leader(领导者):负责日志的同步管理,处理来自客户端的请求,与Follower保持这heartBeat的联系;
◆ Follower(追随者):刚启动时所有节点为Follower状态,响应Leader的日志同步请求,响应Candidate的请求,把请求到Follower的事务转发给Leader;
◆ Candidate(候选者):负责选举投票,Raft刚启动时由一个节点从Follower转为Candidate发起选举,选举出Leader后从Candidate转为Leader状态;因此在我们查看ODL控制器的log中经常可以看到Candidate转为Leader的日志。
四、Raft的Term周期
在Raft中使用了一个可以理解为周期(第几届、任期)的概念,用Term作为一个周期,每个Term都是一个连续递增的编号,每一轮选举都是一个Term周期,在一个Term中只能产生一个Leader;先简单描述下Term的变化流程: Raft开始时所有Follower的Term为1,其中一个Follower逻辑时钟到期后转换为Candidate,Term加1这是Term为2(任期),然后开始选举,这时候有几种情况会使Term发生改变:
1:如果当前Term为2的任期内没有选举出Leader或出现异常,则Term递增,开始新一任期选举
2:当这轮Term为2的周期选举出Leader后,过后Leader宕掉了,然后其他Follower转为Candidate,Term递增,开始新一任期选举
3:当Leader或Candidate发现自己的Term比别的Follower小时Leader或Candidate将转为Follower,Term递增
4:当Follower的Term比别的Term小时Follower也将更新Term保持与其他Follower一致;
可以说每次Term的递增都将发生新一轮的选举,Raft保证一个Term只有一个Leader,在Raft正常运转中所有的节点的Term都是一致的,如果节点不发生故障一个Term(任期)会一直保持下去,当某节点收到的请求中Term比当前Term小时则拒绝该请求。
五、角色选举
Raft的选举由定时器来触发,每个节点的选举定时器时间都是不一样的,开始时状态都为Follower,某个节点定时器触发选举后Term递增,状态由Follower转为Candidate,向其他节点发起RequestVote RPC请求,这时候有三种可能的情况发生:
1:该RequestVote请求接收到n/2+1(过半数)个节点的投票,从Candidate转为Leader,向其他节点发送heartBeat以保持Leader的正常运转
2:在此期间如果收到其他节点发送过来的AppendEntries RPC请求,如该节点的Term大则当前节点转为Follower,否则保持Candidate拒绝该请求
3:Election timeout发生则Term递增,重新发起选举
在一个Term期间每个节点只能投票一次,所以当有多个Candidate存在时就会出现每个Candidate发起的选举都存在接收到的投票数都不过半的问题,这时每个Candidate都将Term递增、重启定时器并重新发起选举,由于每个节点中定时器的时间都是随机的,所以就不会多次存在有多个Candidate同时发起投票的问题。
有这么几种情况会发起选举,1:Raft初次启动,不存在Leader,发起选举;2:Leader宕机或Follower没有接收到Leader的heartBeat,发生election timeout从而发起选举。
六、安全性(Safety)
安全性是用于保证每个节点都执行相同序列的安全机制,如当某个Follower在当前Leader commit Log时变得不可用了,稍后可能该Follower又会被选举为Leader,这时新Leader可能会用新的Log覆盖先前已committed的Log,这就是导致节点执行不同序列;Safety就是用于保证选举出来的Leader一定包含先前 commited Log的机制;
◆ 选举安全性(Election Safety)
◆ 每个Term只能选举出一个Leader
◆ Leader完整性(Leader Completeness)
这里所说的完整性是指Leader日志的完整性,当Log在Term1被Commit后,那么以后Term2、Term3…等的Leader必须包含该Log;Raft在选举阶段就使用Term的判断用于保证完整性:当请求投票的该Candidate的Term较大或Term相同Index更大则投票,否则拒绝该请求。
总结
上述原理就可以很好的解释为什么ODL控制器,选主是必须满足N/2+1张票才能成为主,一旦集群中某些服务器发生故障时,备份节点必须满足N/2+1才能保证高可用性,也就是说必须有N/2+1节点活着,比如有3台odl控制器做集群,只能容许1台控制器死掉,必须还有2=3/2+1控制器活着,数据库才能正常操作,否则数据库不可操作,一旦数据库不可操作,集群也就废了。