订单到期如何实现关闭?

电商、支付等系统中,一般都是先创建订单(支付单),再给用户一定的时间进行支付,如果没有按时支付的话,就需要把之前的订单(支付单)取消掉。这种类似的场景有很多,还有比如到期自动收货、超时自动退款、下单后自动发送短信等等都是类似的业务问题

那么我们应该怎么做才能更好的实现关闭呢?

一、被动关闭

被动关闭说简单一点就是不依靠外界,直接在业务层处理逻辑,当用户访问一个订单的时候判断此时的时间有没有超过过期时间,如果过了就关闭订单。

这种方法简单,不需要定时啥的,缺点也明显如果用户一直不来访问这个订单,那么有可能会造成大量的数据冗杂堆积。

二、定时任务

还有一种最容易想到的就是定时任务,这种的话直接就用一些调度平台如xxl-job之类的直接使用就行,操作也比较简单,但是还是有几个问题:

1、时间不精准。 一般定时任务基于固定的频率、按照时间定时执行的,那么就可能会发生很多订单已经到了超时时间,但是定时任务的调度时间还没到,那么就会导致这些订单的实际关闭时间要比应该关闭的时间晚一些。

2、无法处理大订单量。 定时任务的方式是会把本来比较分散的关闭时间集中到任务调度的那一段时间,如果订单量比较大的话,那么就可能导致任务执行时间很长,整个任务的时间越长,订单被扫描到时间可能就很晚,那么就会导致关闭时间更晚。

3、对数据库造成压力。 定时任务集中扫表,这会使得数据库IO在短时间内被大量占用和消耗,如果没有做好隔离,并且业务量比较大的话,就可能会影响到线上的正常业务。

4、分库分表问题。 订单系统,一旦订单量大就可能会考虑分库分表,在分库分表中进行全表扫描,这是一个极不推荐的方案

所以,定时任务的方案,适合于对时间精确度要求不高、并且业务量不是很大的场景中。如果对时间精度要求比较高,这种方案不适用。(但是一般来说,订单的到期关闭这种业务,对时间精确度要求并不高,所以定时任务也是使用的最广泛的一种方案!)

三、JDK自带的DelayQueue

有这样一种方案,他不需要借助任何外部的资源,直接基于应用自身就能实现,那就是基于JDK自带的DelayQueue来实现。

DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。

基于延迟队列,是可以实现订单的延迟关闭的,首先,在用户创建订单的时候,把订单加入到DelayQueue中,然后,还需要一个常驻任务不断的从队列中取出那些到了超时时间的订单,然后在把他们进行关单,之后再从队列中删除掉。

这个方案需要有一个线程,不断的从队列中取出需要关单的订单。一般在这个线程中需要加一个while(true)循环,这样才能确保任务不断的执行并且能够及时的取出超时订单。

使用DelayQueue实现超时关单的方案,实现起来简单,不须要依赖第三方的框架和类库,JDK原生就支持了。

当然这个方案也不是没有缺点的,首先,基于DelayQueue的话,需要把订单放进去,那如果订单量太大的话,可能会导致内存不足的问题;另外,DelayQueue是基于JVM内存的,一旦机器重启了,里面的数据就都没有了。虽然我们可以配合数据库的持久化一起使用。而且现在很多应用都是集群部署的,那么集群中多个实例上的多个DelayQueue如何配合是一个很大的问题。

所以,基于JDK的DelayQueue方案只适合在单机场景、并且数据量不大的场景中使用,如果涉及到分布式场景,那还是不建议使用。

四、Kafka的时间轮

Kafka的时间轮,Kafka内部有很多延时性的操作,如延时生产,延时拉取,延时数据删除等,这些延时功能由内部的延时操作管理器来做专门的处理,其底层是采用时间轮实现的。

而且,为了解决有一些时间跨度大的延时任务,Kafka 还引入了层级时间轮,能更好控制时间粒度,可以应对更加复杂的定时任务处理场景;

