RabbitMQ-延迟队列

510 阅读3分钟

RabbitMQ延迟队列

介绍

官网上的介绍

实现方式:

混合使用Message TTL和Dead Letter Exchanges

YJrwJP.jpg

利用dead letter exchange的规则

  • The message is negatively acknowledged by a consumer using basic.reject or basic.nack with requeue parameter set to false.消费者拒绝或者nack
  • The message expires due to per-message TTL; or 消息ttl
  • The message is dropped because its queue exceeded a length limit 消息被丢弃由于队列超过长度限制

生产者发送消息给延迟交换机时,让消息设置特定时间过期,转发到延迟队列。这个延迟队列创建时,声明dead letter exchange name和dead letter routing key。由于没有设置消费者,去监听这个延迟队列,导致这个消息ttl,最终转发到死信exchange,根据死信routingkey转发死信队列。(这边的例子,我会在下面举例)

消费者

# application.yml
server:
  port: 8080
spring:
  rabbitmq:
    username: admin
    password: admin@789
    host: 121.36.241.65
    port: 5672
    virtual-host: /test
 
@Configuration
public class RabbitConfig {
       private static final String DELAY_QUEUE_NAME = "test_delay_ttl_springboot_queue";
        private static final String DELAY_EXCHANGE_NAME = "test_delay_ttl_springboot_exchange";
        private static final String DELAY_ROUTING_KEY = "test_delay_ttl_springboot_routingkey";
        private static final String DEAD_QUEUE_NAME = "test_dead_ttl_springboot_queue";
        private static final String DEAD_EXCHANGE_NAME = "test_dead_ttl_springboot_exchange";
        private static final String DEAD_ROUTING_KEY = "test_dead_ttl_springboot_routingkey";

    // 延迟队列
        @Bean
        public Queue dealyQueue(){
            Map<String,Object> args = new HashMap<>();
            // x-dead-letter-exchange 当前队列声明的死信路由
            args.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
            // x-dead-letter-routing-key  这里声明当前队列的死信路由key
            args.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
            return QueueBuilder.durable(DELAY_QUEUE_NAME).withArguments(args).build();
        }

    // 死信queue
        @Bean
        public Queue deadQueue(){
            return new Queue(DEAD_QUEUE_NAME);
        }

    // 延迟exchange
        @Bean
        public DirectExchange delayExchange(){
            return new DirectExchange(DELAY_EXCHANGE_NAME);
        }
// 死信exchange
        @Bean
        public DirectExchange deadExchange(){
            return new DirectExchange(DEAD_EXCHANGE_NAME);
        }

        @Bean
        public Binding dealyBinding(@Qualifier("dealyQueue")Queue queue,@Qualifier("delayExchange")Exchange exchange){
            return BindingBuilder.bind(queue).to(exchange).with(DELAY_ROUTING_KEY).noargs();
        }

        @Bean
        public Binding deadBinding(@Qualifier("deadQueue")Queue queue,@Qualifier("deadExchange")Exchange exchange){
            return BindingBuilder.bind(queue).to(exchange).with(DEAD_ROUTING_KEY).noargs();
        }
    }
    private static final String DELAY_EXCHANGE_NAME = "test_delay_ttl_springboot_exchange";
    private static final String DELAY_ROUTING_KEY = "test_delay_ttl_springboot_routingkey";

    @Resource
    private RabbitTemplate rabbitTemplate;
	@Override
    public void sendDelayMsgTTL(String msg, Integer delay) {
        rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME,DELAY_ROUTING_KEY,msg,msgProcessor->{
            msgProcessor.getMessageProperties().setExpiration(delay.toString());
            return msgProcessor;
        });
    }
// 发送短信
mqService.sendDelayMsgTTL("hello,i send a delay msg by ttl",5000);

消费者

消费者配置文件和生成者一样

    private static final String DEAD_QUEUE_NAME = "test_dead_ttl_springboot_queue";
    @RabbitListener(queues = DEAD_QUEUE_NAME)
    public void delay(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(),"UTF-8");
        System.out.println("msg : " + msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }


运行上面消费者,可以看到5秒后消费者收到

那么,我们来监听延迟队列

    private static final String DEAD_QUEUE_NAME = "test_dead_ttl_springboot_queue";

    @RabbitListener(queues = "test_delay_ttl_springboot_queue")
    public void delay(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(),"UTF-8");
        System.out.println("msg : " + msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

    @RabbitListener(queues = DEAD_QUEUE_NAME)
    public void delay2(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(),"UTF-8");
        System.out.println("msg : " + msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

可以看到,生产者发送消息,消费者瞬间收到。并且死信队列不会监听到消息。

使用RabbitMQ Delayed Message Plugin来实现延迟队列

安装步骤

  • 前往https://www.rabbitmq.com/community-plugins.html,下载rabbitmq_delayed_message_exchange插件
  • 将下载好的插件放入rabbitmq安装目录中的plugin 文件夹下
  • 执行命令rabbitmq-plugins enable rabbitmq_delayed_message_exchange让该plugin可以使用
  • 插件安装完毕后,就会有一个x-delayed-message类型的exchange 代码
// 声明队列和queue
private static final String DELAY_QUEUE_NAME = "test_delay_queue_springboot";
    private static final String DELAY_EXCHANGE_NAME = "test_delay_exchange_springboot";
    private static final String DELAY_EXCHANGE_ROUTING_KEY = "test_delay_routingkey_springboot";    
@Bean
    public Queue delayQueue(){
        return new Queue(DELAY_QUEUE_NAME);
    }

    @Bean
    public CustomExchange customExchange(){
        Map<String,Object> args = new HashMap<>();
        args.put("x-delayed-type","direct");
        return new CustomExchange(DELAY_EXCHANGE_NAME,"x-delayed-message",true,false,args);
    }

    @Bean
    public Binding binding(@Qualifier("delayQueue")Queue queue,@Qualifier("customExchange")CustomExchange customExchange){
        return BindingBuilder.bind(queue).to(customExchange).with(DELAY_EXCHANGE_ROUTING_KEY).noargs();
    }
// 设置具体的延迟时间
@Override
    public void sendDelayMsg(String message, Integer delay) {
        rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME,DELAY_EXCHANGE_ROUTING_KEY,message,msgProcessor->{
            msgProcessor.getMessageProperties().setDelay(delay);
            return msgProcessor;
        });
    }
// 接收消息
    @RabbitListener(queues = DELAY_QUEUE_NAME)
    public void delay(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(),"UTF-8");
        System.out.println("msg : " + msg);
        // 可以看到具体延迟了多少时间,其实也就是x-delay这个头
        System.out.println(message.getMessageProperties().getReceivedDelay());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

可以看到使用插件也可以实现这个功能。

下面,我们来测试一下死信相关的功能

我们发生一条信息到延迟队列里面,监听这个队列,消费的时候拒绝这个消息,这样这条消息就会发往死信队列

// 生产者   
@Override
    public void sendMsgUAck(String message) {
        rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME,DELAY_ROUTING_KEY,message);
    }
   // 消费者
//监听死信队列
   @RabbitListener(queues = DEAD_QUEUE_NAME)
    public void delay2(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(),"UTF-8");
        System.out.println("msg : " + msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

// 监听延迟队列
    @RabbitListener(queues = "test_delay_ttl_springboot_queue")
    public void delay3(Message message, Channel channel) throws IOException {
        channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
        System.out.println("uack message");
    }

可以看到发生消息,延迟队列消费者拒绝,立刻发往死信队列。(扣库存相关逻辑,可以参考)

参考

官方文档

弗兰克的猫的博文