RabbitMQ-延迟队列

延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理。简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列

使用场景

  • 订单在十分钟之内未支付则自动取消
  • 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒
  • 用户注册成功后,如果三天内没有登陆则进行短信提醒。
  • 用户发起退款,如果三天内没有得到处理则通知相关运营人员
  • 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

TTL

  • TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒
  • 如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这条消息如果在 TTL 设置的时间内没有被消费,则会成为"死信"
  • 如果同时配置了队列的 TTL 和消息的TTL,那么较小的那个值将会被使用

消息设置TTL

java 复制代码
rabbitTemplate.convertAndSend("X", "XC", message, correlationData ->{
	//设置消息过期时间
	correlationData.getMessageProperties().setExpiration("10");
	return correlationData;
});

队列设置TTL

java 复制代码
public Queue queueA(){
	Map<String, Object> args = new HashMap<>(3);
	//声明当前队列绑定的死信交换机
	args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
	//声明当前队列的死信路由 key
	args.put("x-dead-letter-routing-key", "YD");
	//声明队列的 TTL
	args.put("x-message-ttl", 10000);
	return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
}
  1. 两种TTL区别
  • 队列的TTL,消息一旦过期就会被队列丢弃(如果配置了死信队列被丢到死信队列中)
  • 消息的TTL,即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间
  • 如果消息不设置TTL,表示永远不会过期,如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃

队列TTL

  • SpringBoot配置文件-15672是Web管理界面的端口;5672是MQ访问的端口

    spring.rabbitmq.host=192.168.1.128
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=admin
    spring.rabbitmq.password=123
    spring.rabbitmq.virtual-host=/

  • 队列配置类

java 复制代码
@Configuration
public class TtlQueueConfig {
    
    public static final String X_EXCHANGE = "X";
    public static final String QUEUE_A = "QA";
    public static final String QUEUE_B = "QB";
    public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
    public static final String DEAD_LETTER_QUEUE = "QD";
    
    // 声明 xExchange
    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(X_EXCHANGE);
    }
    
    // 声明 xExchange
    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }
    
    //声明队列 A ttl 为 10s 并绑定到对应的死信交换机
    @Bean("queueA")
    public Queue queueA(){
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "YD");
        //声明队列的 TTL
        args.put("x-message-ttl", 10000);
        return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
    }
    // 声明队列 A 绑定 X 交换机
    @Bean
    public Binding queueaBindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }
    
    //声明队列 B ttl 为 40s 并绑定到对应的死信交换机
    @Bean("queueB")
    public Queue queueB(){
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "YD");
        //声明队列的 TTL
        args.put("x-message-ttl", 40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
    }
    
    //声明队列 B 绑定 X 交换机
    @Bean
    public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B,
                                  @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
    }
    
    //声明死信队列 QD
    @Bean("queueD")
    public Queue queueD(){
        return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
    }
    
    //声明死信队列 QD 绑定关系
    @Bean
    public Binding deadLetterBindingQAD(
            @Qualifier("queueD") Queue queueD,
            @Qualifier("yExchange") DirectExchange yExchange){
        return BindingBuilder.bind(queueD).to(yExchange).with("YD");
    }
}
  • 生产者
java 复制代码
@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMsgController {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @GetMapping("sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        log.info("当前时间: {},发送一条信息给两个 TTL 队列:{}", new Date(), message);
        rabbitTemplate.convertAndSend("X", "XA", "消息来自 ttl 为 10S 的队列: "+message);
        rabbitTemplate.convertAndSend("X", "XB", "消息来自 ttl 为 40S 的队列: "+message);
    }
}
  • 消费者
java 复制代码
@Slf4j
@Component
public class DeadLetterQueueConsumer {
    
    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间: {},收到死信队列信息{}", new Date().toString(), msg);
    }
}

延时队列优化之消息设置TTL

  • 上述例子,每增加一个新的时间需求,就要新增一个队列,现在只有 10S 和 40S两个时间选项,如果需要一个小时后处理,那么就需要增加 TTL 为一个小时的队列
  • 优化成一个队列适用于所有延时场景,增加队列QC
  • QC配置类
java 复制代码
@Component
public class MsgTtlQueueConfig {
    
