目录
宕机演示
创建队列
创建两个队列,可以在集群中的三个节点都能够看到。
节点1管理界面:
节点2管理界面:
节点3管理界面:
往队列中发送消息
可以在集群中的三个节点都能够看到消息。
节点1管理界面:
节点2管理界面:
节点3管理界面:
关闭队列主节点
现在关闭rabbit2节点
关闭test-queue主节点rabbit2节点后,我们发现test-queue队列中的消息没有了,但是不影响rabbit2为从节点的队列。
那么如何解决这个问题呢?就是下面我们要讲的“仲裁队列”。
仲裁队列
RabbitMQ 的仲裁队列是⼀种基于 Raft ⼀致性算法实现的持久化、复制的 FIFO 队列. 仲裁队列提供队列复制的能⼒, 保障数据的⾼可⽤和安全性. 使⽤仲裁队列可以在 RabbitMQ 节点间进⾏队列数据的复制, 从⽽达到在⼀个节点宕机时, 队列仍然可以提供服务的效果。
仲裁队列是RabbitMQ 3.8版本最重要的改动. 他是镜像队列的替代⽅案. 在 RabbitMQ 3.8 版本问世之前, 镜像队列是实现数据⾼可⽤的唯⼀⼿段, 但是它有⼀些设计上的缺陷, 这也是 RabbitMQ 提供仲裁队列的原因. 经典镜像队列已被弃⽤,并计划在将来版本中移除, 如果当前使⽤经典镜像队列的
RabbitMQ 安装需要迁移, 可以参考官⽅提供的迁移指南。
Raft协议介绍
Raft 是⼀种⽤于管理和维护分布式系统⼀致性的协议, 它是⼀种共识算法, 旨在实现⾼可⽤性和数据的持久性. Raft 通过在节点间复制数据来保证分布式系统中的⼀致性,即使在节点故障的情况下也能保证数据不会丢失.
在分布式系统中, 为了消除单点提⾼系统可⽤性, 通常会使⽤副本来进⾏容错, 但这会带来另⼀个问题,即如何保证多个副本之间的⼀致性?
共识算法(Consensus Algorithm)就是做这个事情的, 它允许多个分布式节点就某个值或⼀系列值达成⼀致性协议. 即使在⼀些节点发⽣故障, ⽹络分区或其他问题的情况下, 共识算法也能保证系统的⼀致性和数据的可靠性.
常⻅的共识算法有: Paxos, Raft, Zab等。
• Paxos: ⼀种经典的共识算法, ⽤于解决分布式系统中的⼀致性问题.
• Raft:⼀种较新的共识算法, Paxos 不易实现, raft是对Paxos算法的简化和改进, 旨在易于理解和实现.
• Zab:ZooKeeper 使⽤的共识算法, 基于 Paxos 算法., ⼤部分和Raft相同, 主要区别是对于Leader的任期, Raft叫做term, Zab叫做epoch, 状态复制的过程中, raft的⼼跳从Leader向Follower发送, ⽽ZAB则相反.
• Gossip: Gossip算法每个节点都是对等的, 即没有⻆⾊之分. Gossip算法中的每个节点都会将数据改动告诉其他节点.
Raft基本概念
Raft 使⽤ Quorum 机制来实现共识和容错, 我们将对 Raft 集群的操作必须得到⼤多数(> N/2)节点的同意才能提交。
当我们向 Raft 集群发起⼀系列读写操作时, 集群内部究竟发⽣了什么呢? 我们先来简单了解⼀下.
Raft 集群必须存在⼀个主节点(Leader), 客⼾端向集群发起的所有操作都必须经由主节点处理. 所以
Raft 核⼼算法中的第⼀部分就是选主(Leader election). 没有主节点集群就⽆法⼯作, 先选出⼀个主节点, 再考虑其它事情。
主节点会负责接收客⼾端发过来的操作请求, 将操作包装为⽇志同步给其它节点, 在保证⼤部分节点都同步了本次操作后, 就可以安全地给客⼾端回应响应了. 这⼀部分⼯作在 Raft 核⼼算法中叫⽇志复制(Log replication).
因为主节点的责任⾮常⼤, 所以只有符合条件的节点才可以当选主节点. 为了保证集群对外展现的⼀致性, 主节点在处理操作⽇志时, 也⼀定要谨慎, 这部分在Raft核⼼算法中叫 安全性(Safety).
Raft算法将⼀致性问题分解为三个⼦问题: Leader选举, ⽇志复制和安全性。
选主
选主(Leader election)就是在集群中抉择出⼀个主节点来负责⼀些特定的⼯作. 在执⾏了选主过程后, 集群中每个节点都会识别出⼀个特定的, 唯⼀的节点作为 leader.
节点角色
在 Raft 算法中,每个节点都处于以下三种⻆⾊之⼀:
• Leader(领导者): 负责处理所有客⼾请求,并将这些请求作为⽇志项复制到所有 Follower. Leader定期向所有 Follower 发送⼼跳消息, 以维持其领导者地位, 防⽌ Follower 进⼊选举过程.
• Follower(跟随者): 接收来⾃ Leader 的⽇志条⽬, 并在本地应⽤这些条⽬. 跟随者不直接处理客⼾请求.
• Candidate(候选者): 当跟随者在⼀段时间内没有收到来⾃ Leader 的⼼跳消息时,它会变得不确定Leader 是否仍然可⽤. 在这种情况下, 跟随者会转变⻆⾊成为 Candidate, 并开始尝试通过投票过程成为新的 Leader。
在正常情况下, 集群中只有⼀个 Leader, 剩下的节点都是 follower。
下图展示了这些状态和它们之间的转换关系:
可以看出所有节点在启动时, 都是follow状态, 在⼀段时间内如果没有收到来⾃leader的⼼跳, 从
follower切换到candidate, 发起选举. 如果收到多数派(majority)的投票(含⾃⼰的⼀票) 则切换到
leader状态. Leader⼀般会⼀直⼯作直到它发⽣异常为⽌.
任期
Raft 将时间划分成任意⻓度的任期(term). 每⼀段任期从⼀次选举开始, 在这个时候会有⼀个或者多个candidate 尝试去成为 leader. 在成功完成⼀次leaderelection之后,⼀个leader就会⼀直节管理集群直到任期结束. 在某些情况下, ⼀次选举⽆法选出 leader, 这个时候这个任期会以没有 leader ⽽结束(如下图t3). 同时⼀个新的任期(包含⼀次新的选举) 会很快重新开始。
Term 更像是⼀个逻辑时钟(logic clock)的作⽤, 有了它,就可以发现哪些节点的状态已经过期. 每⼀个 节点都保存⼀个 current term, 在通信时带上这个 term的值.
每⼀个节点都存储着⼀个当前任期号(current term number). 该任期号会随着时间单调递增. 节点之间 通信的时候会交换当前任期号, 如果⼀个节点的当前任期号⽐其他节点⼩, 那么它就将⾃⼰的任期号更 新为较⼤的那个值. 如果⼀个 candidate 或者 leader 发现⾃⼰的任期号过期了, 它就会⽴刻回到 follower 状态. 如果⼀个节点接收了⼀个带着过期的任期号的请求, 那么它会拒绝这次请求。
Raft 算法中服务器节点之间采⽤ RPC 进⾏通信, 主要有两类 RPC 请求:
• RequestVote RPCs: 请求投票, 由 candidate 在选举过程中发出
• AppendEntries RPCs: 追加条⽬, 由 leader 发出, ⽤来做⽇志复制和提供⼼跳机制
选举过程
Raft 采⽤⼀种⼼跳机制来触发 leader 选举, 当服务器启动的时候, 都是follow状态. 如果follower在
election timeout内没有收到来⾃leader的⼼跳(可能没有选出leader, 也可能leader挂了, 或者leader与follower之间⽹络故障), 则会主动发起选举.
步骤如下:
1. 率先超时的节点, ⾃增当前任期号然后切换为 candidate 状态, 并投⾃⼰⼀票
2. 以并⾏的⽅式发送⼀个 RequestVote RPCs 给集群中的其他服务器节点(企图得到它们的投票)
3. 等待其他节点的回复
在这个过程中, 可能出现三种结果
a. 赢得选举, 成为Leader(包括⾃⼰的⼀票)
b. 其他节点赢得了选举, 它⾃⾏切换到follower
c. ⼀段时间内没有收到majority投票, 保持candidate状态, 重新发出选举
投票要求:
• 每⼀个服务器节点会按照 先来先服务原则(first-come-first-served)只投给⼀个 candidate.
• 候选⼈知道的信息不能⽐⾃⼰的少
接下来对这三种情况进⾏说明:
第⼀种情况: 赢得了选举之后, 新的leader会⽴刻给所有节点发消息, ⼴⽽告之, 避免其余节点触发新的选举.
第⼆种情况: ⽐如有三个节点A B C, A B同时发起选举, ⽽A的选举消息先到达C, C给A投了⼀票, 当B的消息到达C时, 已经不能满⾜上⾯提到的第⼀个约束, 即C不会给B投票, 这时候A就胜出了. A胜出之后, 会给B,C发⼼跳消息, 节点B发现节点A的term不低于⾃⼰的term, 知道有已经有Leader了, 于是把⾃⼰转换成follower.
第三种情况: 没有任何节点获得majority投票. ⽐如所有的 follower 同时变成 candidate, 然后它们都将票投给⾃⼰, 那这样就没有 candidate 能得到超过半数的投票了. 当这种情况发⽣的时候, 每个candidate 都会进⾏⼀次超时响应, 然后通过⾃增任期号来开启⼀轮新的选举, 并启动另⼀轮的RequestVote RPCs. 如果没有额外的措施, 这种⽆结果的投票可能会⽆限重复下去.
为了解决上述问题,Raft 采⽤ 随机选举超时时间(randomized election timeouts) 来确保很少产⽣⽆结果的投票,并且就算发⽣了也能很快地解决。为了防⽌选票⼀开始就被⽠分,选举超时时间是从⼀个固定的区间(⽐如,150-300ms)中随机选择。这样可以把服务器分散开来以确保在⼤多数情况下会只有⼀个服务器率先结束超时,那么这个时候,它就可以赢得选举并在其他服务器结束超时之前发送⼼跳。
Raft协议下的消息复制
每个仲裁队列都有多个副本, 它包含⼀个主和多个从副本. replication factor 为 5的仲裁队列将会有 1个主副本和 4 个从副本. 每个副本都在不同的 RabbitMQ 节点上
客⼾端(⽣产者和消费者)只会与主副本进⾏交互, 主副本再将这些命令复制到从副本. 当主副本所在的节点下线, 其中⼀个从副本会被选举成为主副本, 继续提供服务.
消息复制和主副本选举的操作, 需要超过半数的副本同意. 当⽣产者发送⼀条消息, 需要超过半数的队列副本都将消息写⼊磁盘以后才会向⽣产者进⾏确认, 这意味着少部分⽐较慢的副本不会影响整个队列的性能.
仲裁队列的使用
创建仲裁队列
观察仲裁队列和普通队列的不同
宕机演示
关闭仲裁队列的主节点rabbit2
此时我们发现,关闭仲裁队列的主节点rabbit2之后,仲裁队列的主节点变更为了rabbit,但是队列中的消息依旧存在。