当前位置: 首页 > article >正文

Java实现Redis延迟队列:从原理到高可用架构

在现代分布式系统中延迟队列是一种至关重要的组件。它允许我们将消息或任务放入队列直到指定的延迟时间到达后才被消费。这种机制广泛应用于订单超时自动取消、支付后定时发送通知、任务重试等场景。虽然RabbitMQ和RocketMQ等专业消息中间件都支持延迟消息但在很多轻量级场景下利用Redis来实现延迟队列是一个更简单、高效的选择。本教程将带你深入理解Redis延迟队列的原理并手把手教你用Java实现一个高可用的延迟队列。核心原理有序集合的巧妙应用实现Redis延迟队列的核心在于利用Redis的**有序集合Sorted Set简称ZSET**数据结构。ZSET中的每个成员Member都关联一个分数Score这个分数是一个浮点数Redis会根据分数对成员进行自动排序。利用这一特性我们可以将任务的执行时间戳作为Score将任务内容或ID作为Member。工作流程如下生产消息入队当需要添加一个延迟任务时计算出它的执行时间当前时间 延迟时间然后使用ZADD命令将其添加到ZSET中。消费消息出队消费者不断轮询ZSET使用ZRANGEBYSCORE命令获取所有分数执行时间小于等于当前时间的任务。这些就是已经“到期”的任务。处理与删除消费者获取到到期任务后进行处理处理完成后使用ZREM命令将其从ZSET中移除。这种实现方式利用了ZSET的自动排序特性使得获取最早到期的任务变得非常高效。基础实现基于Jedis的手动封装为了让你更清晰地理解底层原理我们首先使用Jedis客户端手动实现一个延迟队列。Maven依赖首先确保你的项目中包含了Jedis的依赖。dependency groupIdredis.clients/groupId artifactIdjedis/artifactId version4.3.1/version /dependency核心代码实现下面是一个完整的延迟队列工具类它封装了入队、出队和监听的核心逻辑。import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Tuple; import java.util.Set; public class RedisDelayQueue { private final JedisPool jedisPool; private final String queueKey; public RedisDelayQueue(String host, int port, String queueKey) { this.jedisPool new JedisPool(new JedisPoolConfig(), host, port); this.queueKey queueKey; } /** * 添加延迟任务 * param task 任务内容 * param delayMillis 延迟时间毫秒 */ public void addTask(String task, long delayMillis) { try (Jedis jedis jedisPool.getResource()) { // Score为当前时间 延迟时间 long score System.currentTimeMillis() delayMillis; // Member为任务内容 jedis.zadd(queueKey, score, task); System.out.println(任务已添加: task , 将在 delayMillis ms 后执行); } } /** * 获取并移除一个到期的任务 * return 到期的任务内容如果没有到期任务则返回null */ public String getTask() { try (Jedis jedis jedisPool.getResource()) { long now System.currentTimeMillis(); // 获取所有分数小于等于当前时间的任务即已到期任务 // 我们只取第一个保证一次只处理一个 SetTuple tasks jedis.zrangeByScoreWithScores(queueKey, 0, now, 0, 1); if (tasks ! null !tasks.isEmpty()) { Tuple taskTuple tasks.iterator().next(); String task taskTuple.getElement(); // 获取后立即从队列中移除 jedis.zrem(queueKey, task); return task; } return null; } } /** * 启动消费者监听 */ public void startConsumer() { System.out.println(消费者已启动正在监听队列...); new Thread(() - { while (true) { String task getTask(); if (task ! null) { // 处理任务 handleTask(task); } else { // 没有任务短暂休眠以避免CPU空转 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }).start(); } private void handleTask(String task) { System.out.println(正在处理任务: task); // 模拟业务处理耗时 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(任务处理完成: task); } public static void main(String[] args) { RedisDelayQueue queue new RedisDelayQueue(localhost, 6379, my_delay_queue); // 启动消费者 queue.startConsumer(); // 添加一些测试任务 queue.addTask(订单1超时取消, 3000); // 3秒后 queue.addTask(订单2超时取消, 5000); // 5秒后 queue.addTask(订单3超时取消, 3000); // 3秒后 } }进阶方案使用Redisson实现生产级队列虽然手动实现有助于理解原理但在生产环境中我们更推荐使用Redisson。Redisson是一个强大的Java驻内存数据网格客户端它为我们提供了开箱即用的RDelayedQueue解决了手动实现中的许多痛点。Redisson的优势简化开发无需手动编写轮询和删除逻辑。原子性保障内部使用Lua脚本保证获取和删除操作的原子性避免并发问题。高性能底层基于发布/订阅模式消费者可以阻塞等待新任务无需频繁轮询大大降低了CPU和网络开销。Maven依赖dependency groupIdorg.redisson/groupId artifactIdredisson/artifactId version3.21.3/version /dependency核心代码实现Redisson的实现方式非常优雅它将延迟队列RDelayedQueue和最终的执行队列RBlockingQueue分离。import org.redisson.Redisson; import org.redisson.api.RBlockingQueue; import org.redisson.api.RDelayedQueue; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import java.util.concurrent.TimeUnit; public class RedissonDelayQueueDemo { public static void main(String[] args) throws InterruptedException { // 1. 创建Redisson客户端 Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379); RedissonClient redissonClient Redisson.create(config); // 2. 获取一个阻塞队列它将作为延迟任务的最终目的地 RBlockingQueueString blockingQueue redissonClient.getBlockingQueue(my_task_queue); // 3. 基于阻塞队列创建一个延迟队列 RDelayedQueueString delayedQueue redissonClient.getDelayedQueue(blockingQueue); // 4. 启动消费者线程从阻塞队列中获取任务 new Thread(() - { while (true) { try { // take()方法会阻塞直到有任务可用 String task blockingQueue.take(); System.out.println(消费者收到任务: task); // 处理任务... } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); // 5. 生产者添加延迟任务 System.out.println(添加延迟任务...); delayedQueue.offer(订单A-5秒后执行, 5, TimeUnit.SECONDS); delayedQueue.offer(订单B-2秒后执行, 2, TimeUnit.SECONDS); delayedQueue.offer(订单C-10秒后执行, 10, TimeUnit.SECONDS); // 保持主线程运行 Thread.sleep(20000); // 关闭客户端 redissonClient.shutdown(); } }生产环境的挑战与解决方案在实际应用中我们必须考虑一些边界情况以确保队列的可靠性。如何保证消息不丢失在手动实现中如果消费者获取到任务后在处理完成前进程崩溃那么这个任务就会永久丢失。一个常见的解决方案是引入二次确认机制。消费者获取到期任务后不立即删除而是将其移动到一个“处理中”的临时集合。处理完成后再从“处理中”集合删除。同时需要一个独立的“看门狗”线程定期检查“处理中”集合里的任务是否超时。如果超时则将其重新放回主队列实现自动重试。如何避免并发问题在多线程环境下多个消费者可能同时获取到同一个到期任务导致任务被重复执行。Redisson方案Redisson的RBlockingQueue.take()操作是原子的天然支持多消费者竞争不会有重复消费的问题。手动方案可以使用Redis的ZREMRANGEBYSCORE命令该命令会原子性地移除指定分数范围的成员并返回被移除的成员。这样获取和删除就合并成了一个原子操作。如何监控队列状态运维监控是生产环境不可或缺的一环。你可以轻松地通过Redis命令来监控队列。查看队列长度ZCARD my_delay_queue查看下一个即将执行的任务ZRANGE my_delay_queue 0 0 WITHSCORES查看积压的未到期任务ZCOUNT my_delay_queue (当前时间戳 inf

相关文章:

Java实现Redis延迟队列:从原理到高可用架构

在现代分布式系统中,延迟队列是一种至关重要的组件。它允许我们将消息或任务放入队列,直到指定的延迟时间到达后才被消费。这种机制广泛应用于订单超时自动取消、支付后定时发送通知、任务重试等场景。 虽然RabbitMQ和RocketMQ等专业消息中间件都支持延迟…...

二手破损手机涨价,业余 NAS 玩家如何破局?

最近打开手机回收 App,发现家里那台屏幕碎成渣、开不了机的旧安卓机,居然能卖一百多,甚至两三百。你可能会想:这是天上掉馅饼,还是 NAS 玩家的“矿难”前兆? 作为一名业余 NAS 玩家,我正好踩在这…...

网络异常排查:快速定位域连接问题

问题描述与初步排查网络位置异常通常表现为计算机无法正确识别当前所在的AD域环境,导致访问域资源受限或登录问题。常见症状包括系统托盘显示“无法访问域”、组策略无法应用、DNS解析失败等。检查计算机是否能够ping通域控制器的主机名和IP地址。使用nslookup命令验…...

告别Windows AI困扰:RemoveWindowsAI工具全方位解决方案

告别Windows AI困扰:RemoveWindowsAI工具全方位解决方案 【免费下载链接】RemoveWindowsAI Force Remove Copilot and Recall in Windows 项目地址: https://gitcode.com/GitHub_Trending/re/RemoveWindowsAI 在数字时代的隐私保卫战中,Windows系…...

头歌平台实战:C语言文件操作中的数字提取与格式化存储

1. 头歌平台C语言文件操作实战入门 第一次接触头歌平台的C语言文件操作任务时,我完全被那些fopen、fscanf函数弄晕了。直到真正动手完成"数字提取与格式化存储"这个项目,才发现原来文件操作可以这么有趣又实用。这个项目特别适合刚学完C语言基…...

Pixel Dream Workshop 在电商领域的应用:一键生成商品场景图

Pixel Dream Workshop 在电商领域的应用:一键生成商品场景图 1. 电商商品图的痛点与机遇 电商行业有个公开的秘密:商品图片的制作成本往往比想象中高得多。我们曾合作过的一家服装电商,每月仅模特拍摄费用就超过20万元,这还不包…...

TripoSR:0.5秒单图像3D重建技术指南与实战应用

TripoSR:0.5秒单图像3D重建技术指南与实战应用 【免费下载链接】TripoSR 项目地址: https://gitcode.com/GitHub_Trending/tr/TripoSR 在3D内容创作领域,传统建模流程耗时耗力,而TripoSR作为开源3D重建模型,通过单张2D图像…...

三相永磁同步电机FOC控制实战:从霍尔传感器配置到SVPWM调参避坑指南

三相永磁同步电机FOC控制实战:从霍尔传感器配置到SVPWM调参避坑指南 当你在深夜的实验室里盯着示波器上跳动的波形,试图让一台三相永磁同步电机平稳启动时,是否经历过这样的场景:明明按照手册配置了所有参数,电机却像喝…...

4步打造高效能开源路由器:OpenWrt固件安装指南

4步打造高效能开源路由器:OpenWrt固件安装指南 【免费下载链接】openwrt openwrt编译更新库X86-R2C-R2S-R4S-R5S-N1-小米MI系列等多机型全部适配OTA自动升级 项目地址: https://gitcode.com/GitHub_Trending/openwrt5/openwrt OpenWrt固件安装是提升R5S设备性…...

RSA1 - Writeup by AI

RSA1 - Writeup by AI 1. 题目描述项目内容题目来源Bugku题目类型Crypto (密码学)考点RSA 大数分解、私钥计算题目信息 题目给出了 RSA 加密的三个参数: e 65537 N 1018261336751023520497560395829454421245429586704872293236600679847605951423419167478189648…...

FanControl实战指南:从噪音困扰到智能散热的转型之路

FanControl实战指南:从噪音困扰到智能散热的转型之路 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/…...

在Ubuntu 22.04上为RK3588编译带RKmpp和RGA的FFmpeg(保姆级避坑指南)

在Ubuntu 22.04上为RK3588编译带RKmpp和RGA的FFmpeg(保姆级避坑指南) RK3588作为Rockchip新一代旗舰SoC,其强大的多媒体处理能力吸引了众多开发者。本文将手把手带你完成FFmpeg的完整编译流程,重点解决环境配置、依赖管理、运行时…...

告别PDF转换烦恼:Marker让学术文档秒变Markdown的完整指南

告别PDF转换烦恼:Marker让学术文档秒变Markdown的完整指南 【免费下载链接】marker 一个高效、准确的工具,能够将 PDF 和图像快速转换为 Markdown、JSON 和 HTML 格式,支持多语言和复杂布局处理,可选集成 LLM 提升精度&#xff0c…...

探索Ryujinx:Nintendo Switch模拟器全解析

探索Ryujinx:Nintendo Switch模拟器全解析 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx 在游戏技术不断发展的今天,模拟器技术为玩家提供了跨平台体验游戏的可…...

Marin说PCB之GMSL2 POC电路优化实战---从仿真到测试的完整解析

1. GMSL2 POC电路问题诊断与优化思路 最近在测试GMSL2 POC电路时遇到了一个典型问题:多路信号的插损(S21)和回损(S11)指标不达标。这种情况在实际项目中并不少见,但每次遇到都需要我们仔细分析原因并找到有…...

LSPosed-Irena深度解析:Android运行时Hook框架的终极指南

LSPosed-Irena深度解析:Android运行时Hook框架的终极指南 【免费下载链接】LSPosed-Irena Useless LSPosed Framework Fork 项目地址: https://gitcode.com/gh_mirrors/ls/LSPosed-Irena 你是否曾想过,在不修改APK源代码的情况下,深度…...

如何利用多渠道SEO推广提高网站流量

<h2>多渠道SEO推广&#xff1a;如何提高网站流量</h2> <p>在当前竞争激烈的互联网环境中&#xff0c;网站流量是衡量网站成功与否的重要指标之一。如何利用多渠道SEO推广提高网站流量&#xff0c;成为了每一个网站运营者关注的焦点。本文将从问题分析、原因说…...

突破性网络资源嗅探解决方案:从技术困境到智能下载的革命性跨越

突破性网络资源嗅探解决方案&#xff1a;从技术困境到智能下载的革命性跨越 【免费下载链接】res-downloader 资源下载器、网络资源嗅探&#xff0c;支持微信视频号下载、网页抖音无水印下载、网页快手无水印视频下载、酷狗音乐下载等网络资源拦截下载! 项目地址: https://gi…...

PDF文本高效提取:用pdftotext实现秒级文档内容解析

PDF文本高效提取&#xff1a;用pdftotext实现秒级文档内容解析 【免费下载链接】pdftotext Simple PDF text extraction 项目地址: https://gitcode.com/gh_mirrors/pd/pdftotext 破解PDF提取痛点&#xff1a;为什么你需要专业工具&#xff1f; 每天面对数十份PDF文档却…...

AI辅助学术写作:Qwen3-0.6B-FP8搭配LaTeX生成论文章节与参考文献

AI辅助学术写作&#xff1a;Qwen3-0.6B-FP8搭配LaTeX生成论文章节与参考文献 写论文&#xff0c;尤其是写引言和参考文献&#xff0c;是不是让你特别头疼&#xff1f;对着空白的文档发呆&#xff0c;不知道从何下笔&#xff1b;或者为了找一篇关键的参考文献&#xff0c;在数据…...

CLIP-GmP-ViT-L-14图文匹配工具部署教程:Ubuntu 22.04 + Python 3.10 完整环境配置

CLIP-GmP-ViT-L-14图文匹配工具部署教程&#xff1a;Ubuntu 22.04 Python 3.10 完整环境配置 你是不是经常好奇&#xff0c;一张图片到底和哪段文字描述最匹配&#xff1f;比如&#xff0c;你拍了一张自家宠物的照片&#xff0c;想知道AI会觉得它更像“一只可爱的猫”还是“一…...

Pixel Dimension Fissioner 镜像深度配置:环境变量与启动参数详解

Pixel Dimension Fissioner 镜像深度配置&#xff1a;环境变量与启动参数详解 1. 为什么需要深度配置&#xff1f; 当你第一次部署Pixel Dimension Fissioner镜像时&#xff0c;默认设置可能已经能满足基本需求。但随着使用场景的复杂化&#xff0c;你会发现很多情况下需要根…...

从零构建:基于C语言的Modbus RTU从站驱动开发指南

1. Modbus RTU从站驱动开发入门指南 第一次接触Modbus RTU从站开发时&#xff0c;我完全被各种专业术语搞晕了。后来在工厂里调试一个温湿度传感器时&#xff0c;才真正理解这个协议的精妙之处——它就像车间里老师傅们约定俗成的对话方式&#xff0c;主设备问一句&#xff0c;…...

别再被MPU6050的偏航角坑了!手把手教你用MPU9250(或外接HMC5883L磁力计)彻底解决零飘问题

彻底解决MPU6050偏航角零飘&#xff1a;硬件升级与磁力计融合实战指南 在无人机、平衡车和机器人姿态控制领域&#xff0c;MPU6050曾是许多开发者的首选惯性测量单元(IMU)。这款经典的六轴传感器以低廉的价格和稳定的性能赢得了市场&#xff0c;但它的一个致命缺陷让无数工程师…...

手把手教你用Wireshark抓包分析Opener EIP通信,快速定位ForwardOpen失败原因

深度解析EtherNet/IP通信&#xff1a;用Wireshark诊断ForwardOpen失败的实战指南 当你在MCU上成功移植了Opener协议栈&#xff0c;TCP连接建立正常&#xff0c;却在关键时刻遭遇ForwardOpen失败时&#xff0c;那种挫败感我深有体会。去年在汽车生产线控制系统项目中&#xff0c…...

Python实战:5分钟搞定睿尔曼机械臂与AGV底盘的Socket通信(附完整代码)

Python实战&#xff1a;5分钟搞定睿尔曼机械臂与AGV底盘的Socket通信&#xff08;附完整代码&#xff09; 在工业自动化领域&#xff0c;复合机器人正逐渐成为提升生产效率的关键设备。这类机器人通常由AGV&#xff08;自动导引运输车&#xff09;底盘和机械臂组成&#xff0c;…...

USB批量传输中ZLP的必要性:为何512字节整数倍数据包会丢失

1. USB批量传输中的ZLP到底是什么&#xff1f; 第一次遇到USB批量传输丢数据的问题时&#xff0c;我也是一头雾水。明明发送端显示数据已经成功发送&#xff0c;接收端却死活收不到完整数据。后来排查发现&#xff0c;问题出在数据包大小刚好是512字节的整数倍时。这就是我们今…...

Codesys电子凸轮Cam表两种设置方法对比:可视化拖拽 vs 程序动态配置

Codesys电子凸轮Cam表设置方法深度对比&#xff1a;可视化拖拽与程序动态配置实战解析 在工业自动化领域&#xff0c;电子凸轮技术正逐步取代传统机械凸轮&#xff0c;成为运动控制系统的核心组件。作为Codesys平台下的重要功能&#xff0c;Cam表的设置方法直接关系到运动轨迹…...

不用编译!快速修改Scratch-blocks积木字体的偷懒方法

零编译实战&#xff1a;Scratch-blocks字体调整极简方案 在Scratch 3.0的二次开发过程中&#xff0c;积木字体过小是开发者普遍遇到的痛点。官方移除了字体调节功能后&#xff0c;低分辨率设备上的中文显示尤为模糊。传统解决方案需要配置Python环境并重新编译scratch-blocks库…...

Flutter Gradle插件迁移指南:从apply script到声明式plugins的实践

1. 为什么需要迁移到声明式plugins块 最近在维护一个Flutter项目时&#xff0c;我发现每次构建Android端都会弹出一个黄色警告&#xff1a;"You are applying Flutters app_plugin_loader Gradle plugin imperatively using the apply script method..."。这个警告看…...