• spring boot:用rocketmq发送延时消息用来取消订单(spring boot 2.3.3)


    一,为什么要用延时消息来取消订单?

          1,为什么要取消订单

          在电商的下单过程中,需要在生成订单时扣减库存,

          但有可能发生这种情况:用户下了单,临时改变主意不再支付,

          则订单不能无限期的保留,因为还要把占用的库存数量释放出来,

          所以通常会在用户下单后半小时(或其他时长)把未支付的订单取消不再保留。

         2,取消订单的方法:

            通常我们会在crond中创建一个定时运行的任务,每1分钟执行一次,

            把下单时间超过半小时的取出来,检查订单状态是否还是未支付,

            如果仍未支付,则修改订单状态为无效,同时把库存数量加回

            这个做法的缺点是数据库繁忙时会增加数据库的压力

         3,rocketmq的延时消息功能可以精准的在指定时间把消息发送到消费者,

            而无需扫描数据库,

            在这里我们使用延时消息来实现取消订单功能

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: https://github.com/liuhongdi/

    说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,演示项目的相关信息

    1,项目地址:

    https://github.com/liuhongdi/mqdelay

    2,项目功能说明

            演示了用rocketmq发送延时消息

    3,项目结构:如图:

    三,配置文件说明

    1,send/pom.xml

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.rocketmq</groupId>
                <artifactId>rocketmq-client</artifactId>
                <version>4.7.1</version>
            </dependency>
            <!--fastjson begin-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.73</version>
            </dependency>

    2,receive/pom.xml

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.rocketmq</groupId>
                <artifactId>rocketmq-client</artifactId>
                <version>4.7.1</version>
            </dependency>
            <!--fastjson begin-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.73</version>
            </dependency>

    说明:两个模块的pom.xml内容相同

    3,receive/application.properties

    server.port=8081

    说明:两个模块同时运行时,需要把端口区分开,
             send不做修改,使用默认的8080端口
             receive这里指定使用8081端口

    四,java代码说明

    1,send/OrderMsg.java

    //发送的取消订单信息
    public class OrderMsg {
        //用户id
        private int userId;
        public int getUserId() {
            return this.userId;
        }
        public void setUserId(int userId) {
            this.userId = userId;
        }
    
        //订单sn
        private String orderSn;
        public String getOrderSn() {
            return this.orderSn;
        }
        public void setOrderSn(String orderSn) {
            this.orderSn = orderSn;
        }
    
    }

    说明:要取消的订单的消息模型,

    OrderMsg.java在两个模块中一致

    2,send/RocketConstants.java

    public class RocketConstants {//name server,有多个时用分号隔开
        public static final String NAME_SERVER = "127.0.0.1:9876";
        //topic的名字,应该从服务端先创建好,否则会报错
        public static final String TOPIC = "laoliutest";
    }

    rocketmq需要用到的name server和topic名字

    RocketConstants.java在两个模块中一致

    3,send/Producer.java

    //消息生产者类
    @Component
    public class Producer {
        private String producerGroup = "order_producer";
        private DefaultMQProducer producer;
        //构造
        public Producer(){
            //创建生产者
            producer = new DefaultMQProducer(producerGroup);
            //不开启vip通道
            producer.setVipChannelEnabled(false);
            //设定 name server
            producer.setNamesrvAddr(RocketConstants.NAME_SERVER);
            //producer.m
            start();
        }
    
        //使producer启动
        public void start(){
            try {
                this.producer.start();
            } catch (MQClientException e) {
                e.printStackTrace();
            }
        }
        //返回producer
        public DefaultMQProducer getProducer(){
            return this.producer;
        }
    
        //进行关闭的方法
        public void shutdown(){
            this.producer.shutdown();
        }
    }

    生产者类

    4,send/HomeController.java

    @RestController
    @RequestMapping("/home")
    public class HomeController {
    
        @Autowired
        private Producer producer;
    
        //初始化并发送消息
        @RequestMapping("/send")
        public String send() throws Exception {
    
            int userId = 1;
    
            //得到订单编号:
            DateTimeFormatter df_year = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
            LocalDateTime date = LocalDateTime.now();
            String datestr = date.format(df_year);
    
            //消息,指定用户id和订单编号
            OrderMsg msg = new OrderMsg();
            msg.setUserId(userId);
            msg.setOrderSn(userId+"_"+datestr);
    
            String msgJson = JSON.toJSONString(msg);
            //生成一个信息,标签在这里手动指定
            Message message = new Message(RocketConstants.TOPIC, "carttag", msgJson.getBytes());
            //delaytime的值:
            //messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
            message.setDelayTimeLevel(5);
            //发送信息
            SendResult sendResult = producer.getProducer().send(message);
            System.out.println("时间:"+ TimeUtil.getTimeNow()+";生产者发送一条信息,内容:{"+msgJson+"},结果:{"+sendResult+"}");
    
            return "success";
        }
    }

    发送消息

    注意延迟时间的值5对应1m,所以消费者应该会在1分钟后才收到消息

    5,receive/Consumer.java

    @Component
    public class Consumer {
    
        //消费者实体对象
        private DefaultMQPushConsumer consumer;
    
        //消费者组
        public static final String CONSUMER_GROUP = "order_consumer";
    
        //构造函数 用来实例化对象
        public Consumer() throws MQClientException {
            consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);
            consumer.setNamesrvAddr(RocketConstants.NAME_SERVER);
            //指定消费模式
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
            //指定订阅主题
            //指定订阅标签,*代表所有标签
            consumer.subscribe(RocketConstants.TOPIC, "*");
            //注册一个消费消息的Listener
            //对消息的消费在这里实现
            //两种回调 MessageListenerConcurrently 为普通监听,MessageListenerOrderly 为顺序监听
            consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
                //遍历接收到的消息
                try {
                    for (Message msg : msgs) {
                        //得到消息的body
                        String body = new String(msg.getBody(), "utf-8");
                        //用json转成对象
                        OrderMsg msgOne = JSON.parseObject(body, OrderMsg.class);
                        //打印用户id
                        System.out.println("消息:用户id:"+msgOne.getUserId());
                        //打印订单编号
                        System.out.println("消息:订单sn:"+msgOne.getOrderSn());
                        //打印消息内容
                        System.out.println("时间:"+ TimeUtil.getTimeNow()+";消费者已接收到消息-topic={"+msg.getTopic()+"}, 消息内容={"+body+"}");
                    }
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            });
    
            consumer.start();
            System.out.println("消费者 启动成功=======");
        }
    }

    6,其他非关键代码可查看github

    五,测试效果

    1,分别启动两个模块

    2,访问:

    http://127.0.0.1:8080/home/send

    返回:

    success

    查看send的控制台:

    时间:2020-09-17 14:56:53.207;生产者发送一条信息,
    内容:{{"orderSn":"1_20200917145653166","userId":1}},
    结果:{SendResult [sendStatus=SEND_OK, msgId=C0A803D5231F42A57993559ADFC50000, offsetMsgId=7F00000100002A9F0000000000016E7B, 
    messageQueue=MessageQueue [topic=laoliutest, brokerName=broker-a, queueId=0], queueOffset=13]}

    注意发送的时间:2020-09-17 14:56:53.207

    查看receive的控制台:

    消息:用户id:1
    消息:订单sn:1_20200917145653166
    时间:2020-09-17 14:57:53.212;
    消费者已接收到消息-topic={laoliutest}, 
    消息内容={{"orderSn":"1_20200917145653166","userId":1}}

    注意接收到的时间:2020-09-17 14:57:53.212

    时长整好是60秒,和我们在代码中的设置一致

    六,查看spring boot版本:

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.3.3.RELEASE)
  • 相关阅读:
    数组协变性
    tomcat源码阅读23
    用枚举来实现单例模式
    CSS 的 zindex 属性
    屏幕大小与视区大小
    CSS 生成的模态窗口
    事件处理程序的绑定
    事件对象的属性和使用
    android打电话,接电话,挂电话过程
    ubuntu 12.04编译ics
  • 原文地址:https://www.cnblogs.com/architectforest/p/13685314.html
Copyright © 2020-2023  润新知