Redis 给集合元素单独设置过期

news2024/11/15 12:22:41

其他系列文章导航

Java基础合集
数据结构与算法合集

设计模式合集

多线程合集

分布式合集

ES合集


文章目录

其他系列文章导航

文章目录

前言

一、场景

1.1 消费队列

1.2 Redis实现

二、常见的方案

2.1 为单独的 field 设置过期

2.2 设置整体过期时间

2.3 zset 结合 score实现

2.4 底层实现

2.4.1 ZipList 实现

2.4.2 SkipList 实现 

2.5 代码实现

三、总结


前言

Redis 是一个开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理。

在 Redis 中,集合(Set)是一种无序的数据类型,用于存储不重复的字符串元素。

虽然 Redis 的集合本身不支持为每个元素单独设置过期时间,但你可以通过一些技巧和策略来实现类似的功能。


一、场景

1.1 消费队列

最近,朋友在使用 Redis 时,脑中闪过一个创新的想法。他想用 Redis 的基础数据结构来构建一个简单版的延迟消费队列。

对于这个业务需求,我们需要设计一个状态图:

同时,我们需要确保队列的长度始终处于控制之中,例如,我们只允许用户拥有最多3个未支付的订单。

1.2 Redis实现

Redis,这款高性能的缓存和数据存储数据库,已经成为了后台开发者的得力助手。

假如我们要用 Redis 作为消费队列,我们可以考虑使用 List、Hash 和 Set 这三种数据结构。在这个业务场景中,由于我们主要关注的是 orderId(订单 ID),所以这三种数据结构都可以满足我们的需求。

例如,使用 hash 数据结构来存储数据时,我们可以设置 key 为 UnpaidOrder-{userId},然后每个 field 对应一个订单。

然而,现在我们面临一个挑战:每个订单的存活时间不同,有手动消费和定期删除两种逻辑。

  • 订单1,如果手动支付,需要从列表中删除 orderId1;
  • 订单2,如果在半小时内未支付,就会自动过期,用户还可以继续提交订单到未支付状态。

这就意味着在 List、Set 或者 Hash 这三种结构中,每个 field 都需要设置单独的过期时间。

这是一个常见而又棘手的问题,本文将从互联网业务中常见的解决方案入手,深入探讨一下 Redis 的底层实现。


二、常见的方案

在开发过程中,我们经常会遇到一种情况:需要计算某些特定字段的数量,而这些字段的生存时间又各不相同。

比如说,我们需要在业务中计算用户的未支付订单数,但是每个订单的过期时间都不相同。

在这种情况下,我们需要手动删除已经过期的字段,或者设置它们自动过期。

2.1 为单独的 field 设置过期

Redis 里面暂时没有接口给 List、Set 或者 Hash 的 field 单独设置过期时间,只能给整个列表、集合或者 Hash 设置过期时间。

这样,当 List/Set/Hash 过期时,里面的所有 field 元素就全部过期了

但这样并不满足需求。

除非你同时把 field 和过期时间都存下来,然后在程序里面判断它是否过期。

2.2 设置整体过期时间

我们首先可以考虑给整个 List/Set/Hash 设置过期时间。

这很难满足每个字段单独设置过期时间的需要。

既然每个订单的过期时间都不同,我们是否可以根据时间来创建不同的集合,将同一时间过期的订单放在同一个集合中:

然后,我们可以分别为不同的集合设置 TTL。当订单过期未支付时,订单会随着集合的过期而在同一分钟内被删除。

然而,这种方法也存在一些问题。每次新增订单时,我们需要遍历过去30分钟的集合,检查是否有该用户的订单,并判断用户的未支付订单数是否超限。

此外,按分钟创建集合可能存在一个问题:用户的订单可能在01秒就过期了,但在59秒才被删除。

如果按秒创建集合,30分钟将需要创建1800个集合,这使得管理变得更加困难。因此,为集合设置整体过期时间并不是一个可行的解决方案。

2.3 zset 结合 score实现

除了常见的 List/Set/Hash 结构,Redis 还拥有一个专门用于排序的数据结构 zset(Sorted Set,排序集合)。

基于 Redis 的 Zset 结构,我们可以利用 Score 来表示过期时间,从而轻松实现每个字段的独立过期。

具体实现方法如下:

  1. 每次新增待支付订单时,我们将当前时间的 Unix timestamp 加上过期时间 30min 作为 score 设置为该元素。这样,sorted set 会根据这个过期时间戳对元素进行排序和存储。
  2. 当订单被支付后,根据 userId 和 orderId 删除 sorted set 中的待支付订单。
  3. 同时,在程序中添加一个定时任务,每隔一秒删除当前时间已过期的订单。

2.4 底层实现

用 Redis 的 zset 一方面可以很方便地实现了对每个字段的单独过期,不再受整个 Key 的过期时间限制,提高了灵活性。

