Spring Boot中使用Redis和Lua脚本实现延时队列

码到三十五 : 个人主页

延时队列是一种常见的需求。延时队列允许我们延迟处理某些任务,这在处理需要等待一段时间后才能执行的操作时特别有用,如发送提醒、定时任务等。文中,将介绍如何在Spring Boot环境下使用Redis和Lua脚本来实现一个延时队列。

目录

    • 一、延迟队列的四大使用场景
    • 二、如何利用ZSet实现延迟队列
    • 三、实现步骤
    • 四、实现代码
    • 五、使用ZSet实现延迟队列的缺陷
    • 六、替代实现方案
      • 结语

一、延迟队列的四大使用场景

  1. 订单超时自动处理
    在电商领域,延迟队列对于处理订单超时问题至关重要。一旦用户下单,订单信息便进入延迟队列,并预设超时时长。若用户在此时间内未完成支付,订单信息将由消费者从队列中提取,并执行如取消订单、库存释放等后续操作,高效且自动化。

  2. 优惠券到期温馨提醒
    借助延迟队列,我们可以实现优惠券到期前的温馨提醒服务。将临近过期的优惠券信息入队,并设定精确延迟时间。时间一到,系统自动提醒用户优惠券的到期日,引导他们及时享用优惠,提升用户体验。

  3. 智能消息重试策略
    在处理网络请求失败、数据库异常等情况时,延迟队列提供了智能的消息重试机制。当消息初次处理失败,它会被置入队列并设定重试延时。延时结束后,系统会再次尝试处理,确保消息的可靠传递与处理。

  4. 异步通知与定时提醒
    延迟队列还能用于实现异步通知和定时提醒功能。用户完成操作后,系统将相关通知信息加入队列,并设定发送延时,确保在最佳时机向用户推送通知,既不打扰用户,又能保持信息的时效性。

二、如何利用ZSet实现延迟队列

Redis的ZSet(有序集合)是一个根据分数对唯一字符串成员进行排序的数据结构。在多个成员分数相同时,它们会按照字典顺序进行排列。ZSet不仅常用于排行榜和限速器等场景,还可巧妙用于实现延迟队列。

在这里插入图片描述

基于ZSet的延迟队列实现原理,主要利用了其有序性和按分数排序的特点。以下是具体实现步骤的简要介绍:

  1. 定义延迟消息:在ZSet中,我们将延迟消息作为成员,而其对应的延迟时间则作为该成员的分数。这里的延迟时间通常是一个未来的时间戳,它指明了消息应当被处理的确切时刻。

  2. 消息入队:使用ZADD命令,我们可以轻松地将消息添加到ZSet中,并为其指定相应的延迟时间作为分数。

  3. 定期检查:通过定期轮询ZSet,我们可以利用ZRANGEBYSCORE命令来检索那些分数(即延迟时间)小于或等于当前时间戳的消息,这些消息即为到期的、需要被处理的消息。

  4. 消息处理与出队:一旦找到到期的消息,我们可以使用ZPOPMIN命令将它们从ZSet中移除,并进行相应的处理。在处理过程中,需要考虑并发性和数据一致性问题,确保每条消息都能被正确处理且不会被重复处理。

  5. 后续操作与通知:为了提高系统的性能和可靠性,我们可以结合Redis的Pub/Sub机制。在处理完消息后,发布一个事件来通知其他服务或订阅者进行后续的操作或处理。

通过这种方式,ZSet能够有效地按照消息的延迟时间顺序,逐个取出并处理到期的消息,从而实现了一个高效且可靠的延迟队列系统。

三、实现步骤

在这里插入图片描述

在Spring Boot环境下,实现一个基于Redis和Lua脚本的延时队列,需要以下几个步骤:

  1. 环境准备

    • 安装并启动Redis服务器。
    • 在Spring Boot项目中添加spring-boot-starter-data-redis依赖。
  2. Redis数据结构选择

    • 使用Redis的zset(有序集合)数据结构来存储延时任务。zset中的元素是唯一的,但分数(score)可以相同,可以用作任务的延迟时间戳。
  3. Lua脚本编写

    • 编写一个Lua脚本来处理队列的出队和入队操作,以确保操作的原子性。
  4. Spring Boot应用配置

    • 配置Redis连接工厂和Redis模板。
  5. 实现延时队列服务

    • 提供一个服务来管理延时队列,包括入队、出队、检查并处理到期的任务等。
  6. 定时任务调度

    • 使用Spring的@Scheduled注解或者Redis的键空间通知来定期检查并处理到期的任务。

四、实现代码

下面是一个简化版本的实现:

1. 添加Maven依赖

pom.xml中添加spring-boot-starter-data-redis依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 配置Redis

