RabbitMQ之死信队列

什么是死信队列

DLX ,全称为 Dead-Letter-Exchange ,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信( dead message )之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。

消息变成死信一般是由于以下几种情况:

  • 〈1〉消息被拒绝( Basic.Reject/Basic.Nack ),井且设置 requeue 参数为 false;
  • 〈2〉消息过期;
  • 〈3〉队列达到最大长度。

DLX 也是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列中存在死信时RabbitMQ 就会自动地将这个消息新发布到设置的 DLX 上去,进而被路由到另一个队列,即死信队列。可以监听这个队列中的消息、以进行相应的处理。

配置死信队列

在 channel.queueDeclare 方法中设置 x-dead-letter-exchange 参数来为这个队列添加 DLX

一、消息过期

1、添加配置

mq:
  queueBinding:
    queue: prod_queue_pay
    dlQueue: dl-queue
    exchange:
      name: exchang_prod_pay
      dlTopicExchange: dl-topic-exchange
    key: prod_pay
    dlRoutingKey: dl-routing-key

2、创建死信交换机、死信队列以及两者的绑定

    @Value("${mq.queueBinding.exchange.dlTopicExchange}")
    private String dlTopicExchange;
    @Value("${mq.queueBinding.dlRoutingKey}")
    private String dlRoutingKey;
    @Value("${mq.queueBinding.dlQueue}")
    private String dlQueue;
    //创建死信交换机
    @Bean
    public TopicExchange dlTopicExchange(){
        return new TopicExchange(dlTopicExchange,true,false);
    }
    //创建死信队列
    @Bean
    public Queue dlQueue(){
        return new Queue(dlQueue,true);
    }
    //死信队列与死信交换机进行绑定
    @Bean
    public Binding BindingErrorQueueAndExchange(Queue dlQueue, TopicExchange dlTopicExchange){
        return BindingBuilder.bind(dlQueue).to(dlTopicExchange).with(dlRoutingKey);
    }

3、创建业务队列、业务交换机,以及两者的绑定

    @Value("${mq.queueBinding.queue}")
    private String queueName;
    @Value("${mq.queueBinding.exchange.name}")
    private String exchangeMame;
    @Value("${mq.queueBinding.key}")
    private String key;
    private final String dle = "x-dead-letter-exchange";
    private final String dlk = "x-dead-letter-routing-key";
    /**
     * 业务队列
     * @return
     */
    @Bean
    public Queue payQueue(){
        Map params = new HashMap<>();
         //设置队列的过期时间
        params.put(ttl,10000);
        //声明当前队列绑定的死信交换机
        params.put(dle,dlTopicExchange);
        //声明当前队列的死信路由键 如果没有指定,则使用原队列的路由键:
        params.put(dlk,dlRoutingKey);
        return QueueBuilder.durable(queueName).withArguments(params).build();
    }
    @Bean
    public TopicExchange payTopicExchange(){
        return new TopicExchange(exchangeMame,true,false);
    }
    //队列与交换机进行绑定
    @Bean
    public Binding BindingPayQueueAndPayTopicExchange(Queue payQueue, TopicExchange payTopicExchange){
        return BindingBuilder.bind(payQueue).to(payTopicExchange).with(key);
    }

生产者代码

/*
 * 生产者
 */
@Component
@Slf4j
public class RabbitSender {

    @Value("${mq.queueBinding.exchange.name}")
    private String exchangeName;

    @Value("${mq.queueBinding.key}")
    private String key;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String msg){
        log.info("RabbitSender.send() msg = {}",msg);
        rabbitTemplate.convertAndSend(exchangeName,key,msg);
    }

}

提供对外方法

    @Autowired
    private RabbitSender rabbitSender;

    @GetMapping
    public void test(@RequestParam String msg){
        rabbitSender.send(msg);
    }

启动服务,可以看到同时创建了业务队列、业务交换机以及死信队列、死信交换机

image.png

image.png

然后调用接口:[http://localhost:8080/?msg=红红火火]
image.png

在这 10s 内没有消费者消费这条消息,那么判定这条消息为过期消息。由于设置了 DLX ,过期之时消息被丢给dlxExchange 交换器中,这时找到与 dlxExchange匹配的队列 dlQueue后,消息被存储在 dlxQueue 这个死信队列中。

二、消息被拒绝

将业务队列的过期时间去掉,重新生成业务队列

/**
     * 业务队列
     * @return
     */
    @Bean
    public Queue payQueue(){
        Map params = new HashMap<>();
        //声明当前队列绑定的死信交换机
        params.put(dle,dlTopicExchange);
        //声明当前队列的死信路由键 如果没有指定,则使用原队列的路由键:
        params.put(dlk,dlRoutingKey);
        return QueueBuilder.durable(queueName).withArguments(params).build();
    }

配置文件新增配置

rabbitmq:
    listener:
      simple:
        #消息确认方式 manual手动确认 auto自动确认 none不管
        acknowledge-mode: manual

添加消费者

/**
 * 消费者
 */
@Component
@Slf4j
public class RabbitReceiver {

    //测试消费者进行消费发送异常 是否进入死信队列
    @RabbitListener(queues = "${mq.queueBinding.queue}")
    public void infoConsumption(String data, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
        log.info("收到信息:{}",data);
        boolean ack = false;
        Exception exception = null;
        try {
            if(data.contains("888")){
               throw new RuntimeException("信息敏感");
            }
        } catch (RuntimeException e) {
            ack = true;
            exception = e;
        }
        if (ack){
            log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
 //注意第三个参数需要为false
//true则重新入队列,否则丢弃或者进入死信队列。
            channel.basicNack(tag, false, false);
        } else {
            channel.basicAck(tag, false);
        }
    }
}

在上述代码中,如果将第三个参数设置为true,将会出现无限重试问题。
在下一篇中笔者将介绍怎么解决无限重试问题:https://www.jianshu.com/p/5e0455152978

你可能感兴趣的:(RabbitMQ之死信队列)