Redis实现延迟队列(为订单超时关闭提供更多的解决方案)

电商场景中的问题向来很受面试官的青睐,因为业务场景大家都相对更熟悉,相关的问题也很有深度,也有代表性,能更方便地考察候选人的技术水平。

比如商品购买下单支付的流程,在买家购买商品后会先生成订单,之后有15或者30分钟的支付时间,如果超时未支付就会自动取消这个订单。

面试官:订单超时未支付自动取消,这个你用什么方案实现?

这里就不得不提延迟队列了,延迟队列是一种特殊的消息队列,它允许消息在特定时间点或延迟一段时间后才被消费者处理。这一特性使得系统能够更加灵活地控制任务的执行时机。延迟队列作为一种重要的消息队列模式,广泛应用于订单超时处理、定时任务处理、邮件延迟发送等场景。

延迟队列的实现方式多样,比如RocketMQ、RabbitMQ等消息队列本身就支持延迟队列的功能,如果公司正在用这些MQ组件,那可以直接使用。如果公司没有使用这些MQ组件,而在使用Redis,那么我们就可以考虑使用Redis实现延迟队列了。

Redis的Sorted Set数据结构天然适合实现延迟队列。可以将任务ID作为成员(member),任务的执行时间戳作为分数(score)。这样,通过ZADD命令可以轻松地按照执行时间将任务插入到集合中。而ZRangeByScore或ZRemRangeByScore命令则可以在合适的时机取出或删除已到期的任务。如下图:

图片

实现步骤主要包括:

1、任务入队:将任务详情序列化后存储,并以其执行时间戳作为score,通过ZADD命令加入到Sorted Set中。

2、任务出队:使用ZRANGEBYSCORE命令,配合WITHSCORES选项,获取当前时间戳之前的所有任务,并通过分数(score)判断哪些任务已经到期,然后进行处理。

3、周期性检查:通过启动一个额外的定时任务周期性检查并处理已到期的任务。

可能你会说,通过额外的定时任务检查还是挺麻烦的,是否可以使用Redis的Keyspace Notifications,订阅key过期事件来做?

答案是不可以。因为Redis的key过期事件并不能保证key过期的时刻能够及时发出通知事件,甚至不能保证key过期能发出事件。原因是,Redis删除过期key的时机是:客户端访问该key时Redis服务端发现过期或者Redis后台任务检测到这个key过期。如果一直不访问这个key,那有可能长期不能发现key过期,也就不会产生key过期的事件了。设置的key过期精确度如此不可控,这对于大部分使用延迟队列的业务场景应该是不可接受的。

实现生产可用的延迟队列还需要关注什么

按照上述的思路去具体实现一个延迟队列的话,还需要关注以下几点,这样才能打造出一个生产环境可用的好方案。

1、首先是性能。如果底层只采用一个Sorted Set,数据量大的时候,比如同时有几百万人下单,这些数据被存储到同一个Sorted Set,就容易引发性能瓶颈。可以采用指定数量的Sorted Set来解决此问题,这样生产和消费延迟消息的并发处理效率会提升。

2、其次是原子操作。在消费消息的时候可能涉及查询和删除的两步操作,有可能还涉及数据库等其他操作,如果部分处理失败,可能会造成消息丢失或者重复处理的问题。需要采用重试机制和幂等处理机制来应对。

3、最后是简单易用的封装。要实现好延迟队列,不是一件轻松的事儿。可设计上报延迟消息、到期回调处理两个接口,简化延迟队列的接入成本。可以参考Redission等封装实现,使用Sorted Set、消息Pub/Sub、Stream等结合实现完善的延迟队列。

具体实现Demo:

@Service
public class DelayOrderService {
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    public void createOrder(String orderId, int timeoutSeconds) {
        long timeoutTimestamp = System.currentTimeMillis() + (timeoutSeconds * 1000);
        String orderKey = "order:" + orderId;
        redisTemplate.opsForZSet().add("orders_to_close", orderKey, timeoutTimestamp);
        System.out.println("Order " + orderId + " created with " + timeoutSeconds + " seconds timeout.");
    }

    @Scheduled(fixedDelay = 1000)
    public void checkAndCloseExpiredOrders() {
        long currentTime = System.currentTimeMillis();
        System.out.println("Checking for expired orders...Current time:" + currentTime);
        ZSetOperations<String, String> zSetOps = redisTemplate.opsForZSet();
        Set<String> allOrderKeys = zSetOps.range("orders_to_close", 0, -1);
        Set<String> filteredOrderKeys = allOrderKeys.stream()
                .filter(key -> key.startsWith("order:"))
                .collect(Collectors.toSet());
        for (String i : filteredOrderKeys) {
            double score = zSetOps.score("orders_to_close", i);
            System.out.println(i + ":" + score);
            //超时关闭
            if (score <= currentTime) {
                String orderId = i.substring("order:".length());
                System.out.println("Closing expired order: " + orderId);
                // 在这里处理关闭订单的业务逻辑,例如更新数据库状态
                // ...
                // 从Sorted Set中移除订单
                zSetOps.remove("orders_to_close", i);
            } else {
                System.out.println("Order " + i + " is still active.");
            }
        }
    }
}

