Redis Pub/Sub 不支持延时消息,因其仅为实时广播通道,无延迟、持久化、重试与消费确认机制;需借助 Sorted Set + 定时轮询或 Redis Streams 等替代方案实现。

为什么不能直接用 Redis Pub/Sub 做延时消息
Redis 的 PUBSUB 本身是纯实时通道,不支持延迟、重试、持久化或消费确认。一旦发布者调用 PUBLISH,消息立刻推给所有在线订阅者;若消费者离线,消息就彻底丢失。这不是“延时发送”,而是“即时广播”。想实现“10秒后才发给用户”,必须绕开 PUBSUB 的实时性限制,用其他机制触发真正的发布动作。
用 Sorted Set + 定时轮询模拟延时队列
核心思路:把“要延时发布的消息”先存进 ZSET,用时间戳作 score,再起一个轻量级调度器持续 ZRANGEBYSCORE 扫描到期消息,取出后 PUBLISH 并 ZREM。
-
ZADD delay:queue 1717023600000 "msg:order_123:notify_user"(score 是毫秒时间戳) - 调度器每 100ms 执行:
ZRANGEBYSCORE delay:queue -inf (1717023600000,取所有 ≤ 当前时间的消息 - 对每个取出的
member,执行PUBLISH notify:channel "msg:order_123:notify_user",再ZREM delay:queue "msg:order_123:notify_user" - 注意用
(表示开区间,避免重复消费同一时间戳的多条消息
调度器必须处理并发和失败场景
单机轮询容易成为瓶颈,且进程崩溃会导致消息卡住。实际部署至少需保证两点:原子性、可恢复性。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
EVAL脚本包裹ZRANGEBYSCORE + ZREM,防止取出后崩溃导致消息丢失(否则只能靠外部重试) - 不要在脚本里做
PUBLISH—— Lua 脚本中PUBLISH不支持,会报错ERR Error running script (call to f_...): @user_script: N: @enable_strict_redis_enabled@ - 推荐做法:脚本只负责安全弹出(
ZPOPMIN或带WATCH的事务),业务代码拿到后再PUBLISH;失败则把消息写回ZSET并加 backoff score - 多个调度器实例需用分布式锁(如
SET lock:scheduler EX 30 NX)避免重复投递
替代方案:改用 Redis Streams + Consumer Group
如果延时精度要求不高(秒级)、且需要消息留存与重播能力,STREAMS 比 ZSET 更健壮。虽然它也不原生支持延时,但可结合客户端 sleep 或外部定时器 + XADD 实现更清晰的责任分离。
- 不是把消息塞进
ZSET等扫描,而是由业务方计算好触发时间,在那一刻调用XADD stream:delay * event data - 消费者用
XREADGROUP拉取,天然支持 ACK、pending list、多消费者负载分担 - 缺点:无法像
ZSET那样灵活修改 delay 时间(ZINCRBY可调 score,STREAMS的消息不可改)
真正难的不是“怎么写那几行代码”,而是设计好失败重入点、score 时间单位统一(毫秒?秒?)、以及调度器与业务服务之间的边界——别让定时逻辑混进核心接口里。

















