当前位置:首页 > 问答 > 正文

说实话,Redis队列用着挺方便,但那些隐藏的问题真让人头疼,不得不聊聊它的缺点和坑

说实话,Redis队列用着挺方便,但那些隐藏的问题真让人头疼,不得不聊聊它的缺点和坑,这东西就像一把双刃剑,上手快,感觉什么都能干,可真要扛起生产环境的大梁,一堆烦心事儿就冒出来了。

首先最让人提心吊胆的就是数据丢失这个老大难问题,Redis默认是把数据存在内存里的,速度是快,可万一服务器来个突然断电或者进程崩溃,内存里还没来及持久化到磁盘的数据可就全没了,你可能说可以开启AOF(Append Only File)或者RDB(快照)持久化啊,但这里就有坑了:如果为了数据安全,把持久化设置得太严格,比如每次写操作都同步刷盘,那性能就会暴跌,Redis引以为傲的高吞吐量就没了,可要是为了性能,把持久化设置得比较宽松,比如每分钟才做一次快照,那两次快照之间这一分钟的数据,就全在“裸奔”,风险极大,这种性能和可靠性的权衡,非常折磨人,很多刚开始用的人可能根本没意识到这个问题,等出了事才发现为时已晚。

Redis它本质上不是个专业的消息队列,它就像一个多功能的瑞士军刀,提供了List、Pub/Sub、Stream这些数据结构可以拿来模拟队列,但和专业消息队列(比如RabbitMQ、Kafka)比起来,很多消息队列该有的核心功能它是缺失或者很弱的,最典型的就是消息确认(Ack)机制,专业的消息队列,消费者拿到消息处理完后,必须显式地告诉队列“我处理完了,你可以把这条消息删了”,如果消费者处理失败或者没回应,消息会被重新投递给其他消费者,这个机制保证了消息至少被处理一次。

但用Redis的List做简单的队列时,用的是LPOPBRPOP这类命令,消息一旦被消费者读走,就从List里删除了,如果消费者在处理消息的过程中突然崩溃了,这条消息就彻底丢了,因为没有确认机制,虽然后来的Redis Stream数据类型补上了Ack功能,但它用起来比List复杂不少,而且整个生态和认知上,很多人还是习惯用List那种简单粗暴的方式,这就埋下了坑。

再说一个让人头疼的问题:消息堆积,Redis的内存是有限的,而且通常比硬盘贵得多,如果消息的生产速度远远大于消费速度,消息就会在Redis的队列里大量堆积,占满内存,一旦内存满了,Redis要么根据配置的淘汰策略开始删数据(可能把还没消费的消息给删了),要么就直接写不了新数据,整个服务不可用,而像Kafka这类专业的消息队列,消息是存在磁盘上的,并且有完善的清理和扩容策略,应对消息积压的能力要强得多,用Redis当队列,你就得时刻盯着内存使用率,提心吊胆,生怕哪个消费者出问题导致“雪崩”。

还有消费者组的功能相对简单,Redis Stream虽然提供了消费者组的概念,让多个消费者可以共同消费一个队列,分摊压力,但和Kafka那种设计精巧的消费者组模型相比,它的功能还是比较基础的,比如在分区、负载均衡、重平衡(Rebalance)等方面的灵活性和健壮性都有差距,当你的业务变得复杂,消费者数量多起来的时候,可能会觉得有点“不够用”。

队列监控和管理功能薄弱也是一个痛点,专业的消息中间件通常会提供非常丰富的管理控制台,可以清晰地看到每个队列的消息数量、消费延迟、消费者状态等等,而Redis主要靠命令行工具,查看队列信息需要敲一些命令,不够直观,虽然有一些第三方监控工具,但总归没有原生集成那么方便,出了问题,排查起来比较费劲。

容易让人产生误用和滥用,正因为Redis太方便了,一个命令就能搞个队列出来,很多开发者会不管什么场景都往上套,比如把一些非常重量级的任务、需要长时间处理的消息也塞进Redis队列,这会导致消费者处理时间过长,队列容易堵塞,更合适的方式可能是用Redis做轻量级的消息缓冲,而把耗时任务交给更专业的异步任务系统(如Celery),但这种边界需要开发者自己把握,工具本身不会提醒你。

Redis作为一个轻量级、高性能的内存数据库,在简单的、对消息丢失不那么敏感的延迟任务或消息缓冲场景下,确实非常方便快捷,但如果你需要构建一个高可靠、高可用、能应对各种复杂场景的消息系统,尤其是金融、交易等核心业务,那么Redis队列的这些“坑”和局限性就必须认真对待,它可能就不是最佳选择了,选择一个功能更完备的专业消息中间件,虽然初期成本高一点,但从长远来看,会帮你省去很多运维和踩坑的烦恼,说白了,用什么工具,还得看具体的业务场景和你对可靠性要求到底有多高。

说实话,Redis队列用着挺方便,但那些隐藏的问题真让人头疼,不得不聊聊它的缺点和坑