面试官:你用过消息队列吗?

1,208 阅读11分钟

“突袭”的秒杀风波

零点刚过,程序员小狗正准备睡觉,突然被 Oncall 手机的警报声惊醒:「服务器响应超时!数据库连接池耗尽!」小狗一脸懵地登录系统,一看后台日志,瞬间头皮发麻——订单请求像海啸一样涌入,服务器直接崩了!直到打开运营群,小狗才看到一条“精心埋伏”的信息:「今晚零点限时秒杀,商品秒杀价 1 元,冲量啦!」小狗抱头无语:“运营怎么都不提前通知!这么大的流量直接把系统打爆了!”小狗被拉进了事故处理群,客服部门反馈了各种问题:库存超卖、支付失败、订单卡住排队,客服系统瞬间被挤爆。面对产品经理和运营同学的狂轰滥炸,小狗崩溃了。

面对这场灾难,小狗灵机一动:「如果能先把请求排个队,服务器压力就不会这么大了!」。于是,在事故发生的一个月后,他迅速和leader设计了方案,准备上线RocketMQ:

  1. 用户下单请求直接进入消息队列,而不是直接打到数据库。
  2. 后台消费者按服务器能力有节奏地处理消息,保证库存扣减有序。

结果立竿见影:

  • 系统不再被流量洪峰打垮,稳定运行。
  • 用户下单顺畅,排队也井然有序,体验提升。
  • 库存准确扣减,运营活动圆满完成。

疲惫的小狗坐在窗台,写着事故复盘,一边感叹道:RocketMQ,真是流量洪峰的削峰神器。

消息队列的作用

以上这个故事仅是一个简化后的段子,目的是帮助没有接触过消息队列的同学更生动地感受 MQ 在分布式系统中的价值。像故事中描述的这种「瞬时高并发」场景,消息队列的作用非常关键,而削峰填谷正是它的核心功能之一。

在电商场景中,MQ 常见的用途包括:

  • 削峰填谷:当用户下单请求量激增时,将所有请求写入队列,后台服务按能力处理,避免瞬时压力导致系统崩溃。
  • 解耦 异步:下单后立即返回给用户确认信息,而支付、库存扣减等后续操作通过 MQ 异步完成,提升用户体验。
  • 分布式 事务协调:在复杂的多模块调用中,通过 MQ 保证各模块操作的一致性,避免数据不一致的问题。

MQ 就像是分布式系统中的“润滑剂”,既能缓解系统压力,又能让各模块高效协作。不管是削峰填谷、解耦异步,还是事务协调,MQ 的存在让系统的运转更加平稳和高效。

RocketMQ 是阿里巴巴开源的分布式消息中间件(rocketmq.apache.org/zh/docs/),诞…

接下来的一系列文章,将围绕着消息队列和RocketMQ的一系列主题展开,比如消息模型、RocketMQ 的主从架构与容灾机制、消息的可靠性保证、事务消息、RocketMQ 支持顺序消息的机制等。这些话题都是聚焦着分布式相关技术,所以实践性质非常强。在面试过程中,也非常少关注RocketMQ本身实现的原理细节,主要围绕着RocketMQ的应用场景来考察候选人的架构设计水平和实践经验。所以后续文章也会以实践应用场景为核心,无门槛带大家入门业务场景中分布式系统的开发。

其实,主流的MQ组件还有很多,如RabbitMQKafka 等,选择 RocketMQ 作为具体的切入点,更因为其独特的特性和丰富的实践经验,尤其是在阿里巴巴的大规模分布式场景下展现了强大的能力,同时主要是为了展示可以真实运行的代码,文章所阐述的基本的思想可以迁移到其他MQ的使用。后续如果有机会,也会给出MQ选型的方案和其他MQ的介绍。

消息队列的基本概念

首先介绍下,消息队列的架构通常由三个核心组件组成:

  1. 生产者(Producer) 生产者负责生成消息并将其发送到消息队列中。可以是任何需要发布消息的应用程序或服务。
  2. 队列(Queue) 队列是消息的存储介质,用于存放生产者发送的消息。消息在队列中按照先进先出(FIFO)的顺序存储,消费者从队列中获取并处理这些消息。
  3. 消费者(Consumer) 消费者从队列中获取消息并进行处理。它可以是独立的服务或应用程序,通常与生产者没有直接连接,消息的获取和处理是异步的。

whiteboard_exported_image.png

我们以一个生动形象的例子来理解消息队列中的核心的参与者。假设程序员小狗去一家饭馆吃饭,点菜后,服务员需要马上把点菜单交给后厨的手中,而厨师必须立刻开始做菜。这个流程看似直接,但存在很多问题:

  1. 等待问题 如果后厨正在忙(消费者压力大)没有办法接住点菜单,服务员只能在旁边等着(生产者被阻塞),这样后续的客人点菜就得排队。
  2. 高峰期问题 如果是用餐高峰期,后厨完全处理不过来。但是服务员必须要将已经有的点菜单交到后厨手中呀,不能继续为新到的用户点单了,结果整个餐厅效率下降,影响客户体验。
  3. 耦合 问题 服务员和后厨之间必须实时对接。一旦厨师短暂离开或厨房设备出问题,整个餐厅的运转就会中断。

