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要求集群中至少有一个节点是磁盘节点,通常会设置为两个
,保证任何时候至少有一个节点是可用的,在任何时候都能够对集群元数据信息进行变更操作
当节点加入或者离开集群时,必须要将该变更通知到至少一个磁盘节点,如果集群中的磁盘节点
都崩溃了,那么集群还可以继续路由消息,但是不能做任何更改集群元数据信息的操作,包括
- 创建队列
- 创建交换器
- 创建绑定
- 添加用户
- 更改权限
- 添加或删除集群节点
内存节点重启后,会连接到预先配置的磁盘节点,下载当前集群元数据拷贝,如果该内存节点不知道所有的磁盘节点,并且它预设置的磁盘节点崩溃,那么它就无法找到集群,所以在添加内存节点时,要把所有的集群节点都告知给内存节点