RabbitMQ死信交换器如何快速搭建?

1 RabbitMQ死信交换器介绍

首先了解一下什么是死信,官方将其翻译为单词Dead Letter。死信,其实这是RabbitMQ中一种消息类型,和普通的消息在本质上没有什么区别,更多的是一种业务上的划分。如果队列中的消息出现以下情况之一,就会变成死信:

  • 消息接收时被拒绝会变成死信,例如调用channel.basicNack()或channel.basicReject(),并且消息的requeue参数设置为false。
  • 消息在队列的存活时间超过设置的TTL时间。如果给消息队列设置了消息的过期时间(x-message-ttl),或者发送消息时设置了当前消息的过期时间,当消息在队列中的存活时间大于过期时间时,就会变成死信。
  • 消息队列的消息数量已经超过最大队列长度。如果给消息队列设置了最大容量(x-max-length),队列已经满了,后续再进来的消息会溢出,无法被队列接收就会变成死信。

如果不对死信做任何处理,则消息会被直接丢弃。如果一个队列中的消息已经成为死信,并且这个队列通过“x-dead-letter-exchange”属性指定了一个交换器,那么队列中的死信就会投递到这个交换器中,把这个交换器就称为死信交换器(Dead Letter Exchange)。此时如果有队列与死信交换器绑定,则最终死信就会被投递到这个队列中,接收死信消息的队列就是死信队列(Dead Letter Queue)。死信队列就是一个普通的消息队列,没有什么特殊的,只是我们在业务上赋予了它特殊的职责罢了,后期再根据实际情况处理死信队列中的消息即可。

2 RabbitMQ死信交换器的配置方式

死信交换器有什么作用呢?这里主要有三点:收集那些因处理失败而被拒绝的消息、收集那些因队列满了而被拒绝的消息、收集因TTL到期的消息。

如何配置死信交换器呢?其实很简单,大概可以分为以下步骤:

  1. 配置业务队列,绑定到业务交换器上;
  2. 为业务队列配置死信交换器和路由key;
  3. 为死信交换器配置死信队列。

通过对消息产生死信的三种方式的学习,现设计如下的结构,来测试RabbitMQ死信交换器的实现。

3 RabbitMQ死信交换器实现
3.1 消息被拒绝

1.在rabbitmq-consumer-demo项目的application.yml文件的原有配置基础上需添加如下配置。

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual # 设置消费者需要手动确认消息
        default-requeue-rejected: false # 设置消息被拒绝后,不再重新入队
      direct:
        acknowledge-mode: manual # 设置消费者需要手动确认消息

2.接下来在rabbitmq-consumer-demo项目的RabbitMQConfig配置类中创建一个死信交换机和死信队列并完成绑定。这里的交换机也可以称作死信交换机,交换机的类型没有特殊的要求根据实际需求选择即可。

// 创建死信交换机
@Bean
public DirectExchange deadLetterExchange() {
    return new DirectExchange("dead.letter.exchange", true, false, new HashMap<>());
}
// 创建死信队列
@Bean
public Queue deadLetterQueue() {
    return new Queue("dead.letter.queue", true, false, false, new HashMap<>());
}
// 绑定队列和交换机
@Bean
public Binding bindingDeadLetter() {
    // 无条件路由
    return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with("dead.letter");
}

3.然后在rabbitmq-consumer-demo项目的RabbitMQConfig类中创建处理业务消息的交换机和队列并进行绑定。

// 创建交换机
@Bean
public DirectExchange businessExchange() {
    return new DirectExchange("business.exchange", true, false, new HashMap<>());
}
// 创建业务消息队列
@Bean
public Queue testRefuseQueue() {
    HashMap<String, Object> map = new HashMap<>();
    // 设置死信交换机(队列中的消息变成死信后,进入死信交换机)
    map.put("x-dead-letter-exchange", "dead.letter.exchange");
    // 设置死信交换机绑定队列的routingKey
    map.put("x-dead-letter-routing-key", "dead.letter");
    return new Queue("test.refuse.queue", true, false, false, map);
}
// 绑定队列和交换机
@Bean
Binding bindingExchangeRefuse() {
    return BindingBuilder.bind(testRefuseQueue()).to(businessExchange()).with("dl.refuse");
}

