[业务逻辑] 订单超时怎么处理

news2024/10/7 8:25:31

文章目录

      • 1.订单的过程分析
      • 2.JDK自带的延时队列 (单机)
      • 3.RabbitMQ的延时消息 (消息队列方案)
      • 4.RocketMQ的定时消息 (消息队列方案)
      • 5.Redis过期监听 (Redis方案)
      • 6.定时任务分布式批处理 (扫表轮训方案)
      • 7.总结

1.订单的过程分析

在这里插入图片描述

一个订单流程中有许多环节要用到超时处理

  1. 买家超时未付款:比如超过15分钟没有支付,订单自动取消。
  2. 商家超时未发货:比如商家超过1个月没发货,订单自动取消。
  3. 买家超时未收货:比如商家发货后,买家没有在14天内点击确认收货,则系统默认自动收货。

超时订单的结局方式:

  • 扫表轮训
  • 懒删除
  • 消息队列实现
  • Redis实现

2.JDK自带的延时队列 (单机)

JDK中提供了一种延迟队列数据结构DelayQueue,其本质是封装了PriorityQueue,可以把元素进行排序。

在这里插入图片描述

  1. 把订单插入DelayQueue中,以超时时间作为排序条件,将订单按照超时时间从小到大排序。

  2. 起一个线程不停轮询队列的头部,如果订单的超时时间到了,就出队进行超时处理,并更新订单状态到数据库中。

  3. 为了防止机器重启导致内存中的DelayQueue数据丢失,每次机器启动的时候,需要从数据库中初始化未结束的订单,加入到DelayQueue中。

优点:简单,不需要借助其他第三方组件,成本低。

缺点:

  • 所有超时处理订单都要加入到DelayQueue中,占用内存大。

  • 没法做到分布式处理,只能在集群中选一台leader专门处理,效率低。

  • 不适合订单量比较大的场景。

3.RabbitMQ的延时消息 (消息队列方案)

  1. RabbitMQ Delayed Message Plugin

  2. 消息的TTL+死信Exchange

RabbitMQ Delayed Message Plugin是官方提供的延时消息插件,虽然使用起来比较方便,但是不是高可用的,如果节点挂了会导致消息丢失。

消息的TTL+死信Exchange解决方案:

在这里插入图片描述

  1. 定义一个BizQueue,用来接收死信消息,并进行业务消费。

  2. 定义一个死信交换机(DLXExchange),绑定BizQueue,接收延时队列的消息,并转发给BizQueue。

  3. 定义一组延时队列DelayQueue_xx,分别配置不同的TTL,用来处理固定延时5s、10s、30s等延时等级,并绑定到DLXExchange。

  4. 定义DelayExchange,用来接收业务发过来的延时消息,并根据延时时间转发到不同的延时队列中。

优点:可以支持海量延时消息,支持分布式处理。
缺点:

  • 不灵活,只能支持固定延时等级。

  • 使用复杂,要配置一堆延时队列。

4.RocketMQ的定时消息 (消息队列方案)

在这里插入图片描述
只需要在发送消息的时候设置延时时间即可

MessageBuilder messageBuilder = null;
Long deliverTimeStamp = System.currentTimeMillis() + 10L * 60 * 1000; //延迟10分钟
Message message = messageBuilder.setTopic("topic")
        //设置消息索引键,可根据关键字精确查找某条消息。
        .setKeys("messageKey")
        //设置消息Tag,用于消费端根据指定Tag过滤消息。
        .setTag("messageTag")
        //设置延时时间
        .setDeliveryTimestamp(deliverTimeStamp) 
        //消息体
        .setBody("messageBody".getBytes())
        .build();
SendReceipt sendReceipt = producer.send(message);
System.out.println(sendReceipt.getMessageId());

RocketMq定时消息的实现:

使用了经典的时间轮算法, 通过TimerWheel来描述时间轮不同的时刻,通过TimerLog来记录不同时刻的消息。
TimerWheel中的每一格代表着一个时刻,同时会有一个firstPos指向这个刻度下所有定时消息的首条TimerLog记录的地址,一个lastPos指向这个刻度下所有定时消息最后一条TimerLog的记录的地址。并且,对于所处于同一个刻度的的消息,其TimerLog会通过prevPos串联成一个链表。

在这里插入图片描述

