--- tags: - MQ create time: 2026-05-24 19:52 --- # MQ 基础概念 ## 概述 消息队列(Message Queue,简称 MQ)是一种异步通信机制,允许应用程序通过消息的生产和消费来实现解耦与协作。本文从生活类比出发,梳理核心术语、基本工作流程以及 MQ 的三大核心价值。 ## 正文 ### 什么是消息队列 想象一下你去寄快递:你把包裹交给快递柜,快递柜暂存包裹,收件人有空时再来取件。你不需要知道收件人什么时候在家,快递柜也不关心包裹里装的是什么——三方各司其职。 消息队列做的事情完全一样:**Producer(生产者)把消息投递到 Broker(消息服务器),Broker 暂存消息,Consumer(消费者)按自己的节奏拉取并处理。** 生产和消费在时间上解耦,双方不需要同时在线。 > [!question] > 生活中还有哪些场景天然就是"消息队列"的模式?食堂排队打饭算不算? ### 核心术语图解 | 术语 | 说明 | 类比 | |------|------|------| | **Producer** | 消息的发送方 | 寄件人 | | **Broker** | 消息服务器,负责存储和转发 | 快递柜 / 邮局 | | **Consumer** | 消息的接收方和处理方 | 收件人 | | **Topic** | 消息的逻辑分类,Producer 按 Topic 发送 | 快递柜的格口编号 | | **Queue** | 消息的物理存储单元 | 实际的格口 | | **Message** | 传输的数据单元 | 包裹 | | **Offset** | 消息在队列中的位置标识,用于消费进度追踪 | 快递单号 | | **Partition** | Topic 的物理分片,实现并行消费 | 同一排快递柜的不同列 | ### 基本工作流程 ```mermaid sequenceDiagram participant P as "Producer" participant B as "Broker" participant C as "Consumer" P->>B: 1. 发送消息到 Topic B->>B: 2. 持久化消息,分配 Offset C->>B: 3. 拉取消息(Pull)或接收推送(Push) B-->>C: 4. 返回消息 C->>C: 5. 处理业务逻辑 C->>B: 6. 提交消费确认(Ack) ``` > [!question] > 第 6 步的 Ack 确认机制为什么重要?如果没有 Ack 会怎样? ### MQ 的三大核心价值 #### 1. 解耦 没有 MQ 时,下单服务需要直接调用库存、积分、物流等多个下游服务,形成蜘蛛网般的依赖。引入 MQ 后,下单服务只需发一条消息,各下游自行订阅。 ```go // 解耦前: 下单服务直接依赖所有下游 func CreateOrder(order Order) { db.Save(order) inventorySvc.Deduct(order.ItemID, order.Qty) // 强依赖 pointsSvc.Add(order.UserID, order.Points) // 强依赖 logisticsSvc.Create(order) // 强依赖 // 新增通知服务?改代码、重新部署... } // 解耦后: 只管发消息,下游自己订阅 func CreateOrder(order Order) { db.Save(order) mq.Publish("order_created", order) // 一条消息,谁关心谁订阅 } ``` 解耦的本质是把**"我知道谁需要"变成"谁需要谁知道"**,新增下游服务时生产者代码零改动。 #### 2. 异步 有些操作不需要用户等待结果。比如注册后发欢迎邮件、生成报表等,可以把这些耗时操作异步化。 ```go // 同步: 用户等所有操作完成,响应慢 func Register(user User) { db.Save(user) // 50ms emailSvc.SendWelcome(user) // 200ms analyticsSvc.Track(user) // 100ms // 总耗时 350ms,用户等到怀疑人生 } // 异步: 用户只等核心操作,其余扔给 MQ func Register(user User) { db.Save(user) // 50ms mq.Publish("user_registered", user) // 5ms // 总耗时 55ms,邮件和埋点慢慢处理 } ``` #### 3. 削峰 秒杀场景下,瞬间流量可能打垮数据库。MQ 充当缓冲区,让下游按自己的处理能力消费消息。 ```go // 削峰前: 瞬时 1 万 QPS 直接打到数据库,DB 直接跪 func Seckill(userID, itemID string) { if stock > 0 { db.UpdateStock(itemID, -1) // DB 瞬间被打爆 } } // 削峰后: 先接住流量,慢慢消费 func Seckill(userID, itemID string) { mq.Publish("seckill_request", SeckillReq{userID, itemID}) // 瞬时接住 } // 消费者按 DB 承受能力匀速消费 func ConsumeSeckill() { for msg := range mq.Subscribe("seckill_request") { if stock > 0 { db.UpdateStock(msg.ItemID, -1) // 匀速处理,DB 稳如老狗 } } } ``` ### MQ vs 直接调用 | 维度 | 直接调用 | 消息队列 | |------|---------|---------| | 耦合度 | 强耦合,调用方需感知下游 | 松耦合,生产者不关心消费者 | | 响应时间 | 等待所有下游完成 | 只等核心链路,非核心异步处理 | | 可靠性 | 下游挂了直接失败 | Broker 持久化,支持重试 | | 吞吐能力 | 受限于最慢的下游 | 通过缓冲削峰,提升整体吞吐 | | 复杂度 | 简单直观 | 引入额外组件,运维成本上升 | | 一致性 | 容易保证强一致性 | 最终一致性,需要额外设计 | > [!question] > 如果不需要解耦和削峰,还有必要引入 MQ 吗? ## 关联笔记 - [[2-MQ-适用场景与选型原则]] - [[3-MQ-消息模型]]