application.ymlapplication.properties中配置Redis连接信息:

spring:redis:host: localhostport: 6379

3. Lua脚本
定义一个Lua脚本原子性地执行出队操作。脚本使用Redis的有序集合命令来查找并移除到期的任务:

-- KEYS[1] 延时队列的key
-- ARGV[1] 当前时间戳
-- 返回值:任务ID(如果存在)或nil
local key = KEYS[1]
local currentTime = tonumber(ARGV[1])
local task = redis.call('zrangebyscore', key, 0, currentTime, 'LIMIT', 0, 1)
if #task > 0 thenredis.call('zremrangebyscore', key, 0, currentTime)return task[1]
elsereturn nil
end

可以稍微优化一下上面的Lua脚本,以减少不必要的操作和提高效率:

-- KEYS[1] 延时队列的key
-- ARGV[1] 当前时间戳
-- 返回值:任务ID(如果存在)或nillocal key = KEYS[1]
local currentTime = tonumber(ARGV[1])-- 使用zrangebyscore和zrem的组合命令zpopmin,它原子性地返回并移除分数最低的元素
-- zpopmin命令(5.0及以上版本)
local task = redis.call('zpopmin', key, 1, 'BLOCK', 0, 'SCORES')-- zpopmin返回的是一个包含两个元素的数组,第一个元素是分数,第二个是成员
if task and #task > 0 and task[2] and tonumber(task[1]) <= currentTime thenreturn task[2] -- 返回任务ID
elsereturn nil
end

