你还在用Quartz处理延时任务吗?太out了,看狼少的MQ延时队列优雅解决

实现原理请移步至:小生不才-俏如来

资源部署

1. 交换机
  1. 交换机可以接收死信消息,部署一个即可
2. 队列
  1. 异步下单队列
  2. 异步支付队列
  3. 死信消息队列
  4. 延时消息队列(延时不是死信,延时队列不需要监听,而死信队列需要监听)
3. 路由键
都与同一个交换机进行绑定
  1. 异步下单路由键
  2. 异步支付路由键
  3. 死信消息路由键
  4. 延时消息路由键

业务流程

  1. 调用下单接口,使用rabbitmq进行异步下单,将订单信息封装,转发到订单队列中
  2. 消费者监听订单队列,将订单信息保存到数据库中,同时再次向延时队列发送消息
  3. 消费者不会监听延时队列,到达预定时间后,会将信息自动转发到死信队列中
若未完成支付
  1. 消费者监听到死信队列中的信息,从数据库中查询到订单状态为未支付,将订单状态改为取消
若完成支付
  1. 调用支付接口,将支付订单号等信息封装为对象,转发到支付队列中
  2. 消费者监听支付队列,将支付信息保存到数据库中,同时将订单状态改为已完成
  3. 消费者监听死信队列,从数据库中查询订单状态为已完成,不进行任何操作

延迟队列实战

1. 项目依赖
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
    </parent>

    <dependencies>
        <!--引入web包,调用接口进行测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
		<!--springboot集成rabbitmq-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2. yml配置
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /

server:
  port: 80
3. 配置交换机和队列
package com.qiuming.rabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

//rabbitMQ的配置
@Configuration
public class MQConfig {
    //定义交换机
    public static final String EXCHNAGE_DELAY = "EXCHNAGE_DELAY";
    
    //定义订单队列
    public static final String QUEUE_ORDER = "QUEUE_ORDER";
    //定义死信队列
    public static final String QUEUE_DELAY = "QUEUE_DELAY";
    //定义支付队列
    public static final String QUEUE_PAY = "QUEUE_PAY";
    //定义非监听队列,即用于延时消息的发送
    public static final String QUEUE_UNKNOW = "QUEUE_UNKNOW";
    
    //定义订单队列路由键
    public static final String ROUTINGKEY_QUEUE_ORDER = "ROUTINGKEY_QUEUE_ORDER";
    //定义死信队列路由键
    public static final String ROUTINGKEY_QUEUE_DELAY = "ROUTINGKEY_QUEUE_DELAY";
    //定义支付队列路由键
    public static final String ROUTINGKEY_QUEUE_PAY = "ROUTINGKEY_QUEUE_PAY";
    //定义非监听队列路由键,即用户绑定延时消息
    public static final String ROUTINGKEY_QUEUE_UNKNOW = "ROUTINGKEY_QUEUE_UNKNOW";

    //定义交换机
    @Bean
    public Exchange exchangeDelay(){
        return ExchangeBuilder.topicExchange(EXCHNAGE_DELAY).durable(true).build();
    }
   
    //该队列为订单队列
    @Bean(QUEUE_ORDER)
    public Queue queueOrder(){
        return new Queue(QUEUE_ORDER,true);
    }
    //该队列接收死信交换机转发过来的消息
    @Bean(QUEUE_DELAY)
    public Queue queueDelay(){
        return new Queue(QUEUE_DELAY,true);
    }
    //该队列为支付队列
    @Bean(QUEUE_PAY)
    public Queue queuePay(){
        return new Queue(QUEUE_PAY,true);
    }
    //该队列为延时队列
    @Bean(QUEUE_UNKNOW)
    public Queue queueUnKnow(){
        Map<String,Object> map = new HashMap<>();
        //过期的消息给哪个交换机的名字
        map.put("x-dead-letter-exchange", EXCHNAGE_DELAY);
        //死信交换机把消息个哪个个routingkey
        map.put("x-dead-letter-routing-key", ROUTINGKEY_QUEUE_DELAY);
        //队列过期时间10s
        map.put("x-message-ttl", 10000);
        return new Queue(QUEUE_UNKNOW,true,false,false,map);
    }
    
