news 2026/5/10 21:44:27

Redis定时任务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis定时任务

“Redis 定时任务”这个概念通常有两种层面的解读:

  1. 内部原理:Redis 自身是如何管理 key 的过期时间(TTL)的?它是怎么知道并在某个时间点删除数据的?

  2. 应用实现:开发者如何利用 Redis 实现分布式的“延时队列”或“定时任务”(例如:下单 30 分钟后未支付自动取消)?

我们分别来深入讲解。


一、 Redis 内部原理:Key 的过期策略

你可能会以为当你设置EXPIRE key 60时,Redis 会给这个 key 启动一个倒计时器,时间一到就触发删除。但这在海量数据下是不可能的,因为 CPU 撑不住。

Redis 采用的是惰性删除 (Lazy Deletion)+定期删除 (Periodic Deletion)相结合的策略。

1. 惰性删除 (Lazy Deletion)
  • 原理:Redis 不会主动去盯着 key 什么时候过期。

  • 触发时机:当客户端去访问某个 key(get/set 等操作)时,Redis 会先检查:“哎,这个 key 设置了过期时间吗?过期了吗?”

    • 如果过期了:直接删除,返回nil

    • 没过期:正常返回数据。

  • 优缺点:极其节省 CPU,但极其浪费内存。如果大量过期的 key 再也没被访问过,它们就会一直占着内存不释放。

2. 定期删除 (Periodic Deletion)

为了解决惰性删除导致的“内存泄露”问题,Redis 有一个后台周期性任务。

  • 原理:Redis 默认每秒运行 10 次(通过hz参数配置)serverCron任务。

  • 流程:

    1. 从设置了过期时间的 key 集合中,随机抽取20 个 key。

    2. 检查这 20 个 key,删除其中已过期的。

    3. 如果过期的 key 比例超过 25%,则重复步骤 1(说明过期的很多,要多删点)。

  • 限制:为了防止这个循环卡死主线程(Redis 是单线程的),它有一个执行时间上限(默认 25ms)。如果超时,立刻停止,等下一轮再说。

总结:Redis 的过期不是“准时”的,而是在访问时后台随机抽查时清理的。


二、 应用实现:基于 Redis 的定时任务(延时队列)

这是开发者最关心的部分。假设你要做“订单 30 分钟自动关闭”,在 NestJS 或其他后端中,怎么用 Redis 实现?

方案 1:Redis ZSet (Sorted Set) ——最推荐、最主流

这是实现分布式延时队列的标准做法。

  • 原理:利用 ZSet 的Score来存储任务的执行时间戳

  • 数据结构:

    • Key:delay_queue

    • Score:Date.now() + 30 * 60 * 1000(未来执行的时间戳)

    • Member:Order ID(或任务的 JSON 数据)

  • 执行流程 (轮询 Loop):

    1. 消费者 (Consumer)每秒(或几百毫秒)轮询 Redis。

    2. 执行命令ZRANGEBYSCORE delay_queue 0 <当前时间戳> LIMIT 0 1

      • 意思是:把“截止到现在应该执行的任务”拿出来 1 个。

    3. 如果拿到了任务:

      • 原子性移除:使用ZREM移除该任务(防止重复执行)。

      • 注意:在多实例并发下,通常建议使用Lua 脚本ZRANGEZREM原子化,确保只有一个消费者抢到任务。

    4. 处理业务逻辑(如关闭订单)。

  • 优点:精度高,支持海量任务,原生支持排序。

  • 缺点:消费者需要不断轮询(Polling),空转时会增加 Redis QPS。

订单 30 分钟自动关闭的具体实现流程:

假设现在是12:00,用户下了一个单order_1001

第一步:生产者入队 (ZADD)用户下单成功后,代码往 Redis 里写一条记录:

  • 命令:ZADD delay_queue <12:30的时间戳> "order_1001"

  • 含义:“Redis 帮我记一下,order_1001 这个单子,要在 12:30 处理。”

  • 注意:这时候数据是存在的,不是等它消失。

