SpringBoot简单实现RabbitMq的死信队列

死信队列(DLQ)

1.什么是死信队列?

死信队列是一种用于处理无法被正常消费的消息机制,当消息在队列中变成 '死信' 时,RabbitMq会自动将这些消息重新发送到另一个队列,即死信队列。

那么在消息队列中,什么情况下会变成  '死信'? 

 (1)消息被拒:消费者使用basic.reject或basic.nack并且将requeue参数设置为false,表示消息不会在次入队 
(2)消息过期:为消息设置过期时间(TTL),消息在队列中等待时间比TTL时间更长,则会被认为是死信。(如果整个队列过期,则队列中的消息不是死信消息)
(3)队列达到最大长度:如果队列已满并设置x-max-length属性,则最早进入队列的消息被认为是死信
(4)如果消息被返回到仲裁队列的次数超过了delivery-limit所设定的值,那么这个消息被视为死信(delivery-limit它是参数配置,消息被视为死信之前可以重新投递到队列的最大次数)

注意:上图官网说的quorum queue它是仲裁队列,queue的一种类型,通过Raft算法实现高可用和强一致性,本节先不考虑,感兴趣的话可以去了解下,在未来仲裁队列有可能会取代经典队列。

这些‘死信’的流转过程如下

在RabbitMQ中死信交换机和死信队列就是普通的交换机和队列

2.应用场景

(1)消息重试机制:当消息消费者因为某些临时问题(如数据库连接失败、网络抖动等)而无法正常处理消息时,可以将这些消息放入死信队列,并在稍后重试处理。
(2)消息延迟处理:需要延迟发送的通知系统。当消息到达时,你可以将其发送到一个带有TTL的队列中。如果消息在TTL到期前没有被消费,它就会被发送到死信队列。然后,你可以设置一个消费者来从死信队列中读取消息并发送通知。

3.代码实现

 (具体SpringBoot工程请参考SpringBoot集成RabbitMq

准备正常队列、死信队列及交换机

@Configuration
public class MqConfig {
    //正常
    public static final String NORMAL_EXCHANGE  = "normal_exchange";
    public static final String NORMAL_QUEUE  = "normal_queue";
    public static final String NORMAL_ROUTE = "normal_route";
    //死信
    public static final String DEAD_EXCHANGE = "dead_exchange";
    public static final String DEAD_QUEUE = "dead_queue";
    public static final String DEAD_ROUTE = "dead_route";



    //死信交换机
    @Bean
    public Exchange deadExchange() {
        return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
    }
    //死信队列
    @Bean
    public Queue deadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }
    //死信交换机绑定死信队列
    @Bean
    public Binding deadBinding(Exchange deadExchange,Queue deadQueue) {
        return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTE).noargs();
    }

    //正常队列交换机
    @Bean
    public Exchange normalExchange() {
        return ExchangeBuilder.directExchange(NORMAL_EXCHANGE).build();
    }
    //正常交换机绑定正常队列
    @Bean
    public Binding normalBinding(Queue normalQueue,Exchange normalExchange) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(NORMAL_ROUTE).noargs();
    }
    //绑定死信交换机及路由key(该正常队列内的消息无法被正常消费时,会转发给绑定的死信交换机通过路由key转发到死信队列)
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(NORMAL_QUEUE)
                .deadLetterExchange(DEAD_EXCHANGE)
                .deadLetterRoutingKey(DEAD_ROUTE)
                .build();
    }
}

生产者代码 

@RestController
@RequestMapping("/product")
@Slf4j
public class PushMessageController {

    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private MqProductCallBack mqProductCallBack;


    @GetMapping("/pushMessage")
    public String pushMessage() {
        //创建CorrelationData对象,包含唯一id,id的作用是在回调函数中识别消息,也就是根据id跟踪这条消息
        CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis());
        //消息确认与返回
        rabbitTemplate.setConfirmCallback(mqProductCallBack);
        rabbitTemplate.setReturnsCallback(mqProductCallBack);
        //消息发送
        rabbitTemplate.convertAndSend(MqConfig.NORMAL_EXCHANGE, MqConfig.NORMAL_ROUTE, "小桥流水哗啦啦!",
                //Lambda表达式,实现MessagePostProcessor接口
                message -> {
                    //获取消息的属性,设置传输模式DeliveryMode为持久化,会写入磁盘
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    //返回修改后的消息
                    return message;
                }, correlationData);
        return "消息发送成功了!!!";
    }

}

消费者监听死信队列

@Component
@Slf4j
public class DeadLetterListener {

    @RabbitListener(queues = "dead_queue")
    public void listenerDeadLetter(String msg, Message message, Channel channel) throws IOException {
        log.info("--死信队列--接收到的消息是:{}",msg);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag,false);
    }
}
(1)监听正常队列(消费者实现拒绝消息、否认消息)
@Component
@Slf4j
public class NormalListener {

    @RabbitListener(queues = "normal_queue")
    /**
     * @Header(AmqpHeaders.DELIVERY_TAG)Long tag
     * 抽取该参数到方法中,其中AmqpHeaders.DELIVERY_TAG是为消费者提供唯一的标识符;
     * 消费者通过DELIVERY_TAG接收或者拒绝消息
     * (也可以接收Message取参)
     */
    public void handleMessage1(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG)Long tag) throws IOException {
        log.info("正常队列接收到的消息是:{}",message);
        //拒绝消息
        channel.basicReject(tag,false);
        //否认消息
//        channel.basicNack(tag,false,false);
    }
}

控制台输出

监听死信队列的消费者把消息消费下来 

(2)超过消息过期时间TTL

生产者代码:

@GetMapping("/pushMessage")
    public String pushMessage() {
        //创建CorrelationData对象,包含唯一id,id的作用是在回调函数中识别消息,也就是根据id跟踪这条消息
        CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis());
        //消息确认与返回
        rabbitTemplate.setConfirmCallback(mqProductCallBack);
        rabbitTemplate.setReturnsCallback(mqProductCallBack);
        //消息发送
        rabbitTemplate.convertAndSend(MqConfig.NORMAL_EXCHANGE, MqConfig.NORMAL_ROUTE, "小桥流水哗啦啦!",
                //Lambda表达式,实现MessagePostProcessor接口
                message -> {
                    //获取消息的属性,设置传输模式DeliveryMode为持久化,会写入磁盘
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    //消息过期时间 5秒
                    message.getMessageProperties().setExpiration("5000");
                    //返回修改后的消息
                    return message;
                }, correlationData);
        return "消息发送成功了!!!";
    }

延迟五秒我们在启动消费者,消费者控制台:监听死信队列消费者把消息消费下来,而监听正常队列没有打印日志,说明没有消息进行消费,也就是5秒后没有消费者去消费这条消息,直接放到死信队列。

 (3)队列达到最大长度

队列配置

  @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(NORMAL_QUEUE)
                //队列更改需要删除队列,否则报错
                .maxLength(1)
                .deadLetterExchange(DEAD_EXCHANGE)
                .deadLetterRoutingKey(DEAD_ROUTE)
                .build();
    }

消费者控制台

 超出长度的变为死信队列被监听死信的消费者消费下来。

代码比较简单,主要是学习死信的流转及如何成为死信,明白了这些你才能去应用,上手更快,面对自己的业务需求才能更好的去拓展。

第一次学习死信队列,希望各位大佬指出不足、不正确的地方!

bay!!! 

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值