上述代码在创建test.refuse.queue队列时,我们给它配置了前边创建的死信交换器以及Routing Key,这样就完成了业务消息队列和死信队列的绑定,业务消息被拒绝后,就会进入死信队列。注意,如果队列已经创建,之后再修改队列的配置参数,则不会生效,需要删除掉队列重新创建。

4.接下来在rabbitmq-consumer-demo项目的RabbitMQService类中创建消费者来消费test.refuse.queue中的业务消息,为了突出效果,直接让消费者拒绝掉消息。

@RabbitListener(queues = "test.refuse.queue")
public void refuseConsumer(Message message, Channel channel) {
    try {
        // 拒绝消息
        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

5.在rabbitmq-publisher-demo项目的RabbitmqPublisherDemoApplicationTests测试类中定义发送消息的测试方法,并运行测试方法发送一条消息进行测试。

// 测试消息被拒绝
@Test
public void refuseMessage() throws JsonProcessingException {
    // 创建一个ObjectMapper对象
    ObjectMapper objectMapper = new ObjectMapper();
    User user = new User(1, "袁庭新", "123456");
    // 将User对象序列化为JSON字符串
    String msg = objectMapper.writeValueAsString(user);
    // 创建消息
    Message message = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8))
          .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
          .build();
    // 发送消息
    rabbitTemplate.convertAndSend("business.exchange", "dl.refuse", message);
}

6.按照预期,消息最终会流入死信队列dead.letter.queue。可以通过RabbitMQ的后台管理界面查看具体的效果,见下图。

3.2 消息过期

1.在rabbitmq-consumer-demo项目的RabbitMQConfig中再添加一个test.ttl.queue业务消息队列,设置队列中消息的过期时间为10秒,同样配置好死信交换器和死信队列。

@Bean
public Queue testTtlQueue() {
    Map<String, Object> map = new HashMap<>();
    // 设置队列中消息的过期时间,单位毫秒
    map.put("x-message-ttl", 10000);
    map.put("x-dead-letter-exchange", "dead.letter.exchange");
    map.put("x-dead-letter-routing-key", "dead.letter");
    return new Queue("test.ttl.queue", true, false, false, map);
}
@Bean
Binding bindingExchangeTtl() {
    return BindingBuilder.bind(testTtlQueue()).to(businessExchange()).with("dl.ttl");
}

2.不用给test.ttl.queue配置消费者,直接在rabbitmq-publisher-demo项目的RabbitmqPublisherDemoApplicationTests测试类中添加测试方法。运行测试方法发送一条消息,让它自动过期即可。

// 测试消息过期
@Test
public void ttlMessage() {
    rabbitTemplate.convertAndSend("business.exchange", "dl.ttl", "死信队列:测试超时");
}

3.等待10秒后,通过RabbitMQ的后台管理界面查看消息,发现消息会自动流入死信队列,如下图所示。

3.3 消息益出

1.在rabbitmq-consumer-demo项目的RabbitMQConfig中再添加一个test.max.queue业务消息队列,设置队列的大小为3,同样配置好死信交换器和死信队列。

@Bean
public Queue testMaxQueue() {
    Map<String, Object> map = new HashMap<>();
    // 设置队列可以存储的最大消息数量
    map.put("x-max-length", 3);
    map.put("x-dead-letter-exchange", "dead.letter.exchange");
    map.put("x-dead-letter-routing-key", "dead.letter");
    return new Queue("test.max.queue", true, false, false, map);
}
@Bean
Binding bindingExchangeMax() {
    return BindingBuilder.bind(testMaxQueue()).to(businessExchange()).with("dl.max");
}

2.不用设置test.max.queue队列的消费者,直接在rabbitmq-publisher-demo项目的RabbitmqPublisherDemoApplicationTests测试类中添加测试方法。运行测试方法连续发送四条消息。

// 测试消息溢出
@Test
public void maxMessage() {
    rabbitTemplate.convertAndSend("business.exchange", "dl.max", "死信队列:测试长度-1");
    rabbitTemplate.convertAndSend("business.exchange", "dl.max", "死信队列:测试长度-2");
    rabbitTemplate.convertAndSend("business.exchange", "dl.max", "死信队列:测试长度-3");
    rabbitTemplate.convertAndSend("business.exchange", "dl.max", "死信队列:测试长度-4");
}

3.按照预期test.max.queue最终会有4条消息,其中一条会进入死信队列,如下图所示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值