另一方面,Redis 的 zset 操作是十分高效的,不会给系统带来显著的性能压力。

这得益于 zset 底层的数据结构,Zset 底层实现采用了 ZipList(压缩列表)和 SkipList(跳表)两种实现方式,当满足:

  • Zset 中保存的元素个数小于 128(可通过修改 zset-max-ziplist-entries 配置来修改)

  • Zset 中保存的所有元素长度小于 64byte(通过修改 zset-max-ziplist-values 配置来修改)

两个条件时,Zset 采用 ZipList 实现;否则,用 SkipList 实现。

2.4.1 ZipList 实现

ZipList 是一个数组的形式,存储数据时分为列表头部分和数据部分,列表头部分有 3 个元素:

  • zlbytes:表示当前 list 的存储元素的总长度

  • zllen:表示当前 list 存储的元素的个数

  • zltail:表示当前 list 的头结点的地址,通过 zltail 就是可以实现 list 的遍历

数据部分以键值对的方式依次排列,键存储的是实际 member,值存储的是 member 对应的分值(score)。

2.4.2 SkipList 实现 

SkipList 分为两部分:

  1. dict 部分是由字典实现(其实就是 HashMap,里面放了成员到 score 的映射);

  2. zset 部分使用跳跃表实现(存放了所有的成员,解决了 HashMap 中 key 无序的问题)。

从图中可以看出,dict 和 zset 都存储数据。

但实际上 dict 和 zset 最终使用的指针都指向了同一份成员数据,即数据是被两部分共享的,为了方便表达将同一份数据展示在两个地方。

2.5 代码实现

当我们插入一个过期时间到 zset 时,Redis 会自动帮我们排好序,我们只需要在程序中新增一个定时任务,比如:每秒执行一次删除任务,删除时间戳从 0 到当前时间戳的 score 值即可

伪代码如下:

# 1. 创建新的待支付订单时,查询zset个数
count = zcard UnpaidOrder-{userId}

# 2. 判断未支付订单个数
if count >= 3:
    return

# 3. 新增订单
zadd UnpaidOrder-{userId} redis.Z{Score: {timestamp1}, Member: {order1}}

# 4.1 订单支付后,从 set 中删除未支付订单
zrem UnpaidOrder-{userId} order1

# 4.2 过期时间到了,从 set 中删除未支付订单
zremrange UnpaidOrder-{userId} 0 {current_timestamp}

三、总结

通过合理的数据结构选择和巧妙的应用,我们成功地解决了为 List、Set 和 Hash 结构中的字段设置单独过期时间的问题。

这个方案在实际项目中得到了验证,并取得了显著的效果。

对比其它的延时队列,或者 etcd 的 field 过期方案,Redis 的实现相对而言更为便捷,理解起来也更为简单。


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

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

相关文章

java spring mvc 初探 web搭建过程详解

提前准备安装tomcat 设备:mac 第一步:下载 进入官网下载压缩包 注意:如果jdk版本是1.8,则tomcat需要v8才行,否则会报错 https://tomcat.apache.org/ 第二步:解压 解压后路径 /Users/you/Library/tomcat/apache-tomcat-8.5.73 进入此目录 修改配置 code setclasspath.…

小白入门基础 - spring Boot 入门

1.简介 spring Boot是为了简化java的开发流程而构建的,即使是使用springMVC框架,也依然需要大量配置和依赖导入, 这无疑是繁琐的,spring Boot采用了”习惯由于配置“的原则,进行一键化部署,这样极大…

延迟加载:提升性能的隐形利器

引言 想象一下,你正在玩一款大型电子游戏。如果游戏在启动的时候就加载了所有的关卡、角色和道具,那玩家可能需要等待很长时间才能开始游戏,而且大部分内容可能在游戏的初期都不会被用到。显然,这样的做法既低效又耗时。 而延迟加…

LeetCode刷题--- 不同路径

个人主页:元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言:这个专栏主要讲述动…

SESV:通过预测和纠错实现精确的医学图像分割

SESV: Accurate Medical Image Segmentation by Predicting and Correcting Errors SESV:通过预测和纠错实现精确的医学图像分割背景贡献实验方法Thinking SESV:通过预测和纠错实现精确的医学图像分割 286 IEEE TRANSACTIONS ON MEDICAL IMAGING, VOL. …

3元一平方公里的在线卫星影像

我们为大家分享了免费下载卫星影像的方法。 但让人遗憾的是,该影像的最高分辨率只有10米,需要更高清且比较新的卫星影像,看来还是得付费购买才比较靠谱。 自助选择区县范围 商业卫星影像主要面向企事业单位,一般来讲都比较贵&a…

视频转音频软件哪个好? 11 个高效的视频转音频转换器分享