@EnableAutoConfiguration
@RestController
@RequestMapping("/api/delay-order")
public class DelayOrder {

    @javax.annotation.Resource
    private DelayOrderService delayOrderService;

    @ApiOperation("新增")
    @RequestMapping(value = "/create/{time}", method = RequestMethod.GET)
    public Response<Boolean> create(@PathVariable int time) throws SimpleException {
        delayOrderService.createOrder(System.currentTimeMillis() + "", time);
        return Response.ok();
    }
}

补充:

ZSet代表Redis中的有序集合(Sorted Set)数据结构。它是一个集合,每个成员元素都会关联一个分数(score),Redis会根据这个分数对所有成员进行排序。因此,有序集合同时具备了集合和排序列表的特性:

  • 唯一性:集合中的每个成员都是唯一的,不允许重复。
  • 排序性:集合中的元素按照其分数进行排序,可以是升序或降序。有序集合支持的操作包括但不限于:
  • 添加元素并指定分数。
  • 根据分数范围或者成员排名来获取集合的子集。
  • 计算成员数量。
  • 增减成员的分数。
  • 获取指定成员的分数。
  • 删除指定成员等。

在Java中操作Redis有序集合时,通常通过如ZSetOperations这样的接口来进行。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案

文章目录 一、vue中使用el-table的typeindex有时不显示序号Table 表格显示索引自定义索引报错信息解决方案 二、vue中Missing required prop: “value” 报错报错原因解决方案 三、el-table的索引值index在翻页的时候可以连续显示方法一方法二 四、vue3中Element Plus全局组件配…

微信小程序流量主如何自定义广告组件后台控制广告显示方式附源码[收藏]

最近开发了一个微信小程序&#xff0c;开通了流量主&#xff0c;引用广告显示。本教程干货满满&#xff0c;附上代码&#xff0c;建议**【收藏点赞】** 微信小程序广告有以下几种&#xff1a;Banner广告、激励广告、插屏广告、视频广告、视频贴片广告、封面广告。 为了增加广告…

数字工厂管理系统如何助力企业数据采集与分析

随着科技的不断进步&#xff0c;数字化已成为企业发展的重要趋势。在制造业领域&#xff0c;数字工厂管理系统的应用日益广泛&#xff0c;它不仅提升了生产效率&#xff0c;更在数据采集与分析方面发挥着举足轻重的作用。本文旨在探讨数字工厂管理系统如何助力企业数据采集与分…

Java数组(如果想知道Java中有关数组的知识点,那么只看这一篇就足够了!)

前言&#xff1a;数组对于每一门编程语言来说都是重要的数据结构之一&#xff0c;当然不同语言对数组的实现及处理也不尽相同,Java 语言中提供的数组是用来存储固定大小的同类型元素。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSD…

Kafka从0到消费者开发

安装ZK Index of /zookeeper/zookeeper-3.9.2 下载安装包 一定要下载-bin的&#xff0c;不带bin的是源码&#xff0c;没有编译的&#xff0c;无法执行。-bin的才可以执行。 解压 tar -zxvf apache-zookeeper-3.9.2-bin.tar.gz 备份配置 cp zoo_sample.cfg zoo_sample.cfg-b…

Chronos:学习时间序列的大语言模型(论文解读)

前言 《Chronos: Learning the Language of Time Series》原文地址GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习过程中精读的一些论文&#xff0c;并对其进行了中文翻译。还有部分最佳示例教程。如果有帮助到大家&#xff0c;请帮忙点亮…

RAG技术简介

相关文档&#xff1a; 论文链接&#xff1a; https://arxiv.org/abs/2005.11401 课程链接&#xff1a; Tutorial/huixiangdou at camp2 InternLM/Tutorial GitHub 视频链接&#xff1a; 茴香豆&#xff1a;搭建你的 RAG 智能助理_哔哩哔哩_bilibili RAG是一种在LLM中广泛使…

echarts指标盘属性概括

