RabbitMq死信队列

目录

    • 1 什么是死信
    • 2 什么是死信队列
    • 3 环境准备和死信队列创建
    • 4 消息过期,无人消费
    • 5 消息溢出(超出队列最大容量)
    • 6 消息被拒绝
    • 7 小结


1 什么是死信

​ 死信,其实这是 RabbitMQ 中一种消息类型,和普通的消息在本质上没有什么区别,更多的是一种业务上的划分。如果队列中的消息出现以下情况之一,就会变成死信:

  • 如果给消息队列设置了消息的过期时间(x-message-ttl),或者发送消息时设置了当前消息的过期时间,当消息在队列中的存活时间大于过期时间时,就会变成死信。

  • 如果给消息队列设置了最大容量(x-max-length),队列已经满了,后续再进来的消息会溢出,无法被队列接收就会变成死信。

  • 消息接收时被拒绝会变成死信,例如调用channel.basicNackchannel.basicReject ,并设置requeuefalse

2 什么是死信队列

RabbitMQ死信队列俗称,备胎队列。如果不对死信做任何处理,则消息会被直接丢弃。一般死信都是那些在业务上未被正常处理的消息,我们可以考虑用一个队列来接收这些没有被处理的消息,接收死信消息的队列就是死信队列,它就是一个普通的消息队列,没有什么特殊的,只是我们在业务上赋予了它特殊的职责罢了,后期再根据实际情况处理死信队列中的消息即可。

下面对以上三种死信队列情况分别讲述:

3 环境准备和死信队列创建

1.首先添加依赖


<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-amqpartifactId>
dependency>

2.创建死信交换机和队列

其实交换机和队列的创建过程没什么特别的,就是个普通的交换机和队列。

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DeadLetterRabbitConfig {

    @Bean
    DirectExchange deadLetterExchange() {
        return new DirectExchange("dead.letter.exchange", true, false);
    }

    // 创建死信队列
    @Bean
    Queue deadLetterQueue() {
        return new Queue("dead.letter.queue", true);
    }

    // 绑定队列和交换机
    @Bean
    Binding deadLetterBinding() {
        return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with("dead.letter");
    }
}
    

3.统一设置消息的手动确认机制,在下面拒绝消息等操作时需要用到

spring:
  rabbitmq:
  	# rabbitmq连接配置
    host: 127.0.0.1
    port: 5672
#    virtual-host:   # 虚拟主机
    username: guest
    password: guest
    # 确认回调机制,此处也可不写
    publisher-confirm-type: correlated
    publisher-returns: true
    # 手动确认
    listener:
      direct:
        acknowledge-mode: manual
      simple:
        acknowledge-mode: manual

至于消息什么时候被发送到该队列,取决于业务队列的参数设置,常用的参数如下:

  • x-dead-letter-exchange:消息成为死信发送给哪个交换机
  • x-dead-letter-routing-key:死信交换机绑定队列的routingKey
  • x-message-ttl:消息过期时间(ms)
  • x-max-length:队列接收消息的最大数

详细示例在下面创建业务交换机时会讲解

4 消息过期,无人消费

  • 首先创建业务所用的交换机、队列,给队列设置好过期时间、死信发送的交换机、绑定的routing_key。这样消息过期后就会自动发送给死信队列。

    注意,如果队列已经创建,之后再修改队列的配置参数,则不会生效,需要删除掉队列重新创建

    @Configuration
    public class RabbitConfig {
        // 创建交换机
        @Bean
        DirectExchange businessExchange() {
            return new DirectExchange("business.exchange", true, false);
        }
    
        /**
         * 创建业务队列,设置属性
         * x-message-ttl:多少毫秒过期
         * x-dead-letter-exchange:消息成为死信发送给哪个交换机
         * x-dead-letter-routing-key:成为死信发送消息的routing_key
         */
        @Bean
        Queue businessQueue1() {
            HashMap<String, Object> args = new HashMap<>();
            //过期时间:10s
            args.put("x-message-ttl", 10000);
            // 设置死信交换机
            args.put("x-dead-letter-exchange", "dead.letter.exchange");
            // 设置死信交换机绑定队列的routingKey
            args.put("x-dead-letter-routing-key", "dead.letter");
            return new Queue("business.queue1", true, false, false, args);
        }
    
        @Bean
        Binding businessBinding1() {
            return BindingBuilder.bind(businessQueue1()).to(businessExchange()).with("businessRoute1");
        }
    }
    

    可以看到已经有了死信交换机和有过期时间的队列
    RabbitMq死信队列_第1张图片

  • 发送一条消息,不配置消费者消费

    @RestController
    @RequestMapping("/rabbitmq")
    public class CabbitmqController {
    
    	@Autowired
        RabbitTemplate rabbitTemplate;
    
    	@PostMapping("/sendMessageToDeadByExpire")
    	public AjaxResult sendMessageToDeadByExpire(@RequestBody Map params) {
        	String id = UUID.randomUUID().toString();
        	String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        	params.put("messageId",id);
       	 	params.put("createTime",createTime);
        	/**
        	 * 发给交换机,通过匹配队列和交换机绑定关系值,判断发送给哪个队列
        	 */
        	rabbitTemplate.convertAndSend("business.exchange","businessRoute1",params);
        	return AjaxResult.success("成功");
    	}
    }
    

    发送消息后,业务队列接收到了一条消息
    RabbitMq死信队列_第2张图片

    但十秒之后,消息过期,还没被消费,会被发送到死信队列,如下图死信队列已经存在了一条消息
    RabbitMq死信队列_第3张图片

    除了给队列设置消息的超时时间,也可以在发送消息时配置,有兴趣的可以自己尝试。

