用Redis做队列那些事儿,聊聊几个实用命令和效率提升技巧
- 问答
- 2026-01-08 12:19:07
- 3
说到用Redis做队列,这绝对是Redis一个非常经典的应用场景,它不像RabbitMQ、Kafka那样是专业的队列中间件,但胜在简单、快速、足够轻量,很多时候,我们的业务还没复杂到需要引入一个庞大的消息系统,用Redis就能很好地解决问题,今天就来聊聊这里面的一些门道,主要是一些实用的命令和使用时怎么让效率更高。
核心命令:LPUSH 和 RPOP
这俩是Redis实现队列最基本、最经典的组合,也就是我们常说的“先进先出”(FIFO)队列,它的工作模式非常简单:
- 生产者(Producer)用
LPUSH命令,从列表的左侧(头部)插入新的任务。LPUSH my_queue "task1"。 - 消费者(Consumer)用
RPOP命令,从列表的右侧(尾部)取出任务来处理。RPOP my_queue。
这样一来,先被放进去的task1会待在队列的尾部,等着被RPOP取走,保证了先来后到的顺序,这个模型非常直观,也是很多人最开始接触Redis队列的方式。
一个关键问题:忙等待(Busy Waiting)
但上面的基本模型有个大问题:消费者怎么知道队列里来新任务了?最简单的办法是写一个循环,不停地去执行RPOP,如果队列是空的,就歇一小会儿再继续问,这种方式就叫做“忙等待”或者“轮询”,它的缺点非常明显:浪费资源,消费者进程大部分时间都在做无用功,空转查询,既消耗CPU,也给Redis服务器带来了不必要的压力。

救星命令:BRPOP 和 BLPOP
为了解决忙等待的问题,Redis提供了阻塞版本的弹出命令——BRPOP和BLPOP,命令里的B就代表Blocking(阻塞)。
这个命令太有用了,它的用法和RPOP类似,但是多了一个超时时间参数,消费者可以执行:BRPOP my_queue 30,这句话的意思是:“请从my_queue队列的右边弹出一个任务给我,如果现在队列是空的,你别急着返回,我就在这儿等着你,最多等30秒,这期间只要有新任务进来,你马上告诉我;如果30秒过去了还是空的,你再返回一个nil(空值)给我。”
这样一来,消费者进程在队列为空时就会被挂起,不占用任何CPU资源,一旦生产者LPUSH了一个新任务,Redis会立刻唤醒正在等待的消费者,把任务递交给它,这种方式效率极高,也是Redis能做高效队列的基石,根据参考资料“Redis深度探险:核心原理与应用实践”中的说明,这种基于事件的推送模式避免了不必要的轮询开销。

从单消费者到多消费者:PUB/SUB 的误区
一个生产者产生的任务需要被多个消费者同时处理(比如广播一个通知),有些人可能会想到Redis的PUBLISH和SUBSCRIBE(发布/订阅)命令,但这里有个大坑:PUB/SUB模式是“非持久化”的。
它的工作模式是:生产者向一个频道(channel)发布一条消息,所有当时正订阅着这个频道的消费者都会立刻收到,但问题是,如果一个消费者在消息发布之后才上线,它永远也收不到那条历史消息,因为消息就像电台广播,播完就没了,不保留,这对于要求消息必达的任务队列来说,是不可接受的。
PUB/SUB更适合用于实时消息推送、聊天室等场景,而不能用作可靠的任务队列。

更强大的队列:Delayed Queue(延迟队列)
很多业务场景下,我们需要的不是立即处理任务,而是希望任务在指定的时间之后被处理,订单下单后30分钟未支付自动取消,这就是延迟队列。
Redis没有直接叫做“延迟队列”的命令,但我们可以用有序集合(Sorted Set) 巧妙地实现它,根据“Redis实战经验”中的分享,具体做法是:
- 作为成员(member)。
- 将任务需要被执行的时间戳(
当前时间戳 + 30分钟)作为分值(score)。 - 用
ZADD命令将任务加入有序集合。ZADD delay_queue <执行的时间戳> "订单任务内容"。
我们启动一个额外的进程,定时地去扫描这个有序集合,这个进程的工作很简单:
- 使用
ZRANGEBYSCORE命令,查询出所有分值(即执行时间)小于等于当前时间戳的任务,这些就是到期该处理的任务了,命令像这样:ZRANGEBYSCORE delay_queue 0 <当前时间戳> WITHSCORES。 - 把这些任务取出来,扔进一个普通的任务队列(比如我们最开始说的用
LPUSH/BRPOP处理的my_queue)里,让真正的消费者去处理。 - 从有序集合
delay_queue中,用ZREM命令删除这些已经移交的任务,防止重复处理。
这个方法结合了有序集合的排序特性和列表队列的阻塞弹出特性,实现了非常灵活和可靠的延迟任务调度。
提升效率的几个技巧
- 批量操作:如果生产者一次性要产生大量任务,不要用循环一次次地
LPUSH,应该使用LPUSH key value1 value2 value3 ...一次性批量插入,或者使用管道(pipeline)技术,这能大幅减少网络往返次数,极大提升性能,参考资料“高性能Redis实战”中强调,批量操作是提升Redis写入效率最有效的手段之一。 - 谨慎选择数据结构:就像前面说的,普通队列用List,延迟队列用Sorted Set,要根据业务场景选择最合适的数据结构,不要用一个List去硬扛所有需求。
- ACK机制的考虑:专业的消息队列有ACK(确认)机制,确保任务被成功处理,Redis原生没有这个,如果你的任务非常重要,怕消费者在处理过程中崩溃导致任务丢失,可以考虑更复杂的方案,消费者用
BRPOPLPUSH命令(参考资料“Redis设计与实现”中有提及),在从主队列取出任务的同时,把它备份到一个“处理中”的列表,等处理完成后,再从“处理中”列表删除它,如果消费者崩溃,另一个守护进程可以把“处理中”列表的任务重新放回主队列,但这增加了复杂性,需要权衡。 - 监控队列长度:用
LLEN命令定期检查队列的长度,如果队列长度持续快速增长,说明消费者处理速度跟不上生产速度,可能是出现了性能瓶颈,需要及时扩容或排查问题。
用Redis做队列,核心在于理解其数据结构的特性和巧妙组合,从简单的LPUSH/BRPOP,到用Sorted Set实现延迟队列,它提供了一套强大而灵活的工具集,对于大多数中小型应用、对消息可靠性要求不是极端苛刻的场景,Redis队列是一个非常优秀且高效的选择。
本文由度秀梅于2026-01-08发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/76803.html