    public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
    public static final String QUEUE_C = "QC";
    
    //声明队列 C 死信交换机
    @Bean("queueC")
    public Queue queueC() {
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "YD");
        //没有声明 TTL 属性
        return QueueBuilder.durable(QUEUE_C).withArguments(args).build();
    }
    
    //声明队列 B 绑定 X 交换机
    @Bean
    public Binding queuecBindingX(@Qualifier("queueC") Queue queueC,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueC).to(xExchange).with("XC");
    }
    
}
  • 生产者
java 复制代码
@GetMapping("sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,@PathVariable String ttlTime) {
	rabbitTemplate.convertAndSend("X", "XC", message, correlationData ->{
		correlationData.getMessageProperties().setExpiration(ttlTime);
		return correlationData;
	});
	log.info("当前时间: {},发送一条时长{}毫秒 TTL 信息给队列 C:{}", new Date(),ttlTime, message);
}
  • 缺陷 :如果使用在消息属性上设置 TTL 的方式,消息可能并不会按时"死亡",因为 RabbitMQ 只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行

延时插件

  • 为解决延时时间短的消息先被消费执行,可以使用延时插件

  • 插件下载地址:https://www.rabbitmq.com/community-plugins.html

  • 延迟插件名:rabbitmq_delayed_message_exchange

  • 将插件放置到RabbitMQ安装目录的plugins 目录下

    cp rabbitmq_delayed_message_exchange-3.8.0.ez /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins

  • 开启插件

    rabbitmq-plugins enable rabbitmq_delayed_message_exchange

  • 重启mq,查看交换机类型,增加了x-delayed-message

    systemctl restart rabbitmq-server

插件案例

  • 新增了一个队列 delayed.queue,一个自定义交换机 delayed.exchange
  • 配置类
java 复制代码
@Configuration
public class DelayedQueueConfig {
    
    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
   
    @Bean
    public Queue delayedQueue() {
        return new Queue(DELAYED_QUEUE_NAME);
    }
    
    //自定义交换机 我们在这里定义的是一个延迟交换机
    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>();
        //自定义交换机的类型
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME,
                "x-delayed-message",
                true, //是否需要持久化
                false, //是否需要自动删除
                args); //其余参数
    }
    
    @Bean
    public Binding bindingDelayedQueue(
            @Qualifier("delayedQueue") Queue queue,
            @Qualifier("delayedExchange") CustomExchange delayedExchange) {
        return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    }
}
  • 生产者
java 复制代码
public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";

@GetMapping("sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime) {
	rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message,
			correlationData ->{
                //发送消息的时候 延长的时间 单位ms
				correlationData.getMessageProperties().setDelay(delayTime);
				return correlationData;
			}
	);
	
	log.info(" 当 前 时 间 : {}, 发 送 一 条 延 迟 {} 毫 秒 的 信 息 给 队 列 delayed.queue:{}",
			new Date(),delayTime, message);
}
  • 消费者
java 复制代码
public static final String DELAYED_QUEUE_NAME = "delayed.queue";

@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiveDelayedQueue(Message message){
	String msg = new String(message.getBody());
	log.info("当前时间: {},收到延时队列的消息: {}", new Date().toString(), msg);
}
  • 查看效果
相关推荐
源码梦想家2 小时前
云计算与边缘计算:未来数字化转型的关键驱动力
rabbitmq
-大头.3 小时前
深入解析ZooKeeper核心机制
分布式·zookeeper·wpf
2501_941148155 小时前
区块链技术与未来金融:去中心化的革命
rabbitmq
s***4535 小时前
【RabbitMQ】超详细Windows系统下RabbitMQ的安装配置
windows·分布式·rabbitmq
2501_941798736 小时前
人工智能与大数据:智慧医疗的未来探索与挑战
rabbitmq
jinxinyuuuus6 小时前
Info Flow:分布式信息采集、数据去重与内容分级的工程实现
人工智能·分布式·程序人生·生活
2501_941147716 小时前
边缘计算:未来智能时代的关键技术
rabbitmq
2501_9418771310 小时前
边缘计算:推动智能化转型的隐形引擎
rabbitmq
2501_9418868610 小时前
区块链技术:从金融创新到行业赋能的全景视野
rabbitmq