快速入门rabbitmq(延迟队列篇)

一.基础知识

  • 概念:用来存放需要在指定时间被处理的元素的队列,即希望到了指定时间以前或以后处理队列内的元素

  • 关系:属于死信队列三种情况中消息TTL过期的情况(从下图可表现出来)

  • 结构效果图
    在这里插入图片描述
    说明:producer设置消息有效时间为t,如果c1这时不能处理消息,消息会在normal-queue中停留t时间,如果t之后c1还是不能处理信息,就会进入dead_exchange,进而通过死信队列给到c2来处理消息。从producer和c2的角度看,消息从发出到处理经过了时间t,就像是被延迟了t时间

  • 使用场景
    1.订单在十分钟之内未支付则自动取消
    2.用户发起退款,如果三天内没有处理则通知相关运营人员…

  • 相比定时任务
    1.定时任务适用于数据量少和时间要求宽松的情况,比如“如果账单一周内未支付则自动结算”
    2.延迟队列适用于数据量大和时效性强的情况,比如“订单在十分钟之内未支付则自动取消”

二.整合springboot并测试

  • 添加依赖:最重要的是spring-boot-starter-amqp,其他依赖为辅助测试
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <version>2.3.1</version>
        </dependency>
  • 修改配置文件(不要完全复制粘贴,数值填自己的)
spring.rabbitmq.host=192.168.121.129 
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
  • 队列TTL代码架构图
    在这里插入图片描述
    说明:创建两个队列QA和QB,两队列TTL分别设置为10s和40s,然后在创建一个交换机X和死信交换机Y,类型都是direct,创建一个死信队列QD,各部分绑定关系见上图连线

  • 创建配置类

@Configuration
public class TtlQueueConfig {
    //普通交换机名称
    public static final String X_EXCHANGE = "X";
    //死信交换机名称
    public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
    //普通队列名称
    public static final String QUEUE_A = "QA";
    public static final String QUEUE_B = "QB";
    //死信队列名称
    public static final String DEAD_LETTER_QUEUE = "QD";
   
   //声明xExchange别名
    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(X_EXCHANGE);
    }
   //声明yExchange别名
    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }
   //声明普通队列TTL为10s
    @Bean("queueA")
    public Queue queueA(){
        Map<String,Object> map = new HashMap<>(3);
        //设置死信交换机
        map.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //设置死信routingkey
        map.put("x-dead-letter-routing-key", "YD");
        //设置TTL
        map.put("x-message-ttl", 10000);
        return QueueBuilder.durable(QUEUE_A).withArguments(map).build();
    }
    
    @Bean("queueB")
    public Queue queueB(){
        Map<String,Object> map = new HashMap<>(3);
        map.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        map.put("x-dead-letter-routing-key", "YD");
        map.put("x-message-ttl", 40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(map).build();
    }
    //死信队列
    @Bean("queueD")
    public Queue queueD(){
        return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
    }
    //绑定
    @Bean
    public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange exchange){
        return BindingBuilder.bind(queueA).to(exchange).with("XA");
    }

    @Bean
    public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
                                  @Qualifier("xExchange") DirectExchange exchange){
        return BindingBuilder.bind(queueB).to(exchange).with("XB");
    }

    @Bean
    public Binding queueDBindingY(@Qualifier("queueD") Queue queueD,
                                  @Qualifier("yExchange") DirectExchange exchange){
        return BindingBuilder.bind(queueD).to(exchange).with("YD");
    }
}

  • 创建生产者:发送延迟消息
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMsgController {
   @Autowired
    RabbitTemplate rabbitTemplate;

   @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
      log.info("当前时间为:{},发送一条消息给两个TTL队列:{}",new Date().toString(),message);
      rabbitTemplate.convertAndSend("X", "XA","消息来自TTL为10s的队列:" +message);
      rabbitTemplate.convertAndSend("X", "XB","消息来自ttl为40s的队列:" + message);
   }
  • 创建消费者:接收消息
@Component
@Slf4j
public class DeadLetterQueueConsumer {
   @RabbitListener(queues = "QD")
   public void receive(Message message){
       String s = new String(message.getBody());
       log.info("当前时间为:{},收到死信队列的信息:{}", new Date().toString(), s);
   }
}