当需要新增一条记录的时候,例如现在我们要新增一个 “1-4”。那么就将新记录的 prevPos 指向当前的 lastPos,即 “1-3”,然后修改 lastPos 指向 “1-4”。这样就将同一个刻度上面的 TimerLog 记录全都串起来了。

在这里插入图片描述

优点:

  • 精度高,支持任意时刻。

  • 使用门槛低,和使用普通消息一样。

缺点

  • 使用限制:定时时长最大值24小时。

  • 成本高:每个订单需要新增一个定时消息,且不会马上消费,给MQ带来很大的存储成本。

  • 同一个时刻大量消息会导致消息延迟:定时消息的实现逻辑需要先经过定时存储等待触发,定时时间到达后才会被投递给消费者。因此,如果将大量定时消息的定时时间设置为同一时刻,则到达该时刻后会有大量消息同时需要被处理,会造成系统压力过大,导致消息分发延迟,影响定时精度。

5.Redis过期监听 (Redis方案)

删除过期的key的时候, 进行判断状态是否是超时状态, 然后进行关闭订单
在这里插入图片描述

Redis支持过期监听,也能达到和RocketMQ定时消息一样的能力

1.redis配置文件开启"notify-keyspace-events Ex"

在这里插入图片描述

2.监听key的过期回调

@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory factory){
        RedisMessageListenerContainer container=new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        return container;
    }
}

@Component
public class RedisKeyExpirationListerner extends KeyExpirationEventMessageListener {
 
    public RedisKeyExpirationListerner(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
 
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String keyExpira = message.toString();
        System.out.println("监听到key:" + expiredKey + "已过期");
    }
}

在这里插入图片描述

在实际生产上不推荐

每当我们对一个key设置了过期时间,Redis就会把该key带上过期时间,存到过期字典中,在redisDb中通过expires字段维护:


typedef struct redisDb {
    dict *dict;    /* 维护所有key-value键值对 */
    dict *expires; /* 过期字典,维护设置失效时间的键 */
    ....
} redisDb;

过期字典本质上是一个链表

  • key是一个指针,指向某个键对象。

  • value是一个long long类型的整数,保存了key的过期时间。

在这里插入图片描述

Redis主要使用了定期删除和惰性删除策略来进行过期key的删除

Redis过期删除是不精准的,在订单超时处理的场景下,惰性删除基本上也用不到,无法保证key在过期的时候可以立即删除,更不能保证能立即通知。如果订单量比较大,那么延迟几分钟也是有可能的。

Redis过期通知也是不可靠的,Redis在过期通知的时候,如果应用正好重启了,那么就有可能通知事件就丢了,会导致订单一直无法关闭,有稳定性问题。如果一定要使用Redis过期监听方案,建议再通过定时任务做补偿机制。

如果无法删除的话会导致库存数据始终占着, 但是未支付也未取消支付。

6.定时任务分布式批处理 (扫表轮训方案)

开启一个定时任务去扫描订单表, 获取待支付状态的数据, 判断将一些超时状态的数据进行批量修改状态。

在这里插入图片描述

通过定时任务不停轮询数据库的订单,将已经超时的订单捞出来,分发给不同的机器分布式处理:

在这里插入图片描述

  • 稳定性强:基于通知的方案(比如MQ和Redis),比较担心在各种极端情况下导致通知的事件丢了。使用定时任务跑批,只需要保证业务幂等即可,如果这个批次有些订单没有捞出来,或者处理订单的时候应用重启了,下一个批次还是可以捞出来处理,稳定性非常高。

  • 效率高:基于MQ的方案,需要一个订单一个定时消息,consumer处理定时消息的时候也需要一个订单一个订单更新,对数据库tps很高。使用定时任务跑批方案,一次捞出一批订单,处理完了,可以批量更新订单状态,减少数据库的tps。在海量订单处理场景下,批量处理效率最高。

  • 可运维:基于数据库存储,可以很方便的对订单进行修改、暂停、取消等操作,所见即所得。如果业务跑失败了,还可以直接通过sql修改数据库来进行批量运维。

  • 成本低:相对于其他解决方案要借助第三方存储组件,复用数据库的成本大大降低。

缺点:没法做到精度很高。定时任务的延迟时间,由定时任务的调度周期决定。如果把频率设置很小,就会导致数据库的qps比较高,容易造成数据库压力过大,从而影响线上的正常业务。
所以一般需要抽离出超时中心和超时库来单独做订单的超时调度