Kafka 中的时间轮的实现是 TimingWheel 类,位于 kafka.utils.timer 包中。基于Kafka的时间轮同样可以得到O(1)时间复杂度,性能上还是不错的。

基于Kafka的时间轮的实现方式,在实现方式上有点复杂,需要依赖kafka,但是他的稳定性和性能都要更高一些,而且适合用在分布式场景中。

五、RocketMQ延迟消息

相比于Kafka来说,RocketMQ中有一个强大的功能,那就是支持延迟消息。

延迟消息,当消息写入到Broker后,不会立刻被消费者消费,需要等待指定的时长后才可被消费处理的消息,称为延时消息。

有了延迟消息,我们就可以在订单创建好之后,发送一个延迟消息,比如20分钟取消订单,那就发一个延迟20分钟的延迟消息,然后在20分钟之后,消息就会被消费者消费,消费者在接收到消息之后,去关单就行了。

但是,RocketMQ的延迟消息并不是支持任意时长的延迟的,它只支持:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h这几个时长。(商业版支持任意时长)

可以看到,有了RocketMQ延迟消息之后,我们处理上就简单很多,只需要发消息,和接收消息就行了,系统之间完全解耦了。但是因为延迟消息的时长受到了限制,所以并不是很灵活。

如果我们的业务上,关单时长刚好和RocketMQ延迟消息支持的时长匹配的话,那么是可以基于RocketMQ延迟消息来实现的。否则,这种方式并不是最佳的。(但是在RocketMQ 5.0中新增了基于时间轮实现的定时消息,可以解决这个问题!)

六、Redis过期监听

很多用过Redis的人都知道,Redis有一个过期监听的功能,

在 redis.conf 中,加入一条配置notify-keyspace-events Ex开启过期监听,然后再代码中实现一个KeyExpirationEventMessageListener,就可以监听key的过期消息了。

这样就可以在接收到过期消息的时候,进行订单的关单操作。

这个方案不建议大家使用,是因为Redis官网上明确的说过,Redis并不保证Key在过期的时候就能被立即删除,更不保证这个消息能被立即发出。所以,消息延迟是必然存在的,随着数据量越大延迟越长,延迟个几分钟都是常事儿。

而且,在Redis 5.0之前,这个消息是通过PUB/SUB模式发出的,他不会做持久化,至于你有没有接到,有没有消费成功,他不管。也就是说,如果发消息的时候,你的客户端挂了,之后再恢复的话,这个消息你就彻底丢失了。(在Redis 5.0之后,因为引入了Stream,是可以用来做延迟消息队列的。)

七、Redis的zset

虽然基于Redis过期监听的方案并不完美,但是并不是Redis实现关单功能就不完美了,还有其他的方案。

我们可以借助Redis中的有序集合——zset来实现这个功能。

zset是一个有序集合,每一个元素(member)都关联了一个 score,可以通过 score 排序来取集合中的值。

我们将订单超时时间的时间戳(下单时间+超时时长)与订单号分别设置为 score 和 member。这样redis会对zset按照score延时时间进行排序。然后我们再开启redis扫描任务,获取"当前时间 > score"的延时任务,扫描到之后取出订单号,然后查询到订单进行关单操作即可。

使用redis zset来实现订单关闭的功能的优点是可以借助redis的持久化、高可用机制。避免数据丢失。但是这个方案也有缺点,那就是在高并发场景中,有可能有多个消费者同时获取到同一个订单号,一般采用加分布式锁解决,但是这样做也会降低吞吐型。

但是,在大多数业务场景下,如果幂等性做得好的,多个消费者取到同一个订单号也无妨。

十一、Redisson + Redis


上面这种方案看上去还不错,但是需要我们自己基于zset这种数据结构编写代码,那么有没有什么更加友好的方式?有的,那就是基于Redisson。

Redisson是一个在Redis的基础上实现的框架,它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
Redisson中定义了分布式延迟队列RDelayedQueue,这是一种基于我们前面介绍过的zset结构实现的延时队列,它允许以指定的延迟时长将元素放到目标队列中。