第二步:消费者轮询 (ZRANGEBYSCORE)你有一个后台死循环脚本(Consumer),每秒钟问一次 Redis。

  • 12:01 问:“Redis,有没有分数 <= 12:01的订单?”

    • Redis:没有。

  • 12:29 问:“Redis,有没有分数 <= 12:29的订单?”

    • Redis:没有。(因为 order_1001 的分数是 12:30)

  • 12:30:01 问:“Redis,有没有分数 <= 12:30:01的订单?”

    • Redis:“有!order_1001 的分数是 12:30,它到期了!”

第三步:执行并删除 (ZREM)消费者拿到了order_1001

  1. 去数据库查一下这个订单支付没有?

  2. 没支付 -> 执行关单逻辑。

  3. 已支付 -> 忽略。

  4. 关键:从 Redis 里删掉这行记录 (ZREM delay_queue "order_1001"),防止下一秒又把它取出来重复执行。

方案 2:Redis KeySpace Notifications (键空间通知) ——不推荐

Redis 有一个功能:当 Key 过期被删除时,发布一个 Pub/Sub 事件。

  • 原理:

    1. 开启配置notify-keyspace-events Ex

    2. 设置SET order_123 "data" EX 1800(30分钟过期)。

    3. 应用订阅__keyevent@0__:expired频道。

    4. 当 key 过期消失时,应用收到通知,解析 key 里的 ID,去关单。

  • 为什么极其不推荐?

    1. 不可靠:Redis 的 Pub/Sub 是“发后即忘”的。如果你的服务刚好重启了,或者网络抖动了一下,这个“过期事件”就丢了,Redis 不会重发。你的订单就永远关不掉了。

    2. 不准时:结合第一部分说的“定期删除”原理,一个 key 过期了,可能很久之后才会被 Redis 的随机算法抽中并删除,此时才会发通知。延迟可能高达数分钟。

方案 3:Redisson DelayedQueue (Java/Node 生态封装)

如果你不想手写 ZSet 的轮询逻辑,很多客户端库(如 Java 的 Redisson,或者 Node.js 的 BullMQ)封装好了。

  • 原理:它是ZSet + Pub/Sub的结合体。

    • 数据存在 ZSet 里。

    • 客户端一旦有新任务加入,或者有任务即将到期,会通过 Pub/Sub 通知消费者“醒醒,干活了”。

    • 这样避免了 ZSet 方案中高频率空轮询的 CPU 浪费。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 15:45:13

硬件学习规划

找到发表的论文或者项目复现他们

作者头像 李华
网站建设 2026/5/10 10:59:02

(Open-AutoGLM部署黄金法则)资深IT架构师20年经验浓缩6大要点

第一章&#xff1a;质谱Open-AutoGLM部署概述项目背景与核心目标 质谱Open-AutoGLM 是一个面向质谱数据分析场景的自动化大语言模型部署框架&#xff0c;旨在将自然语言处理能力深度集成至质谱数据解析流程中。该系统通过构建领域特定的知识图谱&#xff0c;并结合微调后的生成…

作者头像 李华
网站建设 2026/5/9 9:50:32

6、工作流开发:订单折扣计算与图书馆书籍预订通信实现

工作流开发:订单折扣计算与图书馆书籍预订通信实现 在工作流开发中,我们可以通过扩展内置活动来满足不同的业务需求,同时利用工作流活动简化和协调各种通信场景。下面将详细介绍订单折扣计算和图书馆书籍预订通信的实现过程。 订单折扣计算 在订单处理过程中,我们需要计…

作者头像 李华
网站建设 2026/5/5 2:34:34

8、《WPF 应用与工作流通信开发指南》

《WPF 应用与工作流通信开发指南》 在开发过程中,我们常常需要实现应用程序与工作流之间的有效通信。本文将详细介绍如何构建一个基于 Windows Presentation Foundation (WPF) 的应用程序,并实现它与工作流的通信。 1. 创建 WPF 项目 首先,我们需要创建一个 WPF 项目。具…

作者头像 李华
网站建设 2026/4/25 5:16:32

9、工作流与主机应用程序通信及Web服务开发

工作流与主机应用程序通信及Web服务开发 1. 实现ProcessRequest工作流 ProcessRequest工作流与之前实现的版本有一些不同。此工作流定义如下,需将代码添加到 ReservationWF.cs 文件中: public sealed class ProcessRequest : Activity {public InArgument<Reservati…

作者头像 李华