在这里插入图片描述

如何让超时中心不同的节点协同工作,拉取不同的数据:

通常的解决方案是借助任务调度系统,开源任务调度系统大多支持分片模型,比较适合做分库分表的轮询,比如一个分片代表一张分表。但是如果分表特别多,分片模型配置起来还是比较麻烦的。另外如果只有一张大表,或者超时中心使用其他的存储,这两个模型就不太适合。

阿里巴巴分布式任务调度系统SchedulerX:

  1. 通过实现map函数,通过代码自行构造分片,SchedulerX会将分片平均分给超时中心的不同节点分布式执行。在这里插入图片描述2. 通过实现reduce函数,可以做聚合,可以判断这次跑批有哪些分片跑失败了,从而通知下游处理。

在这里插入图片描述

使用SchedulerX定时跑批解决方案:

  • 免运维、成本低:不需要自建任务调度系统,由云上托管。

  • 可观测:提供任务执行的历史记录、查看堆栈、日志服务、链路追踪等能力。

  • 高可用:支持同城双活容灾,支持多种渠道的监控报警。

  • 混部:可以托管阿里云的机器,也可以托管非阿里云的机器。

7.总结

如果对于超时精度比较高,超时时间在24小时内,且不会有峰值压力的场景,推荐使用RocketMQ的定时消息解决方案

电商业务下,许多订单超时场景都在24小时以上,对于超时精度没有那么敏感,并且有海量订单需要批处理,推荐使用基于定时任务的跑批解决方案

  • 扫表轮训: 定时任务分布式批处理, 阿里使用SchedulerX
  • 懒删除: 通过设置一个数据库的状态, 用户查询订单的时候去判断状态看是否关闭订单
  • 消息队列实现: RabbitMQ的ttl和延迟队列, RocketMQ的定时消息
  • Redis实现: 删除策略实现不乐观

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

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

相关文章

echarts 饼状图 label 既在内部显示数值(百分比),又显示外部指示线

需求 项目开发中,产品经理绘制的原型图中,需要前端实现一个饼状图,且既在饼图内部中 显示 百分比,又显示 外部指示线及数值,效果如下图所示: 查了下 Echarts 官网文档,需要配置 series 下的 la…

使用vs2022编译yolov5+tensorRT+cuda+cudnn代码进行混合编译

