用Redis搞个轻量级分布式队列,消费均衡这事儿其实没那么难
- 问答
- 2026-01-07 12:43:00
- 3
(引用来源:博客文章“用Redis搞个轻量级分布式队列,消费均衡这事儿其实没那么难”)
用Redis搞个轻量级分布式队列,消费均衡这事儿其实没那么难,咱们今天就来聊聊这个,不用那些高大上的专业术语,就用大白话把这事儿说明白。
为啥要用Redis做队列呢?很简单,因为它快,而且数据结构丰富,很多时候,我们的系统没那么复杂,不想引入像RabbitMQ、Kafka那样重量级的消息中间件,维护起来也麻烦,Redis就像一个瑞士军刀,顺手拿来就能用,实现一个简单的队列功能绰绰有余。
最基础的队列,我们用Redis的List结构就能搞定,生产者用LPUSH命令,把任务从左边塞进一个列表里;消费者用RPOP命令,从右边把任务取出来执行,这就是一个标准的先进先出队列,简单直接。
问题马上就来了,这个简单的模型有个大毛病:消费者怎么知道队列里来新任务了?笨办法是让消费者不停地用RPOP去问Redis:“有任务吗?有任务吗?”这叫做“轮询”,如果任务来得不频繁,大部分问询都是白问,浪费网络资源,也浪费Redis的CPU。

那有没有聪明点的办法呢?有!Redis提供了一个阻塞版本的命令,叫BRPOP,消费者可以执行BRPOP myqueue 0,这个“0”代表无限等待,这时候,消费者这个连接就会卡在那里睡觉,直到myqueue这个列表里有新任务进来,Redis才会唤醒它,并把任务直接返回给它,这样消费者就不用傻乎乎地不停追问了,省心又省力,这就是所谓的“阻塞拉取”,解决了通知的问题。
好,单个消费者的问题解决了,那如果任务很多,一个消费者处理不过来,我们需要多个消费者一起干活,这就叫“分布式消费”,目标是把队列里的任务均匀地分给这些消费者,避免有的忙死,有的闲死,这就是“消费均衡”。
用BRPOP能实现多消费者吗?乍一看可以,我们启动三个消费者进程,都去执行BRPOP myqueue 0,当生产者塞进一个任务时,Redis会保证只有一个正在阻塞等待的消费者能拿到这个任务,看起来挺均衡的,对吧?
但这里藏着一个坑,如果任务有“优先级”或者“类别”的区别怎么办?我们有两种任务:一种是发邮件的紧急任务,一种是生成报表的普通任务,我们希望多个消费者都能同时处理这两种任务,但如果只用BRPOP,并且把所有任务都塞进同一个队列,那就无法区分优先级了。

更灵活的做法是使用Redis的“发布订阅”模式吗?不完全是,Pub/Sub模式是广播性质的,一条消息会发给所有订阅者,不能实现“一个任务只被一个消费者处理”的队列语义,它适合聊天室、消息通知这种场景,不适合任务队列。
那该怎么办呢?这里就要用到Redis另一个强大的数据结构——有序集合,我们可以用有序集合来实现一个更智能的队列。
思路是这样的:
- 生产者不直接往列表里塞任务了,而是把任务作为一个成员(Member)加到有序集合里,同时给它一个分数(Score),这个分数就用当前的时间戳,这样,任务就按照投递时间排好队了。
- 消费者怎么取呢?它使用
ZRANGEBYSCORE命令,带上一个很小的分数范围(比如从0到当前时间戳),意思是“把那些最早到期该处理的任务给我”,再用ZREMRANGEBYSCORE命令把这些任务从集合里删掉,为了防止多个消费者抢到同一个任务,我们可以用Redis的Lua脚本把这两个操作原子性地一起执行。
但这还不是最优解,因为一次可能取出一批任务,处理起来可能不均匀。

更常见的、也是很多开源项目在用的方案,是结合List和Set,简单说,
- 待处理的任务还是放在一个List里。
- 当一个消费者用
BRPOP拿到一个任务后,它并不立即把这个任务从Redis里彻底删除,而是同时把它放入一个“进行中集合”,这个集合记录了哪些任务正在被处理。 - 消费者处理完任务后,再从这个“进行中集合”里把任务删除。
这样做最大的好处是避免了“任务丢失”,万一某个消费者拿到任务后,还没来得及处理就崩溃了,这个任务会一直留在“进行中集合”里,我们可以启动一个后台监控进程,定期去检查这个集合,如果发现某个任务在里面待的时间太长了(比如超过了合理的处理时间),就认为处理它的消费者出问题了,于是把这个任务重新放回到待处理队列中,让其他健康的消费者去重试。
你看,这样一来,我们既实现了多个消费者的负载均衡(大家都能用BRPOP去抢任务),又保证了任务不会因为某个消费者崩溃而丢失,实现了基本的可靠性,这个“待处理队列” + “进行中集合”的模式,就是实现消费均衡和可靠性的核心思想。
这还是一个比较基础的模型,真正在生产环境中用,可能还需要考虑任务优先级、失败重试次数、延迟任务(比如半小时后再执行)等更复杂的需求,但这些高级功能,基本上都是在这个核心思想上进行扩展的,比如延迟任务,我们可以用另一个有序集合来存放,分数设为要执行的时间点,然后有个后台进程定时去扫描这个有序集合,把到期的任务转移到待处理队列中。
用Redis搞个轻量级的分布式队列,关键点就几个:
- 用List做队列主干,实现先进先出。
- 用
BRPOP实现阻塞等待,避免无效轮询。 - 用Set或ZSet做辅助结构,来跟踪处理中的任务,防止任务丢失和实现重试。
- 核心目标是让多个消费者都能从同一个队列点安全地获取任务,谁有空谁干活,自然就均衡了。
这事儿真没那么神秘,理解了这几个基本组件和它们之间的配合,你就能根据自己的业务需求,搭出一个简单可靠的消息队列来。
本文由召安青于2026-01-07发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/76198.html
