RabbitMQ的HA方案
默认情况下,queue可以认为是只存在于它被声明的那个节点中,但是broker和binding可以认为存在于集群中的所有节点中. 可以通过镜像的方式,将queue复制到其它的节点中,以此来提高可用性
镜像队列之间彼此形成了一主多从的关系,当主镜像队列因为某些原因消失时,一个从镜像自动被推选为主镜像
不论客户端连接到哪个结点,它都将连接到主镜像队列中,所有队列的操作也都是通过主镜像队列来完成,这样就保证了队列的FIFO特性
发布到主镜像队列中的消息将会被自动镜像到所有的从镜像中
如果主镜像中的消息确认已经被消费了,那么从镜像会自动将该消息删除
这种镜像的方式并不能将流量分散到各个节点,因为每个节点做的事情是一样的,但是它提高了可用性,如果主镜像队列因为某些原因消失了,那么从镜像可以自动升级为主镜像,保证了队列的可用性
配置镜像
队列镜像的配置是通过policy来完成的,通过ha-mode和ha-params参数来配置
- ha-mode = all, ha-params = null: 所有的队列将被镜像到集群中的所有节点中
- ha-mode = exactly, ha-params = count: 所有的队列将被准确地镜像count份,如果集群中的节点少于count个,那么将会被镜像到所有的节点中
- ha-mode = nodes, ha-params = [node_names]: 所有的队列将被镜像到列表中提供的节点中,节点的名称为rabbitmctl cluster_status中列出的节点名
主队列镜像的位置也是可以配置的,可以在声明队列时设置,也可以在配置文件中设置,也可以通过配置policy中的queue-master-locator参数来完成,可选的参数有:
- min-masters: 将集群中拥有最少主队列镜像的那个节点作为主镜像所在的节点
- client-local: 将声明这个队列的节点作为主镜像所在的节点
- random: 随机挑选集群中的节点
如果新配置的policy导致原先的主镜像所在的节点不在队列的镜像集群中,那么RMQ会保持原来的主镜像所在的节点,直到新的主镜像完成同步后,才会将原来的主镜像队列删除。
1如果原来的队列Q被镜像到了[A, B]节点中,其中A节点为主镜像所在的节点,现在配置了新的policy,将队列中镜像到了[C, D]中,那么RMQ的操作将会是,保留[A, C, D], 直到C或者D完成了队列的同步后,再把A从镜像队列中删除,最后变成[C, D]排它队列: 由于具有排它性的队列当声明它的连接(connection)关闭时,会被自动删除,因此这种类型的队列永远也不会被镜像,而且它也不会被持久化,一旦连接关闭,它就会被自动删除,因此镜像和持久化都是没有意义的。
具体的配置可以通过rabbitmctl的set_policy来完成 ,也可以通过RMQ的管理界面来完成
节点间的同步机制和故障转移
1. 同步机制
节点可以选择在任意时刻加入到集群中,当一个节点新增到集群中时,它的镜像队列是空的,随后所有新增的消息都会自动发布到这个新的节点中,但之前队列中已经有的消息并不会自动出现在这个新的队列镜像中,但是随着时间的推移,这部分队列头部的消息被逐步消费掉,等到它们被全部消费掉的时候,新增节点中的消息将和其它节点的消息完全一致, 这种同步的机制被称为“自然同步”(natural synchronization)
当然也可以选择显式地同步消息队列,但是由于同步会导致队列暂停响应,因此,建议只针对那些不活跃的队列进行显式同步,而对于那些活跃的队列只需要使用上述的方式进行同步即可
显式的同步有两种方式,一种是手动同步,一种是自动同步,可以通过设置ha-sync-mode参数来完成,分别为manual和automatic. 在RMQ3.6.0之后,可以通过设置ha-sync-batch-size参数来批量同步,默认每次只同步一条消息.
2. 故障转移
当集群中的主镜像掉线时,集群中的某个从节点将会被推选为主镜像,如果此时集群中没有其它节点,那么该节点中那些被设置为持久化的消息将被持久化到硬盘,如果重启了该节点,这些消息将会被恢复; 如果某个从镜像原先属于某个集群中,那么当这个从镜像重启时,它将会重新加入到集群中,但是由于此时这个从镜像并不知道自己队列中的内容和主镜像中的内容是否一致,因此重新加入集群中意味着它将抛弃之前持久化的所有消息,以保持与主镜像内容的一致性
当关闭主镜像时,如果此时集群中所有的从节点都处于未同步的状态,那么要分两种情况:
如果关闭请求是来自可控制的事件(如RMQ服务器关闭或者系统关闭)那么RMQ会拒绝进行故障转移,此时它会直接将整个队列关闭,从而避免数据的丢失,但是这时候会造成队列的不可用
如果关闭请求是来自不可控制的事件(如服务器崩溃或节点掉线) 那么即使所有的从节点都处于未同步的状态,那么集群还是会自动进行故障转移,此时保障了集群的可用性,但是有可能会千万数据的丢失
如果想保证集群的可用性,在任何情况下都进行故障转移,那么可以设置ha-promote-on-shutdown参数为always, 这时在任何情况下,只要主镜像关闭了,即使集群中所有的从节点都处于未同步的状态,集群也将进行故障转移操作.
当整个集群全部下线时,最后一个下线的节点必须是最先重新启动的节点,因为它是整个集群最后的主镜像,如果它没有被最先启动,那么所有启动的从镜像将会等待30秒,然后失败; 如果需要将这个主镜像从集群中移除,那么可以通过rabbitmqctl设置forget_cluster_node参数, 关于这个参数含义的解释可以参阅参考文档中的“Loss a master while all slaves are stopped.”一节。
HA方案下消息发送确认及事务的语义
HA方案下,消息发送确认及事务的语义是指所有的镜像队列都确认消息发送或者事务提交完成,也就是说,只要集群中的某个镜像没有对发送的消息进行确认,那么发布者将会被暂停,直到这个镜像发送了确认,或者集群中的其他镜像节点认为这个镜像已经掉线(掉线的检测是RMQ通过间隔一定时间的心跳检测来完成的)
HA方案下,发布者的流量控制是也是通过这种方式来实现的,只有当集群中所有的镜像都允许发布者发布消息的时候,发布者才可以继续发布消息,否则它只能继续等待或者直到集群中的其它节点认为该节点已经掉线
有些时间,消费者可能会需要知道集群中的某个节点掉线的通知,这可以通过设置x-cancel-on-ha-failover参数来实现,具体可以参见文档
HA方案下节点掉线的处理和语义
如果是从镜像掉线,那么主镜像依然是主镜像,其它的从镜像也不会得到任何通知或者采取任何操作
如果是主镜像掉线,那么以下的操作将会被执行:
最老的那个从镜像将会被推选为新的主镜像,因为这样才能使丢消息的概率降到最低
如果所有的从镜像都没有处于“已同步”的状态,那么那些只存在于主镜像中的消息将会丢失
新的主镜像会认为之前所有到主镜像的连接都已经被突然中断,因此它会把所有没有收到消费确认的消息重新推到队列中,不管这种没有确认是由于网络中断造成的,还是由于原来的主镜像没有把相应的确认消息同步给它们造成的,这种做法可能会导致消费者重新消费到一条它已经处理过的消息
所有到原来的主镜像的连接都将被取消
不管是主镜像掉线还是从镜像掉线,都不会影响到发布者的发布确认,因此从发布者的角度来讲,发布消息到镜像集群中和发布消息到其它任何类型的队列中是没有区别的. 另外,在掉线期间发布的消息也不会丢失,因为所有的消息都是直接发布到镜像集群中的所有节点中