echarts指标盘属性概括 代码 有模拟数据可以直接使用const options {animation: true,title: {top: "35%",left: "center",// text: "单元测试覆盖度", // 主标题itemGap: 15,textStyle: {// 主标题样式color: "#666666",fontSize:…

Spring MVC分页示例

Spring MVC分页示例 分页用于在不同部分显示大量记录。在这种情况下&#xff0c;我们将在一页中显示10、20或50条记录。对于其余记录&#xff0c;我们提供链接。 我们可以在Spring MVC中简单地创建分页示例。在此分页示例中&#xff0c;我们使用MySQL数据库来获取记录。 创建…

MySQL索引优化(超详细)篇章2--索引调优

目录 1.索引失效状况2.性能分析3.表的索引信息--调整索引顺序4.删除冗余索引5.最佳左前缀法则5.1下面是一个实际的例子来说明这个概念&#xff1a; 6.数据长度和索引长度占用空间比较 1.索引失效状况 MySQL索引失效通常指的是查询语句无法有效地利用索引&#xff0c;而导致全表…

为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的?

一、安全特性 在上篇文章中&#xff0c;我们了解到HTTP在通信过程中&#xff0c;存在以下问题&#xff1a; 通信使用明文&#xff08;不加密&#xff09;&#xff0c;内容可能被窃听不验证通信方的身份&#xff0c;因此有可能遭遇伪装而HTTPS的出现正是解决这些问题&#xff0c…

基于springboot + vue 实现的简易博客系统

项目效果图 登陆页面 文章列表 发表文章 用户管理 栏目管理 数据统计 后端技术栈后端主要采用了&#xff1a; 1.SpringBoot 2.SpringSecurity 3.MyBatis 4.部分接口遵循Restful风格 5.MySQL 前端技术栈前端主要采用了&#xff1a; 1.Vue 2.axios 3.Elemen…

ETL如何执行Java脚本

ETLCloud提供了执行 Java 脚本的方式&#xff0c;让用户能够灵活地处理数据并实现各种复杂的数据处理任务。 ETLCloud在数据处理领域的应用优势主要体现在以下几个方面&#xff1a; 灵活性&#xff1a;通过执行Java脚本&#xff0c;用户能够灵活定制数据处理逻辑&#xff0c;满…

C语言---使用共用体将double型经纬度存储到无符号数组中

1.在上报经纬度时由于数据协议限制需要将double型数据存储到无符号数组中&#xff0c;下边是写了一个简单C程序进行验证&#xff1b; 2.代码示例如下 #include <stdio.h> typedef union {float data;unsigned char arr[4]; } my_data;int main() {my_data test_data {…

IDEA HTTP Client 插件配置空密码的 Request

最近在测试一些 rest api 的时候&#xff0c;发现 IDEA 的 HTTP Client 很好用。对比 postman 更加的轻量&#xff0c;可以满足一些简单的 HTTP 请求测试。这里主要记录下&#xff0c;当用户名没有设置密码时&#xff0c;我们该如何配置这个 HTTP Client 的 Request 文件&#…

综合内容运营实习生实习体验报告怎么写?

分享一个笔灵ai生成的综合内容运营的实习体验报告&#xff0c;有别的岗位需要的可以自己去网站使用 实习体验报告AI写作助手 | AI文章智能生成器 - 笔灵AI写作 实习体验报告 尊敬的领导&#xff1a; 您好&#xff01;我很荣幸有机会向您提交我的实习体验报告。在过去的几个月…

wpf中的图标字体和android的矢量图vector

1.背景 在安卓程序中如下截图所示中&#xff0c;在drawable文件夹下有如下图片文件。 这是一个xml文件&#xff0c;打开后看到Vector节点下paht中有一个pathData属性有一串带字母数字和"."点的数据。这些是什么呢&#xff1f; <vector xmlns:android"http:…

14个项目带你熟练学握AI2G免费分享|一门让你掌握人工智能能力的实操课(送工具)利用Midjourney

目录 1-一门让你掌握人工智能能力的实操课&#xff08;送工具&#xff09;.mp4 10-成为UI设计师&#xff1a;利用Midjourney进行页面UI设计.mp4 11-做室内设计师&#xff1a;利用Midjourney产出家居、室内设计方案.mp4 12-故事绘本&#xff1a;利用Midjouney准备课件、故事…

PCIe协议之-TLP路由基础

✨前言&#xff1a; 在PCI Express (PCIe) 技术中&#xff0c;数据包的路由方式对于确保信息能够高效、准确地传送至目标设备至关重要。PCIe定义了几种路由方式&#xff0c;主要有以下几种。 &#x1f31f;地址路由&#xff08;Address Based Routing&#xff09; 这是最基本…

自动驾驶系统中的数据闭环:挑战与前景

目录 自动驾驶概况 1.1自动驾驶分级 1.2自动驾驶国内发展 ​1.3自动驾驶架构模型 数据闭环的意义 2.1 搜集corner case的数据 2.2 提高模型的泛化能力 2.3 驱动算法迭代 数据闭环落地的痛点及对策 3.1 数据采集和使用的合规性问题 3.2 数据确权问题 3.3 数据采集…