首先依赖有cuda、cudnn、tensorrt、protobuf,从Linux的代码直接移植过来这些库是没法使用的,需要下载对应win的下的版本,其中cuda、cudnn和tensorrt直接从官方下载即可,但是protobuf需要自己编译一下(protobuf3.11.4&a…

unix高级编程-fork之后父子进程共享文件

~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件. 这里我看到的是centos的操作,但我用的是debian系的ubuntu,百度了一下发现debian的在这里…

【数据挖掘实战】——基于水色图像的水质评价

项目地址:Datamining_project: 数据挖掘实战项目代码 目录 一、背景和挖掘目标 1、问题背景 2、水色分类 3、原始数据 4、挖掘目标 二、分析方法和过程 1、初步分析 2、总体流程 第1步:数据预处理——图像切割 第2步:特征提取 ​…

健身蓝牙耳机推荐,推荐五款适合健身的蓝牙耳机

出门运动健身,有音乐的陪伴是我们坚持运动的不懈动力,在健身当中佩戴的耳机,佩戴舒适度以及牢固程度是我们十分需要注意的,还不知道如何选择健身蓝牙耳机,可以看看下面这些运动蓝牙耳机分享。 1、南卡Runner Pro4骨传…

大bug!ChatGPT居然不懂最新的量子计算?

(图片来源:网络)近期,背靠微软的AI语言模型ChatGPT风靡全网,以社交媒体为传播媒介,仅5天,注册用户数就超过100万,2个月破亿。随后谷歌眼红不已,匆忙召开自研AI搜索工具Ba…

【Python】变量类型,赋值+多个变量赋值

嗨害大家好鸭~我是小熊猫(✿◡‿◡) 好像还有一些小伙伴还不是很会python的基础鸭~ 没关系啦~这不是还有我小熊猫嘛 ~ 源码资料电子书:点击此处跳转文末名片获取 Python 变量类型 变量是存储在内存中的值, 这就意味着在创建变量时会在内存中开辟一个空间。 基于…

java面试题-JUC集合类

ConcurrentHashMap1.为什么HashTable慢? 它的并发度是什么? 那么ConcurrentHashMap并发度是什么?首先,HashTable 是一种线程安全的哈希表,它内部使用的是同步锁来保证线程安全。在并发读写的场景下,同步锁会导致线程的阻塞,从而…

Linux 软件包安装

目录 通过源代码编译安装 通过RPM软件包安装 通过Yum软件仓库安装 配置本地Yum源 通过yum安装软件 通过Dnf软件仓库安装 Linux软件包安装有四种方式:源代码、RPM、Yum、Dnf安装四种方式 通过源代码编译安装 通过源代码编译安装可以根据需求定制软件&#xff…

Prefect 工作流中修改ray的地址

事情的起因 prefect 默认创建ray在127.0.0.1 上,我想在外网访问,想修改为0.0.0.0 尝试解决思路1(可行) 自己本地安装一个ray,但是连接不了redis重新安装redis,使用docker安装的docker 安装需要将redis.…

数据结构-算法的空间复杂度(1.2)

目录 1.空间复杂度 1.1 例子 1.2 空间的特殊性质 写在最后: 1.空间复杂度 空间复杂度也是一个数学表达式, 是对一个算法在运行过程中临时占用存储空间大小的量度。 他也是用大O渐进表示法。 1.1 例子 例1: 冒泡排序: v…

【C语言进阶】字符串函数与内存函数的学习与模拟实现

​ ​📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C语言进阶 🎯长路漫漫浩浩,万事皆有期待 文章目录1.字符串处理函数介…

【设计模式】对象行为型模式

行为创建型模式 系列综述: 来源:该系列是主要参考《大话设计模式》和《设计模式(可复用面向对象软件的基础)》,其他详细知识点拷验来自于各大平台大佬的博客。 总结:汇总篇 如果对你有用,希望关注点赞收藏一波。 文章目…

【超分顶会详解+部署】ESRT:Transformer for Single Image Super-Resolution

文章目录ESRT1. 超分基本知识1.1 SRF1.2 xxx_img1.3 裁剪1.4 超分模型评估标准2. LCB、LTB 模块2.1 序列模型3. 损失函数4. 部署运行4.1 数据集4.1.1 训练集4.1.2 验证集4.1.3 测试集4.2 数据集转换4.3 训练4.4 测试4.5 效果ESRT ESRT(Efficient Super-Resolution …

直播预告 | 企业如何轻松完成数据治理?火山引擎 DataLeap 给你一份实战攻略!

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 企业数字化转型正席卷全球,这不仅是趋势所在,也是企业发展必然面对的考题,也是企业最关心、最难决策的难题,数字化不…

C/C++每日一练(20230222)

目录 1. 部分复制字符串(★) 2. 按字典顺序排列问题(★★) 3. 地下城游戏(★★★) 附录 动态规划 1. 部分复制字符串 将字符串2小写字母复制到字符串1:编写程序,输入字符串s2,将其中所有小写字母复制到字符串数组strl中。例如:aal1bb22cc33de4AA55…

简历上面的项目经历怎么写?怎么写才能显得突出?

项目经历可不可以是课堂项目? 其实对很多同学来说,不是不会写项目经历,而是根本不知道什么是项目经历,哪些内容可以写在项目经历中。所以看到简历中的项目经历模块,感觉不知道怎么写?那么对于大学生来说,即使你大学四年中没有太多活动、竞赛、科研的经历,但是你一定上过…

Leetcode力扣秋招刷题路-0088

从0开始的秋招刷题路,记录下所刷每道题的题解,帮助自己回顾总结 88. 合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 …

学术贴 | FPGA 加速图数据库查询执行

导读本篇博客主要讲解发布于 Microprocessors and Microsystems 的文章《Semi-static Operator Graphs for Accelerated Query Execution on FPGAs》,介绍它所提出的算法与试验结果,并结合实际情况给出一些思考。一、背景介绍在当今的数据化场景越来越丰…

【11】FreeRTOS的延时函数

目录1.延时函数-介绍2.相对延时函数-解析2.1函数prvAddCurrentTaskToDelayedList-解析2.3滴答定时器中断服务函数xPortSysTickHandler()-解析2.4函数taskSWITCH_DELAYED_LISTS() -解析3.延时函数-实验4.总结1.延时函数-介绍 函数描述vTaskDelay()相对延时xTaskDelayUntil()绝对…