消息队列之RabbtiMQ延迟队列

什么是延迟队列

延迟队列存储的对象肯定是对应的延迟消息,所谓”延迟消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。

场景一:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行一场处理。这是就可以使用延迟队列将订单信息发送到延迟队列。

场景二:用户希望通过手机远程遥控家里的智能设备在指定的时间进行工作。这时候就可以将用户指令发送到延迟队列,当指令设定的时间到了再将指令推送到只能设备。
RabbitMQ怎么实现延迟队列

AMQP协议和RabbitMQ队列本身没有直接支持延迟队列功能,但是可以通过以下特性模拟出延迟队列的功能。

在这里插入图片描述
1、Time To Live(TTL)

RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)

A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
B: 对消息进行单独设置,每条消息TTL可以不同。

2、Dead Letter Exchanges(DLX)

RabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

下面是案例

提供者

package com.mq.rabbitmqprovider.config;

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;

import java.util.HashMap;
import java.util.Map;

/**
 * 延迟队列
 * @author LiuCheng
 * @data 2021/1/5 14:45
 */
@Configuration
public class DelayRabbitMQConfig {
    // 业务交换机
     public static final String BUSINESS_EXCHANGE="delayBusinesExchange";
    // 业务队列名称
    public static final String BUSINESS_QUEUE="delayBusinessQueue";
    // 业务路由key
    public static final String BUSINESS_ROUTE_KEY="delayBusinessRouteKey";
    // 死信交换机
    public  static final String DEAD_EXCHANGE="delayDeadExchange";
    // 死信队列
    public static final String DEAD_QUEUE="delayDeadQueue";
    // 死信路由key
    public static final String DEAD_ROUTINGKEY="delayDeadRoutingKey";
    // 死信队列交换机标识符
    public static final String DEAD_LETTER_QUEUE_KEY="x-dead-letter-exchange";
    // 死信队列交换机绑定键标识符
    public static final String DEAD_LETTER_ROUTING_KEY="x-dead-letter-routing-key";

    /**
     * 定义队列 队列绑定一个死信队列并制定routekey
     * @return
     */
    @Bean
    public Queue delayBusinessQueue(){
        // 将普通队列绑定到死信交换机上
        Map<String,Object> map= new HashMap<>();
        //map.put("x-message-ttl", 5 * 1000);//直接设置 Queue 延迟时间 但如果直接给队列设置过期时间,这种做法不是很灵活
        //这里采用发送消息动态设置延迟时间,这样我们可以灵活控制
        map.put(DEAD_LETTER_QUEUE_KEY,DEAD_EXCHANGE);
        map.put(DEAD_LETTER_ROUTING_KEY,DEAD_ROUTINGKEY);
        return new Queue(BUSINESS_QUEUE,true,false,false,map);
    }
    @Bean
    public DirectExchange delayBusinessExchange(){
        return  new DirectExchange(BUSINESS_EXCHANGE);
    }
    @Bean
    public Binding delayBindingToExchangge(){
        return BindingBuilder.bind(delayBusinessQueue()).to(delayBusinessExchange()).with(BUSINESS_ROUTE_KEY);
    }
    /**
     * 创建死信队列绑定死信交换机
     */
    @Bean
    public Queue delayDeadQueue(){
        return  new Queue(DEAD_QUEUE,true);
    }
    @Bean
    public DirectExchange delayDeadExchange(){
        return  new DirectExchange(DEAD_EXCHANGE);
    }
    @Bean
    public Binding bindingtoDeadExchange(){
        return BindingBuilder.bind(delayDeadQueue()).to(delayDeadExchange()).with(DEAD_ROUTINGKEY);
    }

}

提供者控制层

package com.mq.rabbitmqprovider.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONNull;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mq.rabbitmqprovider.config.BusinessRabbitMQConfig;
import com.mq.rabbitmqprovider.config.DeadRabbitMQConfig;
import com.mq.rabbitmqprovider.config.DelayRabbitMQConfig;
import com.sun.org.apache.regexp.internal.RE;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author LiuCheng
 * @data 2020/12/30 15:03
 */
@RestController
@RequestMapping("/mq/business")
public class BusinessController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
   
    @GetMapping("/sendDelay")
    public String sendDelay(String msg){
        String messageId= String.valueOf(UUID.randomUUID());
        String creatTime= DateUtil.now();
        Map<String,Object> map = new HashMap<>();
        map.put("messageId",messageId);
        map.put("messgageName",msg);
        map.put("creatTime",creatTime);
        rabbitTemplate.convertAndSend(DelayRabbitMQConfig.BUSINESS_EXCHANGE,DelayRabbitMQConfig.BUSINESS_ROUTE_KEY,JSONUtil.toJsonStr(map),(message)->{
        // 测试用的10s后发送到死信队列
          message.getMessageProperties().setExpiration(1 * 1000 * 10 + "");
            
             return message;
        });
        return "sendDelay  ok";
    }
}

消费者

package com.mq.rabbitmqconsumer.rabbimqConsumer;

import cn.hutool.json.JSONUtil;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * 延迟队列消费者
 * @author LiuCheng
 * @data 2021/1/7 14:24
 */
@Component
@Slf4j
public class DelayBusinessReceiver {
    @RabbitListener(queues = "delayDeadQueue")
    public void process(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        byte[] body = message.getBody();
        log.info("延迟队列消费信息 " + JSONUtil.parse(new String(body,"UTF-8")) );
        //必须手动确认否则消息一直存在队列中,每次启动会被消费一次
        channel.basicAck(deliveryTag,true);
    }
}

测试如下:
postman发送请求
在这里插入图片描述
业务队列中含一条信息
在这里插入图片描述
10s后信息被消费者消费小
在这里插入图片描述
使用死信队列实现延时消息的缺点:

1, 如果统一用队列来设置消息的TTL,当延时时间梯度比较多的话,比如1分钟,2分钟,5分钟,10分钟,20分钟,30分钟……需要创建很多交换机和队列来路由消息。
2 ,如果单独设置消息的TTL,则可能会造成队列中的消息阻塞——前一条消息没有出队(没有被消费),后面的消息无法投递。
3, 可能存在一定的时间误差。

死信队列可参考:https://blog.csdn.net/HBliucheng/article/details/111993550

参考博客:https://yilei.blog.csdn.net/article/details/94070677

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值