上篇文章:
目录
仲裁队列是基于Raft一致性算法实现的队列。其主要作用是提供复制能力,向其它节点复制消息从而保证集群的高可用性。要介绍仲裁队列首先就需要了解Raft算法:
1 Raft一致性算法
Raft一致性算法是一种通过投票选择主节点来保证节点之间达成共识的算法。通过选举Leader(主节点),由Leader和客户端进行交互,负责把客户端的操作打包成日志然后把日志同步给其它从节点(日志复制),只有大部分从节点都成功同步并返回确认主节点才会认为此次操作成功。
关于Raft算法的具体细节见原文:
https://web.stanford.edu/~ouster/cgi-bin/papers/raft-atc14
1.1 角色划分和相关名词
Raft算法共有三种角色:
Leader(领导者):Leader就相当于主节点,负责和客户端交互和日志复制,并且定期向所有Follower发送心跳包,防止Follower以为Leader挂掉而进入选举Leader的流程。
Follower(跟随者):Follower相当于从节点,接受来自Leader的日志并同步到本地,作用就是集群的副本。
Candidate(候选人):当Follower一段时间内接收不到Leader的心跳包(认为其挂掉了),就会出发选举Leader的流程,此时Follower变为Candidate。
在三种角色的划分下,整个集群的工作时间被划分为两个部分:选举期(election)和任期(term)。选举期就是进行Leader选举时间的投票时期,Leader选举成功后就会进入term,任期就是集群正常工作的时期。
term长度任意长(如果某次选举期没有选出Leader就认为该term长度为0,即没有任期)。每个节点内部都记录着current term(当前任期号,单调递增),节点之间通信时会携带该任期号,当节点任期号小于通信节点的任期号,就会把当前任期号改为较大值。Candidate(可能没有竞争过其它Candidate)或Leader(可能之前宕机后恢复或者Leader网络恢复正常)如果接收到新Leader的心跳包,就会发现自己已经的term已经过期了,就会恢复Follower角色并修改任期号为最新值。
节点之间采用RPC通信,有两种类型的请求:
RequestVote RPCs:请求投票,Candidate在选举过程中发出。
AppendEntries RPCs:追加条目,Leader发出的日志复制和心跳机制。
1.2 Leader选举
Leader选举有三条规则:
1.每个节点只有一票;
2.Candidate超过半数节点投票就会选举为Leader;
3.投票按照先来先到原则进行投。
而触发选举有两个时机,一是集群启动的初始时刻,此时没有Leader,率先超过心跳包过期时间的进入Candidate发起投票。二是其它节点没有及时接受到心跳包(故障或网络问题),先发现问题的先进入Candidate发起投票。因此假设集群有5个节点,Leader选举流程如下:
初始情况下,集群没有Leader,节点外圈的环形进度条表示超时时间。S3节点会率先超时成为Candidate。
S3成为Candidate后发起投票,并先给自己投了一票,上图绿色的则是请求投票的数据包(RequestVote RPCs)。
其它节点接受到投票请求后把各自唯一一票投给S3,于是S3接收到5票(超过半数投票),因此成为Leader。
S3成为Leader后向其它节点告知新的Leader的信息,发送心跳包(AppendEntries RPCs),如上图黄色数据包。待接受其它节点的响应后,便开始正常的工作流程,如与客户端交互、日志复制等,在任期期间,S3会不断向Follower发送心跳包维持Leader任期。
对于Candidate除了成为Leader,还可能出现其它两种情况:一是其它节点成为Leader;二是多个Candidate平票,无法选出Leader。
假设发生其它节点成为Leader:
S3先成为Candidate,S1后成为Candidate,因此S3获得3票,S1只获得2票,于是S3成为Leader。S3成为新Leader后向所有节点发送心跳包,此时S1处于Candidate状态接收到心跳包后得知S3的term高于自己,说明已经有新Leader,于是变为Follower,开始正常工作。
假设发生多个Candidate平票:
这种情况常发生在偶数个节点参与投票,并且有多个Follower同时成为Candidate的时机。比如S1和S3同时成为Candidate,并各自收到其它节点1票和自己的一票,平票状态无法选出Leader,因此会进行下一轮投票。
但是如果由于同时成为Candidate很有可能结束投票的时间也一致,因此下一轮还可能同时成为Candidate并平票。因此Raft算法采用随机选举超时时间:每个Candidate的投票期的时间在一个区间内随机生成,保证各个Candidate的选举时间不一样,因此就很难出现同时选举并平票的情况。
1.3 日志复制
成为Leader的节点负责和客户端进行通信,其它节点作为副本。Leader把和客户端的交互的消息打包成日志,并同步给其它节点。只有其它节点超过半数(同步较慢的节点并不影响整个集群的性能)都把消息同步到本地,并返回确认,Leader节点才会认为此次操作成功。
而仲裁队列就是上述Raft算法运行的集群上的队列,每个仲裁队列有一个主和多个从副本,主副本在主节点(创建仲裁队列的节点是主节点)上,从副本在从节点上。由主副本把消费复制到从副本上,当主副本挂掉从副本就会参与选举Leader成为新的主副本进行工作。
2 仲裁队列使用
这里使用SpringBoot来创建和使用仲裁队列:
2.1 配置文件
spring:
rabbitmq:
addresses: amqp://admin:admin@192.168.159.150:5673/testVirtual
2.2 声明仲裁队列
public class RabbitMQConnection {
public static final String QUORUM_QUEUE = "quorum.queue";
}
@Configuration
public class RabbitMQConfig {
@Bean("quorumQueue")
public Queue quorumQueue(){
return QueueBuilder.durable(RabbitMQConnection.QUORUM_QUEUE).quorum().build();
}
}
2.3 生产者
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Resource(name = "rabbitTemplate")
private RabbitTemplate rabbitTemplate;
@RequestMapping("quorum")
public String quorum() {
rabbitTemplate.convertAndSend("", RabbitMQConnection.QUORUM_QUEUE, "Hello SpringBoot RabbitMQ");
return "发送成功";
}
}
2.4 演示仲裁队列的复制
仲裁队列所在的主副本在rabbitmq1节点上,管理界面登录的是rabbit节点,可以发现,我们向rabbitmq1的仲裁队列上发送消息,成功发送:
上述+2表示还有2个副本。仲裁队列副本默认有5个,1主4从副本。当集群中节点的个数小于5个,比如4个节点,则1主3从副本;当集群中节点数大于五个,比如6个节点,则默认只有5个副本,1主4从副本,从副本随机分配在除主节点外的其它5个节点上(有一个节点会没有从副本)。
当rabbitmq1节点宕机时,如果是普通队列,所有节点上的消息都会消失,而仲裁队列其它节点消息还会存在:
可以发现,在rabbitmq1宕机后,发生选举rabbitmq2成为新的Leader,而rabbit还是Follower。
下篇文章: