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

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. Kubernetes K8S之Pod跨namespace名称空间访问Service服务

    Kubernetes的两个Service(ServiceA.ServiceB)和对应的Pod(PodA.PodB)分别属于不同的namespace名称空间,现需要PodA和PodB跨namespace ...

  2. 使用Excel设计fastreport报表

    最近做项目遇到一些报表设计的问题.项目的报表使用 Fast Report,然而该控件调整报表格式比较繁琐,有时候调整某一栏的宽度,整个标题栏都需要调整,且调整以后 还不一定能对齐.客户的很多报表都是基 ...

  3. 学习 | css3基本动画之demo篇

    移动端使用的框架是zepto,但是zepto的内置对象没有传统的animate这个方法,效果都是需要css3来实现的,zepto也不支持fadeIn和fadeOut等一些基本的动画,基于这一现状,我自 ...

  4. JVM七大垃圾回收器下篇G1(Garbage First)

    G1回收器:区域化分代式 既然我们已经有了前面几个强大的GC,为什么还要发布Garbage First (G1)GC?  原因就在于应用程序所应对的业务越来越庞大.复杂,用户越来越多,没有GC就不能保 ...

  5. java虚拟机5 字节码

    java字节码本质是java程序的格式化表示,便于机器处理.所以他是java程序的另一种表示,java程序包含的信息他都包含并且更加结构化. java虚拟机字节码格式: magic 魔数,标识该文件是 ...

  6. hystrix总结之缓存

    通过实现HystrixCommand或者HystrixObservableCommand的getCacheKey方法,可以启动缓存. public class CommandUsingRequestC ...

  7. linux学习(九)Linux知识点汇总

    一.基础概念 Q:linux是什么?  Linux是一种基于UNIX的操作系统,它基于Linux内核,常被用作服务器的操作系统. Q:UNIX和LINUX有什么区别? Unix:收费的,商用的,拥有许 ...

  8. 【测试基础第六篇】bug定义及生命周期

    bug定义 狭义:软件程序的漏洞或缺陷 广义:测试工程师或用户所发现和提出的软件可改进的细节(增强型.建议性)或需求文档存在差异的功能实现 职责:发现bug,提给开发,让其修改 bug类型--了解 代 ...

  9. 划重点!AWS的湖仓一体使用哪种数据湖格式进行衔接?

    此前Apache Hudi社区一直有小伙伴询问能否使用Amazon Redshift查询Hudi表,现在它终于来了. 现在您可以使用Amazon Redshift查询Amazon S3 数据湖中Apa ...

  10. OpenCV图像处理学习笔记-Day1

    OpenCV图像处理学习笔记-Day1 目录 OpenCV图像处理学习笔记-Day1 第1课:图像读入.显示和保存 1. 读入图像 2. 显示图像 3. 保存图像 第2课:图像处理入门基础 1. 基本 ...