文章目录
- RabbitMQ的Broker、Cluster、vhost
- 元数据
- channel、exchange和queue是什么
- 消息时基于什么传输
- 单node节点和多node节点构成的Cluster声明queue、exchange,以及binding有什么不同
- blackholed问题
- 消息怎么路由
- 如何保证消息正确的发送至RabbitMQ
- 如何保证消息接收方消费了消息
- 如何避免消息重复投递或重复消费
- 为什么不对所有的message使用持久化机制
- 如何保证消息不丢失
- 死信队列
- Cluster、mirrored queue以及warrens机制分别解决了什么问题?
- 如何实现个高可用?
- RabbitMQ丢失数据
- RabbitMQ消费消息的顺序性
RabbitMQ的Broker、Cluster、vhost
- Broker:是指一个或多个node的逻辑分组,且node上运行着RbbitMQ应用程序
- Cluster:在Broker基础上,增加了node直接共享元数据的约束
- vhost:虚拟Broker,其内部均含有独立的queue、exchange和binding,最重要的是其拥有独立的权限系统,可以做到vhost范围的用户控制
元数据
在非Cluster模式下,元数据主要分为:
- Queue元数据:queue名字和属性等
- Exchange元数据:exchange名字、类型和属性
- Binding元数据:存放路由关系的查找表
- Vhost元数据:vhot范围内针对前三者的名字空间约束和安全属性设置
channel、exchange和queue是什么
- queue具有自己的进程
- exchange内部实现为保存binding关系的查找表
- channel是实际进行路由工作的实体,即负责按照routing_key将message投递给queue
由AMQP协议可知,channel是真实TCP连接之上的虚拟连接,所有的AMQP命令都是通过channel发送的,切每一个channel有唯一的ID
一个channel只能被单独一个操作系统线程使用,故投递在特定channel上的message是有顺序的,但一个操作系统线程允许使用多个channel
消息时基于什么传输
由于TCP连接的创建和销毁开销交大,且并发数受系统资源限制,会造成瓶颈。RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP链接内的虚拟连接,且每条TCP连接时的信道数量没有限制
单node节点和多node节点构成的Cluster声明queue、exchange,以及binding有什么不同
单node上声明queue时,只要该node上相关的元数据进行更改,就会得到Queue.Declare-ok回应;而cluster上声明,则需全部node都进行元数据更新后才会得到
blackholed问题
blackholed问题是指,向exchange投递了message,而由于各种原因导致该message丢失,但发送者却不知道,可导致blackholed的情况:
- 向未绑定queue的exchange发送message
- exchange以binding_key key绑定queue,但向exchange发送message使用的routing_key是不同的key
消息怎么路由
消息路由必须有三部分:交换机、路由、绑定
- 生产者把消息发布到交换机上
- 绑定决定了消息如何从路由机路由到 特定的队列
- 消息最终到达队列,被消费者接受
详细来讲:
- 消息发布到交换机时,消息将拥有一个路由键,在消息创建时设定
- 通过队列路由键,可以把队列绑定到交换机上
- 消息到达交换机后,rabbitmq会将消息的路由键与队列的路由键进行匹配(针对不同的交换机有不同的路由规则)。如果能够匹配到队列,则消息会投递到相应队列中;如果不能匹配任何队列,消息将进入黑洞
常见的交换机类型:
- direct:路由键完全匹配
- fanout:广播到所有绑定队列
- topic:可以使用来自不同源头的消息能够到达同一个队列。使用topic交换机时,可以使用通配符
如何保证消息正确的发送至RabbitMQ
发送方确认模式
- 发送方确认模式: 将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者信息被写入磁盘后,信道会发送一个确认给生产者(包含消息唯一ID)。如果RabbitMQ发送内部错误从而导致消息丢失,会发送一条nack(未确认)消息
- 发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者引用陈谷的回调方法就会被触发来处理确认消息
如何保证消息接收方消费了消息
接收方消息确认机制: 消费者接受到每一条消息后都必须进行确认(消息接受和消息确认是两个不同操作)。只有消费者确认了消息,rabbitmq才能安全的把消息从队列中删除
存在的特殊情况:
- 如果消费者接受到消息,在确认之前断开了连接或取消订阅,rabbitmq会认为消息没有被分发,然后重新分发给下一个订阅的消费者。此时存在消息重复消费,则需要根据bizid去重
- 如果消费者接收到消息却没有确认消息,连接也未断开,则rabbitmq认为该消费者繁忙,将不会给该消费者分发更多的消息
如何避免消息重复投递或重复消费
在消息生成时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列
在消息消费时,要求消息体重必须要有一个bizid作为去重和幂等的依据,避免同一条消息被重复消费
为什么不对所有的message使用持久化机制
写入磁盘比写RAM慢,导致性能下降,吞吐量可能降低10倍
如何保证消息不丢失
消息持久化的前提是:将交换机/队列的durable属性设置为true,表示交换机/队列是持久交换机/队列,在服务器崩溃或重启之后不需要重新创建交换机/队列
如果消息想要从崩溃中恢复,那么消息必须:
- 在消息发布前,通过把它的投递模式选项设置为2(持久),来吧消息标记成持久化
- 将消息发送到持久交换机
- 消息到达持久队列
恢复方式:将它们写入磁盘的一个持久化日志文件:
- 当发布一条持久性消息到持久交换机上,rabbitmq会在消息提交到日志文件后才发送响应(如果消息路由到了非持久队列,它会自动从持久化日志中移除)
- 一旦消费者从持久队列中消费了一条持久化消息,rabbitmq会在持久化日志中把这条记录标记为等待垃圾收集。如果持久化消息在被消费之前重启,那么rabbitmq会自动重建交换机和队列,并重播持久化日志文件中的消息到合适的队列或者交换机上
死信队列
Dead-Letter-Exchange。当消息在一个队列中变成死信之后,它能被重新publish到另一个Exchange,消息变成死信有几种情况:
- 消息被拒绝,并且requeue=false
- 消息TTL过期
- 队列达到最大长度
用途:延迟消息
Cluster、mirrored queue以及warrens机制分别解决了什么问题?
-
Cluster:
1. 解决当cluster中任意node失效后,生产者和消费者均可以通过其他node继续工作,提高了可用性;另外可以通过增加node数量增加cluster的消息吞吐量
2. 本身不负责message的可靠性问题(需由生产者自行解决);cluster无法解决跨数据中心的问题
3. 可以通过HAProxy解决node选择问题,业务不需知道cluster中多个node的ip,利用HAProxy做负载 -
Mirrored queue
1. 解决使用cluster时创建的queue的完整信息仅存在于单一node上的问题
2. 若想正确使用:
消费者需提供consumer cancellation notification机制
消费者必须能够正确处理重复message -
Warrens
解决cluster中message可能被blackholed的问题,即不能接受生产者不听的republish message,但mq服务无回应的情况:
如何实现个高可用?
单机模式
普通集群模式
镜像集群模式
- 单机模式:单台服务器启动的节点
- 普通集群模式:多台机器人启动多个mq实例
- 创建的queue只会放在一个mq实例上,但每个实例都同步queue元数据
- 消费的时候,实际上如果镰刀另一个实例,那么那个实例会从queue所在实例拉数据过来
- 缺点:MQ集群内部可能产生大量的数据传输;可用性无保障,如果queue所在节点宕机,数据就丢失了
- 镜像集群模式(高可用)
创建的queue,无论元数据还是queue里的消息都会存在多个实力上,每个mq节点都包含这个queue的全部数据。每次写消息到queue都会自动把消息同步到多个实例的queue上
好处:任何一个几点宕机,可以从其他节点获取数据
坏处:性能开销交大,需同步所有机器;不是分布式,没有扩展性
RabbitMQ丢失数据
三种情况:
-
生产者向mq发送消息的过程中丢失了
解决方案:
1. 事务:生产者发送数据之前开启事务 channel.txSelect,然后发送消息
缺点:由于事务机制,吞吐量会降低
2. confirm功能:生产者开启confirm功能,每次写入消息会分配一个唯一的id;如果写入mq,mq会回传一个ack消息,表示接收到了;如果mq没能处理这个消息会回调一个nack接口,告诉你消息失败了,可以尝试重新发送 -
生产者向mq发送消息,mq接收到消息,暂存在内存中还没消费,挂掉了
解决方案:
Broker丢失了数据,此时需要开启持久化,设置持久化的两个步骤:
1. 创建queue的时候将其设置为持久化
2. 第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化,此时mq会将消息持久化到磁盘上 -
mq向消费者发送消息,消费者还未处理,挂掉了
解决方案:
关闭mq自动的ack,采用手动的ack形式,可以通过一个api来调用。
RabbitMQ消费消息的顺序性
一个queue里,相同的key交给同一个worker来执行。单条信息ack。前提是一个queue只能对应一个consumer