其实就是在zset的基础上增加了一个基于内存的延迟队列。当我们要添加一个数据到延迟队列的时候,redisson会把数据+超时时间放到zset中,并且起一个延时任务,当任务到期的时候,再去zset中把数据取出来,返回给客户端使用。

基于Redisson的实现方式,是可以解决基于zset方案中的并发重复问题的,而且还能实现方式也比较简单,稳定性、性能都比较高。

 

实现的复杂度上(包含用到的框架的依赖及部署):

Redisson >  RocketMQ延迟消息 ≈ Redis的zset > Redis过期监听 ≈ kafka时间轮 > 定时任务  > JDK自带的DelayQueue > 被动关闭

不同的场景中也适合不同的方案:

  • 自己玩玩:被动关闭
  • 单体应用,业务量不大、JDK自带的DelayQueue、定时任务
  • 分布式应用,业务量不大:Redis过期监听、Redis的zset、定时任务
  • 分布式应用,业务量大、并发高:Redisson、kafka时间轮、RocketMQ延迟消息、定时任务
  • 业务量特别大:定时任务

总体考虑的话,考虑到成本,方案完整性、以及方案的复杂度,还有用到的第三方框架的流行度来说,个人比较建议优先考虑定时任务、Redisson+Redis、RocketMQ延迟消息等方案。但是,如果考虑到订单到期关闭的业务特点,如果在订单量特别大的时候,MQ其实并不适合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/147178.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

[笔记] 走行电机控制器 防摇摆功能的技术细节

防摇摆用于走行电机控制,一般用于小车。这里参考了数重的彩页: 1.原理 这个无效和有效的控制是靠启动时的幔起,和停车时的缓停实现的。他似乎对加速过程的力矩曲线做了某种控制,能够让启停时,必然的角度变化在运动中逐…

【时时三省】(C语言基础)指针笔试题3

山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 笔试题3 首先创建了一个数组 数组里面放了1 2 3 4 &a取出的是数组的地址 数组地址加1 如下图 直接从1跳到了四后面 然后强制类型转换成了int* 转换成int*之…

ModbusTCP通讯错误的排查

Modbus是一种由MODICON公司开发的工业现场总线协议标准,是一项应用层报文传输协议。该协议用于传输数字和模拟变量[1]。有关该协议的报文具体格式,以及一些基本概念,见[1]。 本文以一个例子,阐述当ModbusTCP通讯出现错误的时候&a…

01_RabbitMQ安装及工作模式

一、消息队列MQ 中间件 1.1 什么是消息队列 消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。 消息队列(Message Queue)是一…

鸿蒙开发(NEXT/API 12)【跨设备互通开发】远场通信服务

跨设备互通提供跨设备的相机、扫描、图库访问能力,平板或2in1设备可以调用手机的相机、扫描、图库等功能。 场景介绍 您通过此能力实现跨设备交互,可以使用其他设备的相机、扫描和图库功能。 约束与限制 需同时满足以下条件,才能使用该功…

COLORmap

在这段MATLAB代码中,surf(peaks)、map的定义以及colormap(map)的调用共同完成了以下任务: 1. **绘制曲面图**: - surf(peaks):这个函数调用了MATLAB内置的peaks函数来生成数据,并使用surf函数将这些数据绘制成一个…

CSS 选择器的分类与使用要点二