    //用路由键绑定交换机和队列
    @Bean
    public Binding queueOrderBinding(){
        return BindingBuilder.bind(queueOrder()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_ORDER).noargs();
    }
    @Bean
    public Binding queueDelayBinding(){
        return BindingBuilder.bind(queueDelay()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_DELAY).noargs();
    }
    @Bean
    public Binding queuePayBinding(){
        return BindingBuilder.bind(queuePay()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_PAY).noargs();
    }
    @Bean
    public Binding queueUnKnowBinding(){
        return BindingBuilder.bind(queueUnKnow()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_UNKNOW).noargs();
    }
    
    //定义json消息转换器
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}
4. 定义接口

定义两个接口用于测试,分别是下单接口和支付接口

package com.qiuming.rabbitmq.controller;

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.RestController;

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

import static com.qiuming.rabbitmq.config.MQConfig.*;

/**
 * rabbitmq控制层
 * @author qiuming
 * @date 2021/03/22 14:03
 **/
@RestController
public class RabbitMQController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/rabbit-order")
    public void order(){
        rabbitTemplate.convertAndSend(
                EXCHNAGE_DELAY,
                ROUTINGKEY_QUEUE_ORDER,
                "已生成订单,请在10s内完成支付");
    }
    
    @GetMapping("/rabbit-pay")
    public void pay(){
        Map<String, Object> map = new HashMap<>(4);
        map.put("orderSn", 1L);
        map.put("message", "已完成支付");
        map.put("success", true);
        rabbitTemplate.convertAndSend(
            EXCHNAGE_DELAY,
            ROUTINGKEY_QUEUE_PAY,
            map);
    }
}
5. 创建消费者
package com.qiuming.rabbitmq.consumer;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

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

import static com.qiuming.rabbitmq.config.MQConfig.*;

/**
 * 消费者
 * @author qiuming
 * @date 2021/03/22 14:10
 **/
@Component
public class Consumer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //未连接数据库,使用静态变量进行测试
    private static Boolean success = null;

    /**
     * 死信队列,延时时间到达进入
     * @param message
     */
    @RabbitListener(queues = QUEUE_DELAY)
    public void listener1(@Payload Map<String,Object> map,Message message){
        //从数据库中查询数据,看订单是否支付完成
        System.out.println("10s到了,死信队列触发");
        if (success!=null && success) {
            System.out.println("支付已成功,死信队列不执行任何操作");
            success = null;
            return ;
        }
        //订单在数据库中处于未完成
        System.out.println(map.get("message"));
        success = null;
    }

    /**
     * 生成订单队列
     * @param message
     */
    @RabbitListener(queues = QUEUE_ORDER)
    public void listener2(@Payload String str,Message message) {
        System.out.println(str);
        //设置10s的支付时间,失败支付则计入死信队列
        Map<String, Object> map = new HashMap<>(4);
        success = false;
        map.put("orderSn", 1L);
        map.put("message", "未在规定时间内完成支付,订单取消");
        rabbitTemplate.convertAndSend(EXCHNAGE_DELAY,ROUTINGKEY_QUEUE_UNKNOW,map);
    }

    /**
     * 支付订单队列
     */
    @RabbitListener(queues = QUEUE_PAY)
    public void listener3(@Payload Map<String,Object> map, Message message) throws InterruptedException {
        success = (boolean)map.get("success");
        if (success) {
            System.out.println("order sn = "+map.get("orderSn")+",message = "+"支付成功");
        }else {
            System.out.println("支付失败");
        }
    }
}
6. 启动类
package com.qiuming.rabbitmq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * rabbitmq启动类
 * @author qiuming
 * @date 2021/03/22 14:03
 **/
@SpringBootApplication
public class RabbitMQApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(RabbitMQApplication.class);
    }
}

打完收工(测试)

下单接口:http://localhost/rabbit-order

支付接口:http://localhost/rabbit-pay

1. 调用下单接口,不调用支付接口
在这里插入图片描述
2. 先调用下单接口,再调用支付接口
在这里插入图片描述

此外:可以根据服务器配置,适当减少50ms-100ms的延时时间,使得业务更加精确

码云传送门

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值