网络上拥有数百个值得观看和聆听的音乐视频。但要聆听喜爱的音乐,用户必须观看整个视频,即使只有音乐让他们兴奋。那么,如何从视频中提取音频呢?简单的答案是使用视频到音频转换器将视频转换为音频格式并将其保存在您的设备上以供…

Docker资源配额

Docker资源配额指的是对Docker容器或服务在系统资源使用方面的限制。 通过资源配额,可以控制和限制Docker容器可以使用的CPU、内存、磁盘空间和网络带宽等资源。 根据应用程序的需求和系统环境来设置适当的资源配额:过于严格的配额可能导致应用程序性能下…

基于ssm的智慧社区电子商务系统+vue论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

嵌入式科普(9)vscode无法跳转和恢复默认配置

一、目的/概述 二、解决办法 2.1 使能Intelli Sense Engine 2.2 vscode恢复默认配置 2.3 c/c与clangd冲突 嵌入式科普(9)vscode无法跳转和恢复默认配置 一、目的/概述 1、2024年的第一天突然vscode无法跳转,莫名其妙 2、尝试了各种设置和插件都无效,卸…

如何将支持标准可观测性协议的中间件快速接入观测

前言 作为一名云原生工程师,如何将支持标准可观测性协议的中间件快速接入观测云呢?答案是只需要三步。 首先,需要确定您要观测的中间件类型。支持标准可观测性协议中间件可通过观测云的 DataKit 采集到中间件的关键指标。有些中间件自带可观…

c++-智能指针

1、概念 堆内存的对象需要手动使用delete销毁,如果忘记使用delete销毁就会造成内存泄漏。 所以C在ISO 98标注中引入了智能指针的概念,并在C11 中趋于完善。 使用智能指针可以让堆内存对象具有栈内存对象的特性。原理时给需要自动回收的堆内存对象套上一层…

【本科生通信原理】【实验报告】【北京航空航天大学】实验一:通信原理初步

一、实验目的: 熟悉 MATLAB开发环境、掌握 MATLAB基本运算操作;熟悉和了解 MATLAB图形绘制基本指令;熟悉使用 MATLAB分析信号频谱的过程;掌握加性白高斯噪声信道模型 二、实验内容: 三、实验程序: 1、 f…

经历和经验的联系

2023年国内互联网大厂疯狂裁员,还出来了“防御性编程”,“开猿节流,降本增笑”等等词汇。阿里裁员后阿里云宕机多次,腾讯裁员和降级了领导层,这些领导回到大头兵岗位,不能融入一线干活,任务完成…

React基础应用及常用代码

目录 基础知识 babel.config.js js,html,css,Vue,react,angular,nodejs,webpack,less,ES6,commonjs等的关系 ECMAScript 6(ES6) let、const、var 的区别 Webpack、npm、node关系 props和state区别 通用框架类 ES 6 export React React.Fragm…

生信技能33 - gnomAD数据库hg19/hg38 VCF文件批量下载脚本

gnomAD数据库下载地址 gnomAD downloads gnomAD v2.1.1数据集包含来自125,748个外显子组和15,708个全基因组的数据,所有这些数据都映射到GRCh 37/hg 19和GRCh 38/hg 38 两个版本的参考序列。 gnomAD数据库hg19与hg39 VCF文件批量下载脚本 download.sh # 获取当前目录路径…

Spring IOC的四种手动注入方法

手动注入 1.Set方法注入-五种类型的注入1.1 业务对象JavaBean第一步:创建dao包下的UserDao类第二步:属性字段提供set⽅法第三步:配置⽂件的bean标签设置property标签第四步:测试 1.2 常用对象String(日期类型&#xff…

【CMake】1. VSCode 开发环境安装与运行

CMake 示例工程代码 https://github.com/LABELNET/cmake-simple 插件 使用 VSCode 开发C项目,安装 CMake 插件 CMakeCMake ToolsCMake Language Support (建议,语法提示) 1. 配置 CMake Language Support , Windows 配置 donet 环境 这…

用通俗易懂的方式讲解:OpenAI 新版 API 使用介绍,帮助大家快速解锁这些新功能

OpenAI 最近举办了首次开发者大会,大会上不仅发布了 GPTs 这样王炸级别的新功能,还发布了一些新模型,比如gpt-4-turbo等,模型的知识截止时间也提高到了 2023 年 4 月。 同时配合这些新模型,OpenAI 还开放了大家期盼已…

Anaconda + Pytorch 超详细安装教程

Anaconda Pytorch 超详细安装教程 安装 Anaconda 略,自行百度即可 安装 Pytorch 虚拟环境 第一步 选择 env第二步 创建第三步 填写环境名称和选择 python 版本号 第四步 打开 https://pytorch.org/ 选择 pytorch 版本,我这里选择的是 GPU 版本 即 CUDA 11.8,也…