5 消息溢出(超出队列最大容量)

给队列设置最大承载消息的数量,超出数量则不再接收,发送给死信队列

同样创建business.queue.max业务消息队列,设置队列的大小为10,设置相同的死信队列

@Bean
Queue businessQueue3() {
    HashMap<String, Object> args = new HashMap<>();
    // 设置消息队列的大小
    args.put("x-max-length", 10);
    args.put("x-dead-letter-exchange", "dead.letter.exchange");
    args.put("x-dead-letter-routing-key", "dead.letter");
    return new Queue("business.queue.max", true, false, false, args);
}
@Bean
Binding businessBinding3() {
    return BindingBuilder.bind(businessQueue3()).to(businessExchange()).with("business-max");
}

发送消息,并调用超过十次以上

public AjaxResult sendMessageToDeadByMax(@RequestBody Map params) {
    String id = UUID.randomUUID().toString();
    String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    params.put("messageId",id);
    params.put("createTime",createTime);
    rabbitTemplate.convertAndSend("business.exchange","business.queue.max",params);
    return AjaxResult.success("成功");
}

可以看到发送到十次消息都会保留,当发送第十一次时,超过设置的最大个数,消息发送到死信队列。

RabbitMq死信队列_第4张图片

6 消息被拒绝

Queue businessQueueNack() {
    HashMap<String, Object> args = new HashMap<>();
    // 设置死信交换机
    args.put("x-dead-letter-exchange", "dead.letter.exchange");
    // 设置死信交换机绑定队列的routingKey
    args.put("x-dead-letter-routing-key", "dead.letter");
    return new Queue("business.queue.nack", true, false, false, args);
}
@Bean
Binding businessBindingNack() {
    return BindingBuilder.bind(businessQueueNack()).to(businessExchange()).with("business-Nack");
}

发送消息同上面一致即可

public AjaxResult sendMessageToDeadByNack(@RequestBody Map params) {
    String id = UUID.randomUUID().toString();
    String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    params.put("messageId",id);
    params.put("createTime",createTime);
    rabbitTemplate.convertAndSend("business.exchange","business-nack",params);
    return AjaxResult.success("成功");
}

监听消息方法,监听business.queue.nack队列,用channel.basicNack拒绝消息,消息就会被发送到死信队列。

@RabbitHandler
@RabbitListener(queues = "business.queue.nack",ackMode = "MANUAL")
public void processQueueTest1(Map param, Message message, Channel channel) throws IOException {
    /**
     *  第一个参数是消息的唯一ID
     *  第二个参数表示是否批量处理
     *  第三个参数表示是否将消息重发回该队列
     */
    channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);

    System.out.println("business.queue.nack消费者接收到消息:" + param.toString());
    System.out.println("message:" + message);
    System.out.println("channel:" + channel);
}

发送消息后,可以看到消费者成功接收到消息,但回复拒绝,消息被转发到死信队列

在这里插入图片描述
RabbitMq死信队列_第5张图片

7 小结

​ 关于死信队列的用法就介绍到这里了,还是很简单的。在一些重要的业务场景中,为了防止有些消息由于各种原因未被正常消费而丢失掉,可以考虑使用死信队列来保存这些消息,以方便后期排查问题使用,这样总比后期再去复现错误要简单的多。其实,延时队列也可以结合死信队列来实现,本文消息过期例子就是它的雏形。

你可能感兴趣的:(RabbitMq,rabbitmq,java-rabbitmq,死信队列,分布式,java)