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

Redis用起来做消息队列,效率还能这么高,真是意想不到的好办法

知乎专栏《技术夜谈》)

记得那会儿我刚接手公司的新项目,团队里有个棘手的问题——用户下单后的通知系统老是卡壳,用MySQL发消息就像节假日挤高速公路,订单量稍微一大,系统就喘不过气来,延迟得厉害,运维同事天天被业务部门催,头发都愁白了几根。

后来我们组里有个不爱说话的后端小哥,在一次技术复盘会上小声提议:“要不…试试用Redis做个简单的消息队列?”这话一出,会议室安静了几秒,Redis不是缓存吗?能当消息队列用?我当时心里也直打鼓,但看着手里密密麻麻的报警记录,心想死马当活马医吧。

Redis用起来做消息队列,效率还能这么高,真是意想不到的好办法

结果这一试,真香定律虽迟但到。

先说说我们是怎么捣鼓的。 其实原理简单得让人想笑,就用Redis里那个叫List的数据结构,生产者(比如下单服务)用LPUSH命令把消息塞进列表左边,消费者(通知服务)用BRPOP从右边慢慢取消息,这个命令最妙的是,如果列表没消息,它会一直阻塞等着,根本不需要傻乎乎地不停轮询。 来源:阮一峰博客《Redis用于消息队列》) 我记得特别清楚,改造上线那个晚上,我们盯着监控屏幕,手心里都是汗,原先MySQL处理1000条通知要磨蹭三四分钟,现在Redis唰唰唰几十秒就搞定了,最直观的感受是——报警群居然安静了,安静得让人有点不习惯,以前每秒处理十几条消息就脸红脖子粗的系统,现在轻轻松松扛住每秒上千条。

Redis用起来做消息队列,效率还能这么高,真是意想不到的好办法

为什么这个小窍门这么灵? 我后来琢磨,关键是Redis把所有数据都放在内存里操作,速度本来就是硬盘数据库的几十上百倍,而且BRPOP这种阻塞式读取,相当于消费者在“睡觉等活干”,CPU资源几乎零消耗,对比之前MySQL那种“隔几秒查一次数据库”的笨办法,简直是智能机和大哥大的区别。 来源:GitHub上某个开源项目Wiki) 不过世上没有完美方案,我们也踩过坑,有次机房网络抖动,消费者服务重启后,发现少了几条消息——原来BRPOP拿到消息后如果程序崩溃,消息就彻底丢了,后来我们学了乖,改用RPOPLPUSH命令:消费者从主列表取消息时,自动把消息备份到个“处理中”的列表,等真正处理成功再删掉,万一消费者挂掉,重启后还能从“处理中”列表找回消息。

还有个惊喜发现是Redis的Pub/Sub功能,像那种需要同时给多个服务广播的消息(比如订单成功后要同时通知客服系统、积分系统、推荐系统),用Pub/Sub一条publish命令,所有订阅的服务都能收到,省得我们一个个去投递。

当然啦,这套方案不适合所有场景,比如有次业务方想搞消息顺序保证,我们就头疼了半天——Redis队列虽然能大体保持先进先出,但多个消费者并发取消息时,顺序可能会乱,后来只能让需要严格顺序的消息都塞到同一个队列,用单消费者来处理。 来源:Stack Overflow上一个高赞回答) 现在三年过去了,虽然公司后来引入了专业的RabbitMQ和Kafka,但Redis队列依然在好几个子系统里活得很好,特别是那些对延迟零容忍、业务逻辑简单的场景,比如实时计数、秒杀状态同步,Redis还是首选,每次看到监控图上那条平稳的直线,我就会想起那个深夜——一个看似不务正业的用法,居然解决了让我们头疼半年的难题。

有时候技术就是这样,最好的工具不一定是那些功能最全面的,而是刚好能四两拨千斤的,就像家里修东西,专业电钻固然好,但关键时刻,一把顺手的老虎钳可能更解决问题。