RabbitMQ集群架构

RabbitMQ中默认带有集群功能,这是它与其他消息队列的一个最明显的区别

RabbitMQ内建集群的目的有

  • 允许消费者和生产者在Rabbit节点崩溃的情况下继续正常工作
  • 通过添加更多的节点来线性扩展消息通信的吞吐量

RabbitMQ集群不能保证消息万无一失,默认情况下,RabbitMQ不会将队列上的内容复制到集群中的其他节点,那么如果某个节点崩溃了,那么消息就丢失了

RabbitMQ四种元数据

  • 队列元数据:队列属性(名称,排他,持久化等)
  • 交换器元数据:交换器属性(名称,排他,持久化等)
  • 绑定元数据:消息路由的依据
  • vhost元数据:为每个vhost内的队列、交换器和绑定提供命名空间和安全属性

集群中的队列

单节点的RabbitMQ中,队列的所有信息(元数据,状态,内容)等都完全存储在该节点上,但是集群中的RabbitMQ,队列的信息只会在单个节点而不是在所有节点上,当队列被创建后,只有在队列创建的节点上才能知道队列的信息,集群中其他节点都不知道该队列的信息,如果集群崩溃,那么该队列所有的信息都将丢失(与队列相关的绑定,附加在队列上的消费者等信息)。新版本(2.6.0之后)的RabbitMQ提供了镜像集群来解决该问题

RabbitMQ为什么不将队列信息复制到所有的集群节点上

  • 存储空间:每个节点都要存储相同数量的队列信息,一份消息占用多个磁盘空间
  • 性能:每发布一条消息都需要将消息复制到所有的节点上,增加网络和磁盘的负载

分布交换器

实际上,交换器只是一个保存了路由键和队列对应关系的查询表,真正路由消息的其实是信道

RabbitMQ的消息路由机制:当消息发布时,信道会到交换器中查询匹配的队列信息,查询到队列信息后,信道会将消息路由到匹配路由键的队列中,队列在本质就是队列在Erlang的地址

集群中的每个节点都会保存交换器的信息,这样在节点故障后,故障节点上的生产者重新连接到集群上,就可以继续向交换器中发布消息了

如果消息在发布到信道后,路由到队列前节点崩溃,会出现什么情况

消息可能会丢失

为了保证消息不丢失,可以采取两种方式

  • AMQP事务机制
  • 发送方确认机制:当消息发送后,将信道置为confirm模式,RabbitMQ会为每条消息生成一个唯一ID,并且维护一个该ID列表,如果消息被成功发送,那么该ID会从ID列表中移除,否则表示该消息发送失败

内存节点还是磁盘节点

  • 内存节点:将队列、交换器、绑定、用户、权限、vhost等元数据信息都存储在内存
  • 磁盘节点:将队列、交换器、绑定、用户、权限、vhost等元数据信息都存储在磁盘

单节点的RabbitMQ只支持磁盘节点,否则重启后所有数据都将丢失,集群中RabbitMQ可以将部分节点设置为内存节点

数据存储在内存中的主要原因是为了加快对队列、交换器等操作的速度

当在集群中声明队列、交换器或者绑定时,这些操作会等到所有集群节点都成功提交元数据后才返回,比如你声明一个交换器,然后要等到集群中所有节点都提交完成后,这次声明才算完成,对于内存节点,这些操作只是写入内存,但是对于磁盘节点,这些操作都是要进行磁盘I/O操作的,这个过程是非常缓慢的,会严重拖累RabbitMQ的吞吐量

RabbitMQ要求集群中至少有一个节点是磁盘节点,通常会设置为两个,保证任何时候至少有一个节点是可用的,在任何时候都能够对集群元数据信息进行变更操作

当节点加入或者离开集群时,必须要将该变更通知到至少一个磁盘节点,如果集群中的磁盘节点都崩溃了,那么集群还可以继续路由消息,但是不能做任何更改集群元数据信息的操作,包括

  • 创建队列
  • 创建交换器
  • 创建绑定
  • 添加用户
  • 更改权限
  • 添加或删除集群节点

内存节点重启后,会连接到预先配置的磁盘节点,下载当前集群元数据拷贝,如果该内存节点不知道所有的磁盘节点,并且它预设置的磁盘节点崩溃,那么它就无法找到集群,所以在添加内存节点时,要把所有的集群节点都告知给内存节点