发送与接收消息的流程
- 生产者发送消息的流程
(1)生产者连接RabbitMQ,建立TCP连接( Connection),开启信道(Channel)
(2)生产者声明一个Exchange(交换器),并设置相关属性,比如交换器类型、是否持久化等
(3)生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
(4)生产者通过bindingKey (绑定Key)将交换器和队列绑定( binding )起来
(5)生产者发送消息至RabbitMQ Broker,其中包含routingKey (路由键)、交换器等信息
(6)相应的交换器根据接收到的routingKey 查找相匹配的队列。
(7)如果找到,则将从生产者发送过来的消息存入相应的队列中。
(8)如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
(9)关闭信道。
(10)关闭连接。 - 消费者接收消息的过程
(1)消费者连接到RabbitMQ Broker ,建立一个连接(Connection ) ,开启一个信道(Channel) 。
(2)消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数, 以及做一些准备工作
(3)等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
(4)消费者确认( ack) 接收到的消息。
(5)RabbitMQ 从队列中删除相应己经被确认的消息。
(6)关闭信道。
(7)关闭连接。
几种模式
-
simple简单模式;
一个生产者,一个队列,一个消费者,不需要经过交换机 -
work工作模式(资源的竞争,轮循);
一个生产者,一个队列,多个消费者 -
publish/subscribe发布订阅(共享资源);
生产者将消息发送到交换机,交换机发送给每一个队列,每个队列下面可以有多个消费者,都是相同的消息,Routing Key为空 -
routing路由模式;
交换机根据Routing Key的名字将消息发送给指定的队列 -
topic 主题模式。
主题模式是在原有的 Routing Key 增加了通配符,可以进行 Routing Key 的模糊匹配,进行更加灵活的消息分发。
*和#,分表是主题模式的通配符,*代表单个关键字,#代表多个关键字。
在实际使用场景中, 路由模式的效率是高于主题模式,实际工作中可以使用路由模式解决的问题就尽量不要采用主题模式。
Mq消息积压怎么处理
-
产生原因有可能是
- 消费端宕机
- 消费端消费能力不足
- 生产端发送流量过大
-
方案一:通常的解决方案就是增加消费端实例。说白了就是增加机器。如果出现线上事故,能申请多少机器就申请多少机器,争取在最短的时间内消费掉积压在MQ中的消息。
-
方案二:如果申请机器行不通,毕竟公司的机器是有限的,此时可以增加消费端的消费能力。在MQ的配置中配置"最大消费者数量"与"每次从队列中获取的消息数量"
-
方案三:如果还是不能解决问题的话,还有另外一种解决方案。紧急上线专门用于记录消息的队列,不多BB,先把MQ中的消息记录到数据库中,然后再慢慢的消化处理。
死信队列是什么
- producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
- 死信来源
1、消息 TTL 过期
2、队列达到最大长度(队列满了,无法再添加数据到 mq 中)
3、消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false.
如何实现延时队列
ttl+死信
MQ如何保证消息的可靠性
-
生产者端
1、事务机制
AMQP协议提供了事务机制,在投递消息时开启事务支持,如果消息投递失败,则回滚事务。
2、confirm -
消费者端
yml中配置acknowledge-mode: manual手动签收
public void receive(String message, @Headers Map<String,Object> headers, Channel channel) throws Exception{
System.out.println(message);
// 唯一的消息ID
Long deliverTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 确认该条消息
if(...){
channel.basicAck(deliverTag,false);
}else{
// 消费失败,消息重返队列
channel.basicNack(deliverTag,false,true);
}
}