用Redis搞队列,功能强大但其实细节还挺多的事情
- 问答
- 2026-01-02 21:49:59
- 2
主要基于《Redis实战》(Josiah L. Carlson著)一书中的相关章节、Redis官方文档关于列表(List)和发布订阅(Pub/Sub)的说明,以及网络上众多开发者分享的实际项目经验总结,比如关于消息确认、队列监控等“踩坑”记录。)
用Redis来实现消息队列,乍一听特别简单,不就是用个叫LPUSH的命令往一个列表左边塞消息,另一个程序用BRPOP命令从列表右边阻塞地取消息嘛,这确实能跑起来,而且性能贼快,感觉分分钟就能替代那些笨重的专业消息队列(比如RabbitMQ、Kafka),但真当你准备用到生产环境,处理一些关键业务时,一大堆细节问题就冒出来了,你会发现这事儿远不是LPUSH和BRPOP那么简单。
第一个大细节就是“消息可靠性”,专业消息队列核心功能之一是保证消息不丢,你用BRPOP把消息取出来,Redis服务器上这条消息就没了,万一你的工作程序(消费者)刚拿到消息,还没来得及处理就突然崩溃了,这条消息就彻底消失了,你可能会想,那我不用BRPOP,我用LRANGE看看消息,处理完了再LREM删掉行不行?且不说这很麻烦,更致命的是在“看”和“删”这个间隙,可能有两个消费者都看到了同一条消息,然后都去处理,造成重复消费。
那怎么办呢?Redis本身没提供现成的“确认”机制,于是人们想出了个办法,搞个“第二队列”,具体是:生产者用LPUSH把消息塞进待处理队列(比如叫queue:waiting),消费者不用BRPOP,而是用BRPOPLPUSH这个原子命令,它能把消息从queue:waiting同时移动到另一个进行中队列(比如叫queue:processing),这样,消息永远不会处于“不在任何列表”的悬空状态,消费者从queue:processing取到消息,安心处理,处理成功后,再显式地用LREM把消息从queue:processing里删除,如果消费者半路崩溃了,那条消息会一直留在queue:processing队列里,你需要再启动一个监控程序,定期去扫描queue:processing里滞留时间过长的消息,把它们重新塞回queue:waiting,让其他健康的消费者去重试,你看,就一个“不丢消息”的需求,你就得自己管理两个队列,还得写个守护程序来兜底。
第二个细节是“重复消费”和“幂等性”,即便你用上了上面的双队列法,也难免出问题,比如消费者处理成功了,但在最后删除queue:processing里的消息时网络闪断,删除失败,监控程序一看这消息待太久了,又把它放回queue:waiting,这就导致了同一条消息被处理了两次,你的业务逻辑必须设计成“幂等”的,简单说就是,同一个消息无论来多少次,处理的结果都应该是一样的,不是“给用户余额加10元”,而应该是“如果这个订单ID的加款记录不存在,则加10元”,这需要你在业务层面和数据结构上做文章,Redis可帮不了你。
第三个细节是“队列监控和管理”,专业队列有完善的管理界面,能看到有多少消息积压、多少消费者在线、处理速度如何,用Redis,你啥都得自己来,你想知道队列长度?得用LLEN命令去查,想知道有没有消费者卡住了?得自己去查queue:processing队列的长度和里面消息的存活时间,想动态调整消费者数量?得自己手动启动或停止进程,这些运维成本,在项目初期可能不明显,一旦系统复杂起来,会非常折腾人。
第四个细节是“扩展性和负载均衡”,多个消费者同时用BRPOP从一个队列取消息,Redis会保证每个消息只被一个消费者拿到,这本身是一种负载均衡,但如果消息有不同优先级怎么办?Redis的简单列表是先进先出的,不支持优先级,你可能得为每个优先级设置一个队列,然后消费者用BRPOP同时监听多个队列(比如BRPOP queue_high queue_normal 0),这样会优先处理高优先级队列,这又增加了复杂度,还有一种情况,如果某些消息处理起来特别耗时,可能会导致“队头阻塞”,前面一个慢消息卡着,后面快的也动不了,你可能需要根据处理时间的不同,拆分出不同的队列,由不同的消费者组来处理。
第五个细节是“发布订阅模式”的陷阱,有人觉得Redis的PUB/SUB(发布/订阅)模式更像消息队列,但它有个大问题:它是“fire and forget”(发射后不管)的,如果消费者当时不在线,或者连接断开了,那么它永远都收不到错过的消息,消息就像广播一样,发出就没了,没有任何持久化,所以PUB/SUB只适合用于对消息丢失不敏感的场景,比如实时推送在线用户的状态更新,绝对不能用于订单、支付这类关键任务。
总结下来就是,Redis确实能“凑合”出一个可用的队列,它的高性能和简单数据结构是巨大优势,非常适合对可靠性要求不是极高、允许极少量消息丢失的场景,比如发短信验证码、记录用户操作日志、清理缓存等,但一旦你的业务要求消息绝对不能丢、顺序要保证、要有重试机制,那么用Redis自制队列就会让你陷入各种自制逻辑的泥潭,最终你可能发现,维护这些自制组件的成本,已经超过了直接引入一个真正专业消息队列的成本,这其中的各种细节,都是前人一步步踩坑踩出来的经验。

本文由盈壮于2026-01-02发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/73320.html
