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

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)

spring boot:用rocketmq发送延时消息用来取消订单(spring boot 2.3.3)的更多相关文章

  1. springboot项目整合rabbitMq涉及消息的发送确认,消息的消费确认机制,延时队列的实现

    1.引入maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactI ...

  2. RocketMQ源码 — 九、 RocketMQ延时消息

    上一节消息重试里面提到了重试的消息可以被延时消费,其实除此之外,用户发送的消息也可以指定延时时间(更准确的说是延时等级),然后在指定延时时间之后投递消息,然后被consumer消费.阿里云的ons还支 ...

  3. rocketmq 延时消息

    rocketmq  的延时消息不能支持任意延时,她定义了18 个延时等级,并且我们可以指定这18 个延时等级的延时时间. 发送消息的时候只需在消息中指定 当前消息的 延时等级即可,并且这个延时消息不是 ...

  4. 在服务端处理同步发送小消息的性能上Kafka>RocketMQ>RabbitMQ

    在发送小消息的场景中,三个消息中间件的表现区分明显: Kafka的吞吐量高达17.3w/s,远超其他两个产品.这主要取决于它的队列模式保证了写磁盘的过程是线性IO.此时broker磁盘IO已达瓶颈. ...

  5. RocketMQ(6)---发送普通消息(三种方式)

    发送普通消息(三种方式) RocketMQ 发送普通消息有三种实现方式:可靠同步发送.可靠异步发送.单向(Oneway)发送. 注意 :顺序消息只支持可靠同步发送. GitHub地址: https:/ ...

  6. 阿里云RocketMQ定时/延迟消息队列实现

    新的阅读体验:http://www.zhouhong.icu/post/157 一.业务需求 需要实现一个提前二十分钟通知用户去做某件事的一个业务,拿到这个业务首先想到的最简单得方法就是使用Redis ...

  7. ActiveMQ笔记(7):如何清理无效的延时消息?

    ActiveMQ的延时消息是一个让人又爱又恨的功能,具体使用可参考上篇ActiveMQ笔记(6):消息延时投递,在很多需要消息延时投递的业务场景十分有用,但是也有一个缺陷,在一些大访问量的场景,如果瞬 ...

  8. Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码

    一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...

  9. spring boot Rabbitmq集成,延时消息队列实现

    本篇主要记录Spring boot 集成Rabbitmq,分为两部分, 第一部分为创建普通消息队列, 第二部分为延时消息队列实现: spring boot提供对mq消息队列支持amqp相关包,引入即可 ...

随机推荐

  1. python中unittest查找测试用例

    将整个BeautifulReport文件夹放到site-packages目录下

  2. java oop 修饰符&关键字

    一.修饰符 Java中四种访问权限修饰符: private 缺省(default) protected public 修饰类的成员,用来限定对象对该类成员的访问权限. 修饰符 类内部 同一个包 不同包 ...

  3. [LeetCode]子串的最大出现次数(字符串)

    题目 给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数: 子串中不同字母的数目必须小于等于 maxLetters . 子串的长度必须大于等于 minSize 且小于等于 ...

  4. PHP基础之面向对象篇

    前言 前面写的都是运算符.流程控制.排序查找等,下面说一说面向对象的一些内容.这是前面写的,有兴趣可以去看一看. PHP入门之类型与运算符 PHP入门之流程控制 PHP入门之函数 PHP入门之数组 P ...

  5. outh2

    之前做天猫精灵对接,就碰到了outh鉴权,当时实现好之后没有细细缕,今天看了一个博主的介绍,贴一下 转载自http://www.ruanyifeng.com/blog/2014/05/oauth_2_ ...

  6. hystrix(2) metrics

    上一节讲到了hystrix提供的五个功能,这一节我们首先来讲hystrix中提供实时执行metrics信息的实现.为什么先讲metrics,因为很多功能都是基于metrics的数据来实现的,它是很多功 ...

  7. python常用sys模块

    sys.argv 命令行参数List,第一个元素是程序本身路径 sys.modules.keys() 返回所有已经导入的模块列表 sys.exc_info() 获取当前正在处理的异常类,exc_typ ...

  8. 关于取整函数ceil(),floor(),round()函数得应用

    ceil()返回向上取整最接近的整数. double ceil(double); floor()返回向下取整最接近的整数. double floor(double); round()用于对浮点数的四舍 ...

  9. 栈帧的内部结构--动态链接 (Dynamic Linking)

    每个栈帧中包含: 局部变量表(Local Variables) 操作数栈(Opreand Stack) 或表达式栈 动态链接 (Dynamic Linking) (或指向运行时常量的方法引用) 动态返 ...

  10. JSTL1.1函数标签库(functions)

    JSTL1.1函数标签库(functions) 在jstl中的fn标签也是我们在网页设计中经常要用到的很关键的标签,在使用的时候要先加上头 <%@ taglib uri="http:/ ...