目录 非 VIP 用户可前往公众号进行免费阅读 标签选择器 id 选择器 类选择器 介绍 公共类 CSS 中优先用 class 选择器,慎用 id 选择器 后代选择器 交集选择器 以标签名作为开头 以类名作为开头 连续交集 并集选择器(分组选择器) 通配符* 儿子选择器 >(IE7…

CSS 的继承性、层叠性与权重问题解析

目录 非 VIP 用户可前往公众号进行免费阅读 继承性 层叠性 CSS的权重问题 如果权重一样,以后出现的为准 以权重大的为准 没有选中,权重为0,就近原则 权重只和css顺序有关 非 VIP 用户可前往公众号进行免费阅读 CSS 的继承性、层叠性与权重问题解析本文主要介绍了 C…

AIGC8: 高通骁龙AIPC开发者大会记录B

图中是一个小男孩在市场卖他的作品。 AI应用开发出来之后,无论是个人开发者还是企业开发者。 如何推广分发是面临的大问题。 做出来的东西一定要符合商业规律。否则就是实验室里面的玩物,或者自嗨的东西。 背景 上次是回顾和思考前面两个硬件营销总的…

解决Python Debug没有反应的问题

应该有伙伴和我一样,用的2024版本的VS code,但是用到的python解释器是3.6.x,或者是更旧版本的Python. 想要进行Debug就会在扩展里面安装 一般安装就会安装最新版本,但是debug时又没有反应,其主要原因是Python的版本与…

Gin框架入门(2)--异常捕获与日志实现

异常捕获 Go语言的异常捕获采用的是延迟处理的方法实现的,实际上就是利用defer,panic和recover三个关键字和函数来实现的。 关键字 defer关键字(函数) 这个关键字在控制语句中就有所涉及,本质上是采用一个栈的存储结构,在整个…

时钟的配置

在使用51单片机时,系统使用的时钟源是一个外部晶体振荡器,频率为12M。由于51单片机每个指令周期都是12分频的,所以实际工作频率仅为1M。2440作为一种性能远高于51的Soc,主频肯定要远远高于51,因此2440有着比51单片机复…

yolov8模型在Xray图像中关键点检测识别中的应用【代码+数据集+python环境+GUI系统】

yolov8模型在X yolov8模型在Xray图像中关键点检测识别中的应用【代码数据集python环境GUI系统】 1.背景意义 X射线是一种波长极短、穿透能力极强的电磁波。当X射线穿透物体时,不同密度和厚度的物质会吸收不同程度的X射线,从而在接收端产生不同强度的信号…

pycharm加载虚拟环境及运行代码

pycharm加载虚拟环境及运行代码 pycharm下载地址: https://www.jetbrains.com/pycharm/download/ 1.加载虚拟环境 选择pycharm图标,点击启动。 选择OPEN, 选择工程文件夹: 选择File->setting 选择python 解释器: Project--…

扫码挪车是怎么实现的呢?一篇文章带你了解一下!扫码挪车小程序基础版上线了!!!

挪车小程序系统源码的功能特点 快速定位与挪车请求:车主通过小程序可以快速定位车辆位置,并发送挪车请求。系统会自动将请求发送给附近的车主,提醒其尽快挪车。实时通信与交互:小程序支持实时通信功能,车主之间可以通…

【C++笔记】C++编译器拷贝优化和内存管理

【C笔记】C编译器拷贝优化和内存管理 🔥个人主页:大白的编程日记 🔥专栏:C笔记 文章目录 【C笔记】C编译器拷贝优化和内存管理前言一.对象拷贝时的编译器优化二.C/C内存管理2.1练习2.2 C内存管理方式2.3 operator new与operator…

tornado

Tornado通过使用非阻塞网络1/0,可以扩展到数以万计的开放链接,非常适合 长时间轮询,WebSockets和其他需要与每个用户建立长期连接的应用程序。 特点 注重性能优越,速度快解决高并发异步非阻塞websockets 长连接内嵌了HTTP服务器…

十一、 JDK17 新特性梳理

文章目录 为什么是JDK17语法层面新特性1、文本块2 、Switch 表达式增强3、instanceof的模式匹配4、var 局部变量推导 模块化及类封装1、记录类 record2 、隐藏类 Hidden Classes3 、密封类 Sealed Classes4、模块化 Module System1 、什么是模块化2、声明一个module3 、require…

“智能密钥管家”IKE

IKE的出现 上一篇通过IPSec实现了BJ到CS的业务互通,但是是通过手工方式把加密和验证密钥手动配置,为了保障安全性,就需要经常去修改这些密钥,小型场景还好,来来回回就这2个点, 修改起来不算麻烦&#xff…

[Redis] 渐进式遍历+使用jedis操作Redis+使用Spring操作Redis

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…