Redis实现延时任务

基于 Redis 实现延时任务主要有下面两种方案:

  • Redis 过期事件监听
  • Redisson 内置的延时队列

一、Redis Key 过期事件(Keyspace Notifications)

1️.是什么

Redis 提供的一种事件通知机制,可以监听:

  • key 过期
  • key 删除
  • key 修改

延时任务里常用的是:key 过期事件


2️.如何开启

默认是关闭的,需要手动开启:

1
CONFIG SET notify-keyspace-events Ex

参数解释

参数 含义
E Keyevent 事件
x 过期事件

👉 常见配置:

1
Ex

3️.如何监听

订阅 Redis 频道:

1
PSUBSCRIBE __keyevent@0__:expired

4️.使用示例(延时任务)

① 设置一个延时 key

1
SET order:123 "xxx" EX 10

② 10秒后触发事件

监听到:

1
order:123

然后执行对应逻辑(比如关闭订单)


5️.底层原理(重点🔥)

Redis 过期机制不是“定时器”,而是:

两种策略

1. 惰性删除(lazy)

  • 访问 key 时发现过期 → 删除

2. 定期删除(active)

  • Redis 每隔一段时间随机扫描过期 key

关键点:

不是精确时间触发


6️.致命问题(面试必说⚠️)

❌ 1. 不实时

  • 10秒 ≠ 一定10秒执行
  • 可能延迟几十秒甚至更久

❌ 2. 可能丢事件

  • Redis 重启 → 事件丢失
  • 没有持久化事件机制

❌ 3. 不保证顺序

  • 多个 key 同时过期 → 顺序不确定

❌ 4. 集群问题

  • 事件只在当前节点
  • 分布式下难统一监听

7️.适用场景

✔ 可以用:

  • 简单通知(如缓存失效)
  • 不敏感延迟任务

❌ 不建议:

  • 订单超时关闭
  • 金融类任务

8️.一句话总结

Redis 过期事件 = “尽力而为”的通知机制,不是可靠延时队列


二、Redisson 延时队列(企业级方案 ⭐⭐⭐⭐⭐)

1️.是什么

Redisson 提供的:分布式延时队列实现


2️.使用方式(非常简单)

1
2
3
4
5
6
7
8
RBlockingQueue<String> queue = redisson.getBlockingQueue("queue");
RDelayedQueue<String> delayedQueue = redisson.getDelayedQueue(queue);

// 添加延时任务
delayedQueue.offer("task1", 10, TimeUnit.SECONDS);

// 消费任务(阻塞)
queue.take();

3️.核心设计(重点🔥)

Redisson 内部并不是简单用 ZSet,而是:

组合结构

1
2
3
4
5
ZSet(延时队列)
↓(时间到了)
List(阻塞队列)

消费者(BLPOP / take)

4️.工作流程

① 生产者

1
任务 → 写入 ZSet(score=执行时间)

② 调度线程(核心)

Redisson 内部有一个后台线程

  • 定期扫描 ZSet
  • 找到“到期任务”
  • 移动到 List

③ 消费者

1
从 List 阻塞消费(无轮询)

5️.为什么比你自己写好?

1. 无轮询

  • 使用阻塞队列
  • CPU 友好

2. 原子性保证(Lua脚本)

类似:

1
ZRANGEBYSCORE + ZREM + LPUSH

一次完成,避免重复消费


3. 分布式安全

  • 多节点不会重复消费
  • 内部处理竞争问题

4. 高可靠性

  • 支持 Redis 持久化(RDB/AOF)
  • 不依赖事件通知

6️.底层细节(加分点✨)

🔹 使用 Lua 脚本保证:

  • 查询任务
  • 删除任务
  • 投递任务

原子执行


🔹 使用时间轮优化(部分版本)

减少扫描压力


🔹 使用 Pub/Sub 唤醒机制

避免无意义轮询


7️.优缺点总结

优点

  • 简单易用
  • 高可靠
  • 支持分布式
  • 性能好

缺点

  • 依赖 Redis
  • 延时精度 ≈ 100ms级(非绝对实时)

8️.适用场景

✔ 非常适合:

  • 订单超时关闭
  • 延迟消息
  • 重试机制

9️.面试对比总结(重点🔥)

你可以这样说:

Redis Key 过期事件是基于 Redis 的过期机制触发的通知,但由于 Redis 采用惰性删除和定期删除策略,导致事件触发不实时且可能丢失,因此不适合做可靠延时任务。

而 Redisson 延时队列是基于 ZSet + List + Lua 实现的,内部通过后台线程将到期任务从 ZSet 转移到阻塞队列中,消费者再进行消费,具有更好的实时性和可靠性,因此在实际项目中更推荐使用。


十、一张图总结

1
2
3
4
5
Key过期事件:
SET EX → 等过期 → 监听通知 ❌ 不可靠

Redisson:
ZSet → 搬运 → List → 阻塞消费 ✅ 企业级方案