用Redis订阅来搞个延时消息推送,定时发信息也能这么玩
- 问答
- 2026-01-06 21:01:42
- 21
(引用来源:主要思想源自Redis官方文档关于PUB/SUB频道的描述,以及有序集合(Sorted Set)数据结构的特性,并结合常见的延时任务设计模式。)
用Redis订阅来搞个延时消息推送,定时发信息也能这么玩,这个想法听起来很酷,但这里有个关键点需要先澄清一下,很多人一听到“订阅”,就会立刻想到Redis自带的那个PUB/SUB(发布/订阅)功能,但实话实说,如果你直接用原生的PUB/SUB来做延时消息,可能会掉进坑里,因为原生的订阅消息是即时的,像一阵风,吹过去就没了,它没有持久化的能力,也就是说,如果你的服务在消息发布的那一刻刚好重启了或者没在线,那这条消息就永远收不到了,这对于重要的定时任务来说简直是灾难。
更靠谱、更常用的玩法,其实是利用Redis的另一个强大功能——有序集合(Sorted Set),来模拟实现一个“延时队列”,然后配合一个不停轮询的“工人”服务,最终达到延时推送的效果,这个过程虽然底层不是直接用SUBSCRIBE命令,但整个思想依然是“发布”和“消费”的订阅模式,只是我们把它做得更聪明、更可靠了。
下面我就来详细说说这个“这么玩”的具体步骤,非常简单易懂:
第一步:把带时间的“任务单”塞进Redis
想象一下,Redis就像一个超级智能的公告板,但这个公告板有个特点:它上面的每张纸条(也就是每个任务)都带有一个精确的时间戳,这个时间戳不是纸条贴上去的时间,而是你希望这张纸条被处理的时间。
你想在晚上8点给用户小红推送一条生日祝福,你就在晚上8点之前,任何方便的时候,执行一个命令,把这条任务存进去,命令大概是这样的:

ZADD delayed_queue 2023-10-27T20:00:00 '{"user": "小红", "message": "生日快乐!"}'
这里,delayed_queue 就是我们给这个公告板取的名字,也就是有序集合的键名。2023-10-27T20:00:00 是这个时间戳的具体数值,它决定了纸条的排列顺序,后面那一串JSON字符串,就是任务的具体内容,告诉系统要干什么。
第二步:安排一个永不疲倦的“监工”
任务单已经贴到公告板上了,但还需要一个眼睛特别尖、手脚特别勤快的“监工”来盯着,这个“监工”其实就是我们写的一段后台程序(比如用Python、Java、Go等任何你熟悉的语言写的),它在一个死循环里,一刻不停地做两件事:
- 查看最紧急的任务:它不断地去问Redis公告板:“现在几点了?有没有哪些纸条上写的时间已经过了当前时间?” 这个动作用Redis命令来实现就是
ZRANGEBYSCORE delayed_queue 0 <当前时间戳> WITHSCORES LIMIT 0 1,这个命令的意思是:从delayed_queue这个集合里,找出分数(也就是时间戳)在0到当前时间之间的成员,只取最老的一个(LIMIT 0 1)。 - 处理任务并撕掉纸条:如果监工发现有这样的纸条(任务),它立刻把纸条内容取出来,它要做的关键一步是“撕掉纸条”,防止被其他监工重复处理,这个动作通常用一个原子命令完成,
ZREM delayed_queue '取出的任务内容',确保只有一个人能成功撕下纸条。
第三步:监工把任务派发出去(这才是“推送”的核心)

监工成功拿到并移除任务纸条后,它就可以真正地执行“推送”动做了,这个时候,推送的方式就非常灵活了:
- 方式A:直接干活:如果任务很简单,比如就是发个邮件或者更新一下数据库,监工自己就能顺手做了。
- 方式B:再次发布,触发真正的订阅者(这才是用到PUB/SUB的地方):这才是把“订阅”概念完美融入的地方,监工可以扮演一个“触发者”的角色,它把从纸条上拿到的任务内容,原封不动地通过Redis的原生PUB/SUB功能,发布到一个特定的频道(Channel)上,比如叫做
notification_channel。
在你的系统里,那些真正关心消息推送的服务(比如专门负责连接用户手机、发送推送消息的服务),就可以提前订阅这个 notification_channel,一旦监工发布了消息,这些服务就能实时收到,并立刻执行推送,这样做的好处是职责分离,监工只负责按时触发,专业的推送服务负责具体执行。
为什么这个方法比单纯用PUB/SUB好?
- 消息不会丢:因为任务是用ZADD存储在有序集合里的,即使你的“监工”服务重启,任务依然安然无恙地待在Redis里,等监工上线后还能继续处理。
- 时间精准:通过有序集合的分数排序,可以确保时间最早的任务永远被优先处理,非常公平。
- 可扩展性强:你可以轻松启动多个“监工”实例同时工作,只要用原子命令ZREM来争抢任务,就能实现负载均衡,处理海量延时消息。
- 灵活度高:监工派发任务的方式可以随心所欲,可以直接处理,可以触发PUB/SUB,也可以扔进另一个消息队列(如Kafka、RabbitMQ),适应各种复杂的业务场景。
可能会遇到的小麻烦和解决办法
- 麻烦1:监工太忙了,空转浪费资源:如果公告板上最近都没有要到点的任务,监工不停地查询,会发现每次都空手而归,这样循环会非常快,白白消耗CPU,解决办法是“稍微歇一会儿”,在每次查询不到任务时,让程序休眠一小段时间,比如1秒或0.5秒,然后再继续查。
- 麻烦2:任务处理失败怎么办?:如果监工在撕下纸条后,还没来得及推送,自己就崩溃了,那这个任务就永远丢失了,为了解决这个问题,更严谨的做法是:监工先不要急着用ZREM撕纸条,而是用一个更复杂的原子命令(如Lua脚本)先把任务从一个集合“移动”到另一个“处理中”的临时集合,等确认推送成功后,再从“处理中”集合里删除,还需要有另一个线程定期检查“处理中”集合里有没有“卡住”太久的任务,把它们重新放回主队列。
用Redis搞延时消息推送,其精髓不在于直接用SUBSCRIBE命令守株待兔,而在于“有序集合存任务 + 后台进程轮询检查 + 灵活派发”这套组合拳,它巧妙地利用了Redis速度快、支持排序的特性,构建了一个简单却无比强大的延时任务系统,无论是电商平台的订单超时取消、游戏的定时活动开启,还是我们日常的提醒推送,都能用这种“玩法”轻松搞定,既高效又可靠。
本文由歧云亭于2026-01-06发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/75791.html