三.优化延迟队列

  • 原因:每增加一个新的时间需求,就要新增一个队列
  • 架构图
    在这里插入图片描述
    说明:新增一个队列QC,不设置TTL时间,而是通过producer发消息的时候指定延迟时间,从而不用通过新增队列来满足新的时间需求
  • 配置类优化
//之前的代码基础上新增普通队列QC
public static final String QUEUE_C = "QC";
@Bean("queueC")
    public Queue queueC(){
        Map<String, Object> map = new HashMap<>(3);
        map.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        map.put("x-dead-letter-routing-key", "YD");
        return QueueBuilder.durable(QUEUE_C).withArguments(map).build();
    }
  • 生产者优化
//通过地址参数ttlTime设置延迟时间
@GetMapping("/sendExpirationMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
       log.info("当前时间:{},发送一条时长{}毫秒TTL消息给队列QC:{}",
                new Date().toString(),ttlTime,message);
       //以下为部分convertAndSend参数
       //参数1:exchange
       //参数2:routingkey
       rabbitTemplate.convertAndSend("X", "XC", message,msg->{
       //设置延迟时长
           msg.getMessageProperties().setExpiration(ttlTime);
           return msg;
       });
   }
  • 结果展示
    在这里插入图片描述
    说明死信的问题:发现延迟时间2000比20000短,但最后消息却不是延迟时间短的先收到,原因在于rabbitmq只会检测第一个消息是否过期,如果第一个消息延时太长,那之后的消息即使延时很短也不会优先执行

四.基于插件实现延迟队列

  • 原因:解决主题三中由死信队列实现延迟队列出现的问题

1.安装插件

  • 资源链接:https://pan.baidu.com/s/1lwHInWuvIwOJiXDmJIye-Q;提取码:ufrp
  • 步骤一:进入rabbitmq安装目录下的plugins目录,命令如下:(注意…根据自己的来写,X版本号也是)
/.../rabbitmq/lib/rabbitmq_server-X.X.X/plugins
  • 步骤二:执行如下命令使插件生效,然后重启rabbitmq
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
  • 验证添加插件成功如下:出现如下x-delayed-message选项
    在这里插入图片描述

2.实现结构改变

  • 死信队列下的延迟队列架构图
    在这里插入图片描述
  • 基于插件的延迟队列架构图
    在这里插入图片描述
  • 综上可知,延迟功能由队列主导变为了由交换机主导

3.简单测试

  • 代码架构示意图
    在这里插入图片描述

  • 创建配置类

@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> map = new HashMap<>();
        map.put("x-delayed-type", "direct");
        /**
         * 参数1:交换机名
         * 参数2:交换机类型
         * 参数3:是否持久化
         * 参数4:是否自动删除
         * 参数5:其他
         */
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message",
                                  true,false,map);
    }

    @Bean
    public Binding delayedBinding(@Qualifier("delayedQueue") Queue delayedQueue,
                                  @Qualifier("delayedExchange") CustomExchange delayedExchange){
        return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    }


}
  • 添加生产者
@GetMapping("/sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime){
       log.info("当前时间:{},发送一条时长{}毫秒TTL消息给延迟队列delayed.queue:{}",
               new Date().toString(),delayTime,message);
       rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME, DelayedQueueConfig.DELAYED_ROUTING_KEY, message, msg->{
           msg.getMessageProperties().setDelay(delayTime);
           return msg;
       });
   }
  • 添加消费者
@Slf4j
@Component
public class DelayQueueConsumer {
    @RabbitListener(queues = DelayedQueueConfig.DELAYED_QUEUE_NAME)
    public void receiveDelayQueue(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到延迟队列的消息:{}", new Date().toString(), msg);
    }
}
  • 测试结果如下:实现了让多条消息中延迟时间短的先处理,提高效率
当前时间:Sun Nov 07 19:33:32 CST 2021,发送一条时长20000毫秒TTL消息给延迟队列delayed.queue:hello1
当前时间:Sun Nov 07 19:33:45 CST 2021,发送一条时长2000毫秒TTL消息给延迟队列delayed.queue:hello2
当前时间:Sun Nov 07 19:33:47 CST 2021,收到延迟队列的消息:hello2
当前时间:Sun Nov 07 19:33:52 CST 2021,收到延迟队列的消息:hello1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值