注意:

  1. zpopmin命令是一个原子性的操作,它返回并删除分数最低的元素。避免了先查询后删除可能带来的并发问题。zpopmin`命令在Redis 5.0及以上版本中可用。

  2. zpopmin命令可以设置阻塞时间,这里设置为0,表示不阻塞。如果希望在没有可用元素时阻塞等待一段时间,可以调整这个值。

  3. 脚本检查了返回的分数是否小于等于当前时间戳,以确保只处理到期的任务。

  4. 如果Redis版本低于5.0zpopmin将不可用,可以使用zrangebyscorezrem的组合,但需要注意并发问题。

4. 实现延时队列服务

@Service
public class DelayQueueService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final String DELAY_QUEUE_KEY = "delay_queue";// 入队操作public void enqueue(String taskId, long delayInSeconds) {long score = System.currentTimeMillis() / 1000 + delayInSeconds;stringRedisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, taskId, score);}// 出队操作,使用Lua脚本确保原子性public String dequeue() {String luaScript = "..."; // 上面定义的Lua脚本内容RedisScript<String> script = RedisScript.of(luaScript, String.class);long currentTime = System.currentTimeMillis() / 1000;return stringRedisTemplate.execute(script, Collections.singletonList(DELAY_QUEUE_KEY), String.valueOf(currentTime));}
}

5. 定时任务调度

@Component
public class DelayQueueScheduler {@Autowiredprivate DelayQueueService delayQueueService;private static final long POLLING_INTERVAL = 1000; // 检查间隔1秒@Scheduled(fixedRate = POLLING_INTERVAL)public void pollAndProcess() {String taskId = delayQueueService.dequeue();if (taskId != null) {// 处理任务逻辑,例如调用某个服务或者方法等。System.out.println("Processing task: " + taskId);}}
}

五、使用ZSet实现延迟队列的缺陷

虽然Redis的ZSet能满足一些简单场景的延迟队列需求,但也存在一些明显的缺陷。

  1. 资源空转问题
    延迟任务的时间分布往往是不均匀的。在某些时段,可能会有大量的任务需要处理,而在其他时段则可能几乎没有任务。这种情况下,如果系统持续检查ZSet以寻找到期任务,那么在任务稀少或无任务的时段,系统会处于空转状态,这无疑是对计算资源的浪费。

  2. 性能瓶颈
    当延迟消息数量众多时,不断地轮询整个ZSet以查找到期消息会对性能产生显著影响。特别是当任务数量庞大且到期时间分散时,范围查询的开销会变得尤为突出。此外,如果多个任务同时到期且回调函数执行效率低下,还可能导致延迟处理中心的性能下降,进而引发连锁反应,影响到后续任务的及时处理。

  3. 时间精度问题
    ZSet使用浮点数作为分数来排序元素,这在某些需要高精度时间控制的场景中可能不够用。同时,Redis实例的故障、重启或时钟回拨等问题都可能影响到延迟事件处理的准确性。

六、替代实现方案

  1. 状态即时校验
    在某些业务流程中,可以通过即时校验当前状态与应有状态的方式来替代延迟队列。但这种方法更适用于工单等可以持续校验的业务场景,对于一次性的延迟通知任务则不太适用。

  2. 利用消息中间件的延迟消息功能
    像RocketMQ和RabbitMQ这样的消息中间件提供了延迟消息的功能。例如,RocketMQ在商业版本中支持自定义时长的延迟消息。

  3. 数据库轮询
    通过定期轮询数据库中的业务单据表或专门的延迟事件表来处理过期任务。但这种方法可能会对业务数据库和服务造成性能负担,且轮询的时间间隔难以精确把控。

  4. 时间轮算法
    时间轮算法是一种有效的处理定时任务的方法。但为了实现持久化和避免任务丢失,需要结合Redis或关系数据库来存储延迟任务。在服务启动时,需要将存储的延迟任务加载到时间轮中,并在任务过期后更新任务状态,以防止重复执行或加载。

结语

通过使用Redis和Lua脚本,可以在Spring Boot环境中实现一个高效且可靠的延时队列系统。这种方法利用了Redis的有序集合数据结构和Lua脚本的原子性操作来确保任务的正确性和一致性。通过定期调度任务来处理到期的任务,可以实现各种需要延迟执行的操作,如发送提醒、执行定时任务等。



听说...关注下面公众号的人都变牛了,纯技术,纯干货 !

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++STL详解(五)】--------list的介绍与使用

目录 前言 一、list的介绍 二、list的使用 Ⅰ.默认成员函数 1、构造函数 2、赋值重载 3、析构函数 Ⅱ、容量 1.size() Ⅲ、迭代器与遍历 1.beginend (正向迭代器) 2.rbeginrend (反向迭代器) 3.front 4.back Ⅳ、增删查改 1.push_front 2.pop_front 3.push_b…

vue3 + ts 快速入门(全)

文章目录 学习链接1. Vue3简介1.1. 性能的提升1.2.源码的升级1.3. 拥抱TypeScript1.4. 新的特性 2. 创建Vue3工程2.1. 基于 vue-cli 创建2.2. 基于 vite 创建&#xff08;推荐&#xff09;vite介绍创建步骤项目结构安装插件项目结构总结 2.3. 一个简单的效果Person.vueApp.vue …

CUDA共享内存

GPU中有两种类型的内存&#xff1a; 板载内存片上内存 全局内存是较大的板载内存&#xff0c;延迟相对较高&#xff1b;共享内存是较小的片上内存&#xff0c;具有相对较低的延迟。共享内存的常用用途&#xff1a; 块内线程的通信通道全局内存数据的可编程管理缓存高速暂存存…

设备能源数据采集新篇章

在当今这个信息化、智能化的时代&#xff0c;设备能源数据的采集已经成为企业高效运营、绿色发展的重要基石。而今天&#xff0c;我们要向大家介绍的就是一款颠覆传统、引领未来的设备能源数据采集神器——HiWoo Box网关&#xff01; 一、HiWoo Box网关&#xff1a;一站式解决…

confluence 设置https代理

使用nginx反待confluence并开启https后&#xff0c;登录confluence会一直提示&#xff1a;scheme、proxyName、proxyPort设置错误。 解决办法&#xff1a; find / -name server.xmlvi /opt/atlassian/confluence/conf/server.xml HTTP反代配置 HTTPS反代配置

jvm垃圾回收机制介绍

JVM&#xff08;Java虚拟机&#xff09;是Java程序的运行环境&#xff0c;它负责执行字节码文件。JVM的工作原理主要包括以下几个部分&#xff1a;类加载器、执行引擎、垃圾收集器和内存管理。类加载器负责加载字节码文件并将其转换成Java平台上的机器码&#xff0c;执行引擎负…

SQL数据库

一.什么是数据库 数据库&#xff1a;存储数据的仓库&#xff0c;数据是有组织的进行存储。&#xff08;database 简称DB&#xff09; 数据库管理系统&#xff1a;管理数据库的大型软禁&#xff08;DataBase Management System 简称DBMS&#xff09; SQL&#xff1a;操作关系…

微信开发api、微信视频号开发

接口地址&#xff1a; http://api.videostui.com/finder/v2/api/login/checkLogin 接口说明 获取到登录二维码后需每间隔5s调用本接口来判断是否登录成功新设备登录平台&#xff0c;次日凌晨会掉线一次&#xff0c;重新登录时需调用获取二维码且传appId取码&#xff0c;登录成…

莫比乌斯变换的数学原理

关键词&#xff1a;Mobius Transformations 一、说明 关于莫比乌斯变换&#xff0c;是一个代数几何变换的重要概念。也是双曲几何的重要理论&#xff0c;比如庞加莱盘就是建立在这个理论上&#xff0c;那么这个变换到底有哪些内容&#xff1f;本文将做出详细的解读。 二、线…

使用量排名前50的GPTs趋势和特征

Chatgpt的gpt商店已经有几千gpts了。目前哪些gpts比较受欢迎呢&#xff1f;有哪些趋势和投资呢? 根据whatplugin.ai&#xff08;截止日期为2024年3月&#xff09;&#xff0c;使用量最多的50个gpts数据分析结果如下&#xff1a; GPTs类型的分布情况如下&#xff1a; 图像生成…

FreeRTOS任务通知

FreeRTOS任务通知 FreeRTOS 新增了任务通知(Task Notifictions)这个功能&#xff0c;可以使用任务通知来代替信号量、消息队列、事件标志组等这些东西。使用任务通知的话效率会更高&#xff0c;任务通知在 FreeRTOS 中是一个可选的功能&#xff0c; 使用队列、信号量、事件标志…

Facebook的语言学:社交媒体如何影响我们的沟通方式

1. 引言 社交媒体已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中最具影响力的平台之一&#xff0c;不仅改变了人们之间的社交方式&#xff0c;也对我们的语言学产生了深远的影响。本文将深入探讨Facebook的语言学特点&#xff0c;以及它如何塑造和改变…

C++面经(简洁版)

1. 谈谈C和C的认识 C在C的基础上添加类&#xff0c;C是一种结构化语言&#xff0c;它的重点在于数据结构和算法。C语言的设计首要考虑的是如何通过一个过程&#xff0c;对输入进行运算处理得到输出&#xff0c;而对C&#xff0c;首先要考虑的是如何构造一个对象&#xff0c;通…

2-手工sql注入(进阶篇) sqlilabs靶场1-4题

1. 阅读&#xff0c;学习本章前&#xff0c;可以先去看看基础篇&#xff1a;1-手工sql注入(基础篇)-CSDN博客 2. 本章通过对sqlilabs靶场的实战&#xff0c;关于sqlilabs靶场的搭建&#xff1a;Linux搭建靶场-CSDN博客 3. 本章会使用到sqlmap&#xff0c;关于sqlmap的命令&…

stm32f103zet6_串口实现-DHT11-tim1(定时)

1思路 1打开时钟 1.1使用定时器实现us级的计时 1.2在打开串口 1,3在DHT11驱动中修改引脚 stm32cudeMX 配置 1打开时钟 2打开串口 3打开tim1(定时器) 4生成代码 代码设置 1导入DHT11库(tim.h是定时器的文件系统自动生成的) DHT11.c #include "dht11.h" #inc…

PHP算命源码_最新测算塔罗源码_可以运营

功能介绍 八字精批、事业财运、姓名分析、宝宝起名、公司测名、姓名配对、综合详批、姻缘测算、牛年感情、PC版测算、八字合婚、紫微斗数、鼠年运程、月老姻缘、许愿祈福、号码解析、塔罗运势、脱单占卜、感情继续、脱单占卜、塔罗爱情、心理有你、能否复合、暗恋对象、是否分…

5.3 进程间通信管道和共享内存

每次打开一个网页都是一个进程 进行管道之间通信的方式&#xff1a;以前学到的有可以在磁盘上开辟空间进行交互&#xff0c;也可以在内存中开辟缓冲区进行交互。 一定注意可读性 管道就是属于在内存中的一片缓冲区&#xff0c;管道可以在命令行中创建管道mkfifo也可以在vim中…

java技术栈快速复习05_基础运维(linux,git)

Linux知识总览 linux可以简单的理解成和window一样的操作系统。 Linux和Windows区别 Linux是严格区分大小写的&#xff1b;Linux中一切皆是文件&#xff1b;Linux中文件是没有后缀的&#xff0c;但是他有一些约定俗成的后缀&#xff1b;Windows下的软件一般是无法直接运行的Li…

京东JD商品SKU信息API返回值解析:精准掌握商品属性

在电子商务迅猛发展的今天&#xff0c;商家对于商品信息的掌握和管理显得尤为重要。作为电商平台的佼佼者&#xff0c;京东&#xff08;JD&#xff09;提供了丰富的API接口&#xff0c;使得商家能够轻松地获取商品的详细信息&#xff0c;包括SKU&#xff08;Stock Keeping Unit…

2024五一数学建模A题思路代码与论文分析

2024五一数学建模A题完整代码和成品论文获取↓↓↓↓↓ https://www.yuque.com/u42168770/qv6z0d/gyoz9ou5upvkv6nx?singleDoc# 2024五一数学建模A题钢板最优切割路径问题需要建立的模型和算法: 图论 最短路径算法(Dijkstra算法、Floyd算法等) 动态规划 网格化离散建模 …