目录
一、什么是死信交换机
当一个队列中信息满足下列情况之一时,可以成为死信(dead letter)
&消费者使用basic.reject(RejectAndDontRequeueRecoverer处理策略)或bastic.nack(ImmediateRequeueMessageRecoverer处理策略)消费失败,requeue(重新入队)设置参数为false;
&消息是一个过期消息,过期无人消费;
&要投递的队列消息堆积满了,最早的消息成为死信;
如果该队列配置了dead-letter-exchange属性,指定交换机,队列设置dead-letter-routing-key属性,设置死信交换机与死信队列的RoutingKey,那么队列中的死信就会投递到这个交换机中,而这个交换机成为死信交换机(Dead Letter Exchange,简称DLX)。
二、TTL
TTL,也就是Time-To-Live。如果队列中的消息TTL结束仍未消费则会成为死信,TTL超时分为两种情况:
&消息所在的队列设置了存活时间;
&消息本身设置了存活时间;
消息超时的两种方式:
&给队列设置超时时间ttl,进入队列超过ttl的消息变为死信;
&给消息设置超时时间,队列接受消息超过ttl时间未消费;
&两者共存时,以ttl时间短的为准。
三、代码实现
生产者(publisher)
RabbitConfig
// 死信,延迟队列 @Bean public Queue queueA(){ Map<String,Object> config = new HashMap<>(); //message在该队列queue的存活时间最大20秒 config.put("x-message-ttl",20000); //x-dead-letter-exchange参数是设置该队列的死信交换器(DLX) config.put("x-dead-letter-exchange","ExchangeB"); config.put("x-dead-letter-routing-key","bb"); return new Queue("queueA",true,false,false,config); } @Bean public DirectExchange ExchangeA(){ return new DirectExchange("ExchangeA"); } @Bean public Binding bindingA(){ return BindingBuilder .bind(queueA()) .to(ExchangeA()) .with("aa"); } @Bean public Queue queueB(){ return new Queue("queueB"); } @Bean public DirectExchange ExchangeB(){ return new DirectExchange("ExchangeB"); } @Bean public Binding bindingB(){ return BindingBuilder .bind(queueB()) .to(ExchangeB()) .with("bb"); }
TestController
@RequestMapping("/send5") public String send5() throws Exception{ template.convertAndSend("ExchangeA","aa","1232321412242"); return "🤣"; }
消费者(consumer)
ReceiverQB
@Component @SuppressWarnings("all") @Slf4j @RabbitListener(queues = "queueB") public class ReceiverQB { @RabbitHandler public void process(String id) { log.warn("QB接收到:" + id); //去数据库做修改 //更改数据库的状态为为取消(id一致且订单未支付的情况下 // update order set status=-1 where id=#{id} and status=1 } }
效果展示
四、延迟队列
利用ttl结合死信交换机,实现了消息发出后,消费者延迟收到消息的结果,这种消息队列成为延迟队列(Delay Queue)模式。
延迟队列使用的场景:
&用户下单后超过15分钟未支付取消支付;
&预约20分钟会议,到时间自动通知参会人员;
五、消息发送确认
5.1.发送的消息怎么样才算失败或成功?如何确认?
当消息无法路由到队列时,确认消息路由失败。消息成功路由时,当需要发送的队列都发送成功后,进行确认消息,对于持久化队列意味着写入磁盘,对于镜像队列意味着所有镜像接收成功
通过实现 ConfirmCallback 接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中
@Component
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback{@Autowired
private RabbitTemplate rabbitTemplate;@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this); //指定 ConfirmCallback
}@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("消息唯一标识:"+correlationData);
System.out.println("确认结果:"+ack);
System.out.println("失败原因:"+cause);
}
还需要在配置文件添加配置
spring:
rabbitmq:
publisher-confirms: true
-
通过实现 ReturnCallback 接口,启动消息失败返回,比如路由不到队列时触发回调
@Component
public class RabbitTemplateConfig implements RabbitTemplate.ReturnCallback{@Autowired
private RabbitTemplate rabbitTemplate;@PostConstruct
public void init(){
rabbitTemplate.setReturnCallback(this); //指定 ReturnCallback
}@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息主体 message : "+message);
System.out.println("消息主体 message : "+replyCode);
System.out.println("描述:"+replyText);
System.out.println("消息使用的交换器 exchange : "+exchange);
System.out.println("消息使用的路由键 routing : "+routingKey);
}
}
还需要在配置文件添加配置
spring:
rabbitmq:
publisher-returns: true
消息消费者如何通知 Rabbit 消息消费成功?
消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK
-
自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
-
如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
-
如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者
-
如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限
-
ACK 机制还可以起到限流作用,比如在接收到某条消息时休眠几秒钟
-
消息确认模式有:
-
AcknowledgeMode.NONE:自动确认
-
AcknowledgeMode.AUTO:根据情况确认
-
AcknowledgeMode.MANUAL:手动确认
-
六、消息堆积问题
当生产者发送的消息的速度大于消费者消费的速度,就会导致队列中的消息堆积,直到达到消息的上限,最早接受的消息,可能会被丢弃,成为死信。
解决消费堆积的三种策略:
&增加消费者,增加消费速度
&在消费者内开启线程池加快消息处理速度
&扩大队列的容积
七、惰性队列
从RabbitMq的3.6.0版本开始增加了Lazy Queues的概念,也就是惰性队列。
惰性队列的特征如下:
&接受消息后直接存入磁盘而非内存;
&消费者消费消息才会中磁盘中将消息加载到内存;
&支持数百万的数据存储;