Redis实现延迟任务的几种方案
🏷️个人主页:牵着猫散步的鼠鼠
🏷️系列专栏:Java全栈-专栏
🏷️个人学习笔记,若有缺误,欢迎评论区指正
目录
1.前言
2.Redis如何实现延迟任务?
3.代码实现
3.1. 过期键通知事件实现
3.2. 使用ZSet实现延迟任务
3.3 使用Redisson的延时队列(常用)
4.Redis实现延迟任务优缺点分析
5. 总结
1.前言
延迟任务(Delayed Task)是指在未来的某个时间点,执行相应的任务。也就是说,延迟任务是一种计划任务,它被安排在特定的时间后执行,而不是立即执行。
延迟任务的常见使用场景有以下几个:
- 定时发送通知或消息:
- 发送定时短信、邮件或应用内消息,如注册确认、订单状态更新、促销活动通知等。
- 定时推送新闻、天气预报、股票价格等实时信息。
- 异步处理和后台任务:
- 将耗时的操作安排为延迟任务,避免阻塞主线程或用户界面,提高系统的响应性能。
- 执行批量数据处理,如日志分析、数据报表生成等。
- 缓存管理和过期处理:
- 定时清理过期的缓存数据,释放存储空间。
- 更新缓存中的数据,保持数据的时效性和准确性。
- 计划任务和定时调度:
- 在特定时间执行系统维护任务,如数据库备份、系统更新等。
- 定时启动或关闭服务,以节约资源或满足业务需求。
- 订单和支付处理:
- 在用户下单后的一段时间内,如果用户未支付,则自动取消订单。
- 定时检查订单的支付状态,并更新相应的订单信息。
- 重试和失败恢复机制:
- 当某个操作失败时,可以在延迟一段时间后自动重试,以提高成功率。
- 实现分布式锁的超时释放,避免死锁情况。
- 提醒和日程管理:
- 设置日程提醒,如会议、生日、纪念日等。
- 定时提醒用户完成任务或进行某项活动。
- 定时数据采集和上报:
- 定期从传感器、设备或外部系统中采集数据。
- 定时上报应用的使用情况、统计数据或用户行为分析。
2.Redis如何实现延迟任务?
Redis 本身并没有直接提供延迟任务的功能,但可以通过一些策略和手段,在 Redis 中手动实现延迟任务。
使用 Redis 实现延迟任务的主要手段有以下几个:
1. 使用过期键的事件通知执行延时任务:开启过期键通知,当 Redis 中键值过期时触发时间,在事件中实现延迟代码,但因为 Redis 的 Key 过期时不会被及时删除,所以这个过期事件也不保证可以立即触发,所以此方式很少用来实现延迟任务(因为极其不稳定)。
2. 使用 ZSet 执行延时任务:在 ZSet 中插入延迟执行时间和任务 ID,如下命令所示:
ZADD delay_tasks <timestamp> <task_id>
然后,启动一个后台线程或者定时任务,定期通过 ZRANGEBYSCORE 命令从有序集合中获取已到达执行时间的任务,即分数小于或等于当前时间的任务,进行执行即可实现延时任务。
3. 使用 Redisson 执行延迟任务:在 Redisson 框架中,提供了一个 RDelayedQueue 用于实现延迟队列,使用简单方便,推荐使用。
3.代码实现
3.1. 过期键通知事件实现
Redis 提供了键空间通知功能,当某个键发生变化(过期)时,可以发送通知。你可以结合 EXPIRE 过期命令和键空间通知来实现延迟任务。
当为某个键设置过期时间时,一旦该键过期,Redis 会发送一个通知。你可以订阅这个通知,并在接收到通知时执行任务。但这种方法可能不够精确,且依赖于 Redis 的内部机制。
它的实现步骤是:
- 设置开启 Redis 过期键通知事件,可以通过执行“CONFIG SET notify-keyspace-events KEA”命令来动态开启键空间通知功能,而无需重启 Redis 服务器。
- 设置过期键,可以通过命令“SET mykey "myvalue" EX 3”设置某个 key 3 秒后过期(3s 后执行)。
- 编写一个监听程序来订阅 Redis 的键空间通知。这可以通过使用 Redis 的发布/订阅功能来实现,具体实现代码如下(以 Jedis 框架使用为例):
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub; public class RedisKeyspaceNotifier { public static void main(String[] args) { // 创建Jedis实例 Jedis jedis = new Jedis("localhost", 6379); // 配置键空间通知(通常这一步在Redis配置文件中完成,但也可以在运行时配置) jedis.configSet("notify-keyspace-events", "KEA"); // 订阅键空间通知 jedis.subscribe(new KeyspaceNotificationSubscriber(), "__keyevent@0__:expired"); } static class KeyspaceNotificationSubscriber extends JedisPubSub { @Override public void onMessage(String channel, String message) { System.out.println("Received message from channel: " + channel + ", message: " + message); // 在这里处理接收到的键空间通知 // 例如,如果message是一个需要处理的任务ID,你可以在这里触发相应的任务处理逻辑 } @Override public void onSubscribe(String channel, int subscribedChannels) { System.out.println("Subscribed to channel: " + channel); } @Override public void onUnsubscribe(String channel, int subscribedChannels) { System.out.println("Unsubscribed from channel: " + channel); } }
}
但因为 Redis 的 Key 过期时不会被及时删除,Redis 采用的是惰性删除和定期删除,所以这个过期事件也不保证可以立即触发,所以此方式很少用来实现延迟任务(因为极其不稳定)。
3.2. 使用ZSet实现延迟任务
可以将任务及其执行时间作为成员和分数存储在 ZSET 中,然后,使用一个后台任务(如定时任务或守护进程)定期检查 ZSET,查找分数(即执行时间)小于或等于当前时间的成员,并执行相应的任务。执行后,从 ZSET 中删除该成员,具体实现代码如下:
import redis.clients.jedis.Jedis; import java.util.Set; public class RedisDelayedTaskDemo { private static final String ZSET_KEY = "delayed_tasks"; private static final String REDIS_HOST = "localhost"; private static final int REDIS_PORT = 6379; public static void main(String[] args) { Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT); // 添加延迟任务 addDelayedTask(jedis, "task1", System.currentTimeMillis() / 1000 + 5); // 5秒后执行 addDelayedTask(jedis, "task2", System.currentTimeMillis() / 1000 + 10); // 10秒后执行 // 模拟定时任务检查器 new Thread(() -> { while (true) { try { // 检查并执行到期的任务 checkAndExecuteTasks(jedis); Thread.sleep(1000); // 每秒检查一次 } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } private static void addDelayedTask(Jedis jedis, String task, long executeTime) { jedis.zadd(ZSET_KEY, executeTime, task); System.out.println("Added task: " + task + " with execution time: " + executeTime); } private static void checkAndExecuteTasks(Jedis jedis) { long currentTime = System.currentTimeMillis() / 1000; Set<String> tasks = jedis.zrangeByScore(ZSET_KEY, 0, currentTime); for (String task : tasks) { jedis.zrem(ZSET_KEY, task); // 从有序集合中移除任务 executeTask(task); // 执行任务 } } private static void executeTask(String task) { System.out.println("Executing task: " + task); // 在这里添加实际的任务执行逻辑 }
}
在这个示例中,我们首先使用 addDelayedTask 方法向 Redis 的有序集合中添加任务,并设置它们的执行时间。然后,我们启动一个线程来模拟定时任务检查器,它会每秒检查一次是否有任务到期,并执行到期的任务。
3.3 使用Redisson的延时队列(常用)
在 Redisson 框架中,提供了一个 RDelayedQueue 用于实现延迟队列,使用简单方便,推荐使用,它的具体实现如下:
import org.redisson.Redisson;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;import java.util.concurrent.TimeUnit;public class RDelayedQueueDemo {public static void main(String[] args) throws InterruptedException {// 创建 Redisson 客户端Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);// 获取延迟队列RDelayedQueue<String> delayedQueue = redisson.getDelayedQueue("delayedQueue");// 添加延迟任务delayedQueue.offer("task1", 5, TimeUnit.SECONDS);// 监听并处理延迟任务Thread listenerThread = new Thread(() -> {while (true) {try {// 通过 take 方法等待并获取到期的任务String task = delayedQueue.take();System.out.println("Handle task: " + task);} catch (InterruptedException e) {break;}}});listenerThread.start();}
}
在上述示例中,我们首先创建了一个 Redisson 客户端,通过配置文件指定了使用单节点 Redis 服务器。然后,我们获取一个延迟队列 RDelayedQueue,并添加一个延迟任务,延迟时间为 5 秒,接着,我们通过线程监听并处理延迟队列中的任务。
4.Redis实现延迟任务优缺点分析
优点:
- 轻量级与高性能:Redis 是一个内存中的数据结构存储系统,因此读写速度非常快。将任务信息存储在 Redis 中可以迅速地进行存取操作。
- 简单易用:Redis 的 API 简洁明了,易于集成到现有的应用系统中。
缺点:
- 精度有限:Redis 的延迟任务依赖于系统的定时检查机制,而不是精确的定时器。这意味着任务的执行可能会有一定的延迟,特别是在系统负载较高或检查间隔较长的情况下。
- 功能有限:与专业的任务调度系统相比,Redis 提供的延迟任务功能可能相对简单。对于复杂的任务调度需求,如任务依赖、任务优先级等,可能需要额外的逻辑来实现。
- 稳定性较差:使用 Redis 实现延迟任务没有重试机制和 ACK 确认机制,所以稳定性比较差。
- 单点故障风险:如果没有正确配置 Redis 集群或主从复制,那么单个 Redis 实例的故障可能导致整个延迟任务系统的瘫痪。
5. 总结
以上我们总结了使用redis实现延时任务的几种方案,在一些简单的场景可以直接使用redisson提供的延时队列来实现延时任务,非常容易上手。在复杂大型的场景下,还是推荐使用专业的任务调度系统,如xxl-job,Quartz等。
相关文章:
Redis实现延迟任务的几种方案
🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Java全栈-专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 目录 1.前言 2.Redis如何实现延迟任务? 3.代码实现 3.1. 过期键通知事…...
一种springboot请求参数校验的实现方案
一、前提引入 很多时候,springboot提供的Restful-api需要根据业务需要进行参数校验,相应的,基于各位码友的习惯,各有各的实现方式,可谓是八仙过海各显神通。 二、常见方案 2.1 一种最原始的方法 通过if语句,对特定参数进行校验 if(null == name){return "name …...
盒子模型+响应式布局 + 原型链与继承
盒子模型 是什么 css布局基础,规定了元素在页面上如何呈现,以及元素之间的空间关系 由content paddingbordermargin四部分组成 为什么 盒子模型分为 标准盒子模型: 元素的宽度与高度 只包括content IE盒子模型: 元素的宽度与高度 包括content,padding,border 在实际操作中…...
面试准备 集合 List
ArrayList 底层实现 使用Object[] 动态数组进行存储 特性 支持存储null值非线程安全支持快速访问 初始化方法 无参–返回一个空的列表(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)指定初始容量: new ArrayList(20);指定集合 new ArrayList(col…...
Java快速入门系列-7(测试与调试)
第七章:测试与调试 第7章:测试与调试7.1 单元测试(JUnit)7.1.1 为什么要进行单元测试7.1.2 JUnit基础7.1.3 断言7.1.4 测试套件7.2 集成测试与系统测试7.2.1 集成测试7.2.2 系统测试7.3 调试技巧与工具7.3.1 断点7.3.2 单步执行7.3.3 变量检查7.3.4 条件断点7.3.5 日志记录…...
算法:双指针
算法:双指针 双指针快慢指针对撞指针总结 双指针 LeetCode 283.移动零 以上题目要求我们把所有0移动到数组的末尾,也就是说,我们要把数组转化为以下状态: [ 非0区域 ] [ 0区域 ] 像这种把一个数组划分为多个区域的题型࿰…...
MySQL一些特殊功能的索引(6/16)
特殊功能性索引 B-Tree索引: InnoDB的默认索引类型,适用于多种查询操作。 可以用于等值查询、范围查询和索引列的组合查询。 创建B-Tree索引的示例: CREATE INDEX index_name ON table_name (column1, column2);全文索引(FULLTEX…...
ES11-12
1-ES11-Promise.allSettled Promise.allSettled0)方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。 简单来说不管成功失败都会调用.then(),然后处理成功和失败的结果 const promises [ …...
【学习笔记】Vue3源码解析:第三部分-获取computed的值;实现computed
课程地址:【已完结】全网最详细Vue3源码解析!(一行行带你手写Vue3源码) 第三部分-获取computed的值;实现computed:(对应课程的第18-21节) 第22节:《获取computed的值》 …...
顺序表(C语言版)
前言:本篇文章我们来详细的讲解一下顺序的有关知识,这篇文章中博主会以C语言的方式实现顺序表。以及用顺序表去实现通讯录的代码操作。 目录 一.顺序表的概念 二.顺序表的分类 1.静态顺序表 三.动态顺序表的实现 1.顺序表的初始化 2.顺序表的尾插…...
Python高质量函数编写指南
The Ultimate Guide to Writing Functions 1.视频 https://www.youtube.com/watch?vyatgY4NpZXE 2.代码 https://github.com/ArjanCodes/2022-funcguide Python高质量函数编写指南 1. 一次做好一件事 from dataclasses import dataclass from datetime import datetimedatacl…...
探索Spring、Spring Boot和Spring Cloud的奇妙关系(二)
本系列文章简介: 在当今快节奏、高竞争的软件开发世界中,构建可靠、高效的应用程序是至关重要的。而Spring框架一直以来都是业界领先的Java开发框架之一,帮助开发者简化了复杂的任务,并提供了丰富的功能和强大的支持。 然而&#…...
Mysql的事务隔离级别以及事务的四大特性。
MySQL 的事务隔离级别是数据库管理系统中的一个重要概念,它决定了事务如何隔离和影响其他并发事务。MySQL 支持四种事务隔离级别,分别是:读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)…...
人工智能_大模型023_AssistantsAPI_01_OpenAI助手的创建_API的调用_生命周期管理_对话服务创建---人工智能工作笔记0159
先来说一下一些问题: 尽量不要微调,很麻烦,而且效果需要自己不断的去测试. 如果文档中有图表,大量的图片去分析就不合适了. 是否用RAG搜索,这个可以这样来弄,首先去es库去搜能直接找到答案可以就不用去RAG检索了,也可以设置一个分,如果低于60分,那么就可以去进行RAG检索 微…...
锁策略总结
锁策略 悲观锁和乐观锁 乐观锁和悲观锁不是具体类型的锁而是指两种不同的对待加锁的态度,这两个锁面对锁冲突的态度是相反的。 乐观锁:认为不存在很多的并发操作,因此不需要加锁。悲观锁:认为存在很多并发操作,因此需…...
蓝桥杯备考day2
1.1 map及其函数 map 提供一对一的数据处理能力,由于这个特性,它完成有可 能在我们处理一对一数据的时候,在编程上提供快速通道。map 中的第一 个值称为关键字(key),每个关键字只能在 map 中出现一次,第二个称为该 关…...
Mac电脑安装蚁剑
1: github 下载源码和加载器:https://github.com/AntSwordProjectAntSwordProject GitHubAntSwordProject has 12 repositories available. Follow their code on GitHub.https://github.com/AntSwordProject 以该图为主页面:antSword为源码…...
品牌百度百科词条创建多少钱?
百度百科作为国内最具权威和影响力的知识型平台,吸引了无数品牌和企业争相入驻。一个品牌的百度百科词条,不仅是对品牌形象的一种提升,更是增加品牌曝光度、提高品牌知名度的重要途径。品牌百度百科词条创建多少钱,这成为了许多企…...
Linux安装及管理程序
目录 一.Linux应用程序基础 1.应用程序与系统命令的关系 2.典型应用程序的目录结构 3.常见的Linux软件包封装类型 二.RPM 软件包管理工具 1.RPM 软件包管理器 Red-Hat Package Manger 2.RPM软件包 3.RPM命令 三.源代码编译安装 1. yum 软件包管理器: 配…...
Mybatis generate xml 没有被覆盖
添加插件即可 <plugin type"org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>...
MercadoLibre(美客多)入仓预约系统操作流程-自动化约号(开篇)
目录 一、添加货件信息 二、输入货件信息 三、选择发货 四、填写交货日期 五、注意事项 MercadoLibre(美客多)于2021年10月18号上线了新预约入仓系统,在MercadoLibre美客多平台上,新入仓预约系统是一项非常重要的功能&#x…...
Unity TextMeshProUGUI 获取文本尺寸·大小
一般使用ContentSizeFitter组件自动变更大小 API 渲染前 Vector2 GetPreferredValues(string text)Vector2 GetPreferredValues(string text, float width, float height)Vector2 GetPreferredValues(float width, float height) 渲染后 Vector2 GetRenderedValues()Vector…...
Sonar下启动发生错误,elasticsearch启动错误
Download | SonarQube | Sonar (sonarsource.com) 1.首先我的sonar版本为 10.4.1 ,java版本为17 2.sonar启动需要数据库,我先安装了mysql, 但是目前sonar从7.9开始不支持mysql,且java版本要最少11,推荐使用java17 3.安装postsql,创建sonar数据库 4.启…...
Git常用命令以及异常信息汇总
常用命令: 查看本地分支: git branch 创建一个新仓库 git clone 仓库地址xxxxx cd 目标目录 git switch -c main touch README.md git add README.md git commit -m "add README" git push -u origin main 推送现有文件夹 cd 目标目录 git in…...
解释Python中的RESTful API设计和实现
解释Python中的RESTful API设计和实现 RESTful API,即符合REST(Representational State Transfer,表述性状态转移)架构风格的Web服务接口,已成为现代Web应用程序通信的标准。Python作为一种灵活且强大的编程语言&…...
一、Nginx部署
Nginx部署 一、Docker部署1.复制Nginx配置文件2.启动Nginx容器 一、Docker部署 1.复制Nginx配置文件 # 1.拉取镜像 docker pull nginx # 2.启动nginx容器 docker run --restartalways --namenginx -p 80:80 -d nginx # 3.宿主机创建挂载目录 mkdir /root/docker/nginx -p # 4…...
C语言基础---指针的基本语法
概述 内存地址 在计算机内存中,每个存储单元都有一个唯一的地址(内存编号)。通俗理解,内存就是房间,地址就是门牌号 指针和指针变量 指针(Pointer)是一种特殊的变量类型,它用于存储内存地址。指针的实…...
记录--病理切片图像处理
简介 数字病理切片,也称为全幻灯片成像(Whole Slide Imaging,WSI)或数字切片扫描,是将传统的玻片病理切片通过高分辨率扫描仪转换为数字图像的技术。这种技术对病理学领域具有革命性的意义,因为它允许病理…...
Android使用shape属性绘制边框内渐变色
目录 先上效果图实现方法shape属性介绍代码结果 先上效果图 这是使用AndroidStudio绘制的带有渐变色的边框背景色 实现方法 项目中由于UI设计需求,需要给按钮、控件设置带有背景色效果的。以下是UI效果图。 这里我们使用shape属性来绘制背景效果。 shape属性介…...
分类算法(数据挖掘)
目录 1. 逻辑回归(Logistic Regression) 2. 支持向量机(Support Vector Machine, SVM) 3. 决策树(Decision Tree) 4. 随机森林(Random Forest) 5. K近邻(K-Nearest …...
html做调查问卷网站/360网站推广客服电话
HTML(javascript)或其他静态html技术中是否有能够:停止页面加载(如果浏览器尚未下载)停止页面渲染(从放置代码的位置开始)停止执行javascript(从放置代码的位置开始)简单地说,是否有类似的代码window.StopWhateverBelow()让浏览器完全忽略代码下面的内容…...
做企业网站对企业的好处/商品关键词优化的方法
第三部分 Java平台AOP技术研究 3.1 Java平台AOP技术概览 3.1.1 AOP技术在Java平台中的应用 AOP在实验室应用和商业应用上,Java平台始终走在前面。从最初也是目前最成熟的AOP工具——AspectJ,到目前已经融和在企业级容器JBoss中的JBoss AOP,均…...
旅游app界面设计/快速优化网站排名的方法
Proxy SwitchyOmega安装和使用 Proxy SwitchyOmega 是一款非常优秀的浏览器插件,适用于 Chrome 和 Firefox,它可以轻松快捷地管理和切换 IP 代理。 下载安装插件 Proxy SwitchyOmega 下载安装非常简单,除了通过 Chrome 应用商店安装之外&a…...
山东高端网站建设/谷歌广告推广网站
本文介绍通过Java程序批量替换PDF中的指定文本内容。程序环境准备如下:程序使用环境如图,需要注意的是,本文使用了免费版的PDF jar工具;另外JDK版本建议使用高版本更佳。jar文件导入后,可调用Spire.PDF提供的接口、方法…...
三亚网站制作/什么是软文营销
2018年02月01日 15:01:02 Ansel_m 阅读数:31331. 用ARC管理内存ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露。它自动为你管理retain和release的过程,所…...
广州化妆品网站建设公司/游戏特效培训机构排名
为保证Windows XP系统安全,很多朋友都在公共电脑上设置了组策略的“只运行许可的Windows应用程序”项,以此来防范外来程序对系统的破坏。而疏忽大意或为了防范他人修改组策略,一些朋友干脆连“gpedit.msc”文件也一并排除在允许运行程序之外&…...