为了优化流程,餐厅设置了一个点餐单中转台( 消息队列 ,服务员将订单放到中转台后,后厨根据自己的处理能力从中转台领取订单。这样,流程变得更加高效:

  1. 服务员(生产者)将顾客的订单放到中转台(消息队列),就可以马上继续接待下一个顾客,无需等待厨师(消费者)。
  2. 厨师根据自己的速度,从中转台上领取订单并制作菜品。如果厨师很忙,中转台可以暂时堆积更多的订单。如果出现高峰期,中转台起到缓冲作用,服务员可以继续接单,而不是等后厨完成一个点菜单再提交下一个。

从上述的例子中,可以体会到队列作为缓冲区,确保消息的可靠存储,并按顺序分发给消费者。生产者和消费者之间是解耦的,生产者只负责发送消息,而不关心消费者是否已处理。通过这种方式,生产者可以快速生成消息而不需要等待消费者完成处理,消费者也可以根据自己的速度处理消息。这种机制在分布式系统中尤其重要,可以有效地应对高并发、降低耦合并提高系统的整体的稳定性。

消息队列的消费模型

消息队列通常有两种主要的消费模型:

  • 在点对点(Point-to-Point)模型中,每个消息只能被一个消费者消费。在这种模型下,队列中的每条消息只有一个消费者会接收到并进行处理。也就是说,消费者之间是互斥的,消息被取出后就会被删除。
  • 在发布/订阅(Publish/Subscribe)模型中,每条消息可以被多个消费者消费。也就是说,消息被发布到一个主题(Topic)上,所有订阅了该主题的消费者都会收到这条消息并进行处理。每个消费者会独立消费消息。

为了帮助大家更直观地理解消息队列的两种消费模型,我们继续用餐厅点餐的例子来说明。

随着小狗经常去的这家餐厅生意越来越好,这家餐厅招来了很多个厨师(点餐单的生产者)和多个服务员(点餐单的消费者),但是只有一个通用点餐单中转台,所有厨师都可以处理任何点餐单(消息),每张点餐单只能由一个厨师领取并处理,其他厨师就不会处理同样的订单。这就是典型的点对点(Point-to-Point)模型。

随着餐厅的生意越来越火爆,菜品种类越来越复杂。某天小狗去吃饭,点了一个非常有名的川菜「麻婆豆腐」,这道菜是非常考验厨师经验和能力的。小狗吃到的时候,直接吐了出来,“汪汪,这也太难吃了!!”。服务员就赶紧来和小狗解释道,小狗这天的点餐单被一个之前是做凉菜的厨师拿到了,炒菜还没有经验。聪明的小狗想了想说,“你们为啥不让做热菜的师傅专门做菜单里的热菜呀,这样就可与你保证品质了呀!”服务员说,“不行呀,现在点菜单只能被一个厨师给拿到”。小狗给服务员说了自己的方案:

  • 把厨师分成两拨,热菜小队和凉菜小队
  • 两队厨师在做菜的时候,只做属于自己小队的菜
  • 但是额外注意,一个点餐单需要同时被热菜小队和凉菜小队各处理一次,才算完成

小狗给出的方案就是发布/订阅(Publish/Subscribe)模型。这时候凉菜和热菜可以同时制作,不再互相影响,并且厨师只专注于自己的领域,处理速度更快质量更高。从餐厅整体运作来看,整体的效率和质量都变高了,职责分工也更明确了。

暂时无法在飞书文档外展示此内容

消息队列的消费模型经常出现在面试中。面试官会通过消费模型来考察候选人对于系统稳定性、拓展性等的较为宏观的理解。本部分需要掌握的是两种模式的特点和适合场景:

点对点模型:所有消费者竞争获取同一个订单,适合单一任务类型或对消息分工要求较低的场景。

发布/订阅模型:生产者将消息发布到特定的主题,多个消费者可以订阅不同的消息类别,适合任务类型复杂、需要专业分工的场景。

知识点总结

本文主要以较为简单和轻松的方式,来向大家初步入门下消息队列相关的知识,给出的例子也希望能够帮助到大家理解。最后为了加深对于消息队列核心概念的印象,总结下消息队列核心的知识点(其实就是面试题)。

消息队列 的基本概念: 消息队列是一个用于存储和传递消息的中间组件,它实现了生产者与消费者之间的解耦。

消息队列的作用:

(1)解耦:生产者和消费者通过消息队列作为中间件互相独立,降低系统耦合度。

(2)异步处理:允许生产者快速完成任务,不必等待消费者实时响应。

(3)削峰填谷:通过缓冲区的作用,平滑流量波动,避免系统因瞬时高并发而崩溃。

在消息队列中,消息队列通常有两种主要的消费模型,点对点(Point-to-Point)模型的核心特征是一个消息只能被一个消费者处理,发布/订阅(Publish/Subscribe)模型的核心特征是消息可以被多个消费者订阅并并行处理。

后续文章会以更加专业的角度(尽量做到生动易懂)来介绍消息队列相关的知识,当然知识密度也会更加大。