Files
cs-note/hhs/MQ/01-基础概念/1-MQ-基础概念.md
T
2026-05-24 20:58:10 +08:00

183 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- MQ
- 基础概念
- 异步
- 架构
create time: 2026-05-24 19:52
---
# MQ 基础概念
## 概述
消息队列(Message Queue,简称 MQ)是一种异步通信机制,允许应用程序通过消息的生产和消费来实现解耦与协作。本文从生活类比出发,梳理核心术语、Pull/Push 两种消费模型、基本工作流程、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 会怎样?
### Pull vs Push:两种消费模型
流程图第 3 步提到了"拉取"和"推送"两种方式,这其实是消息消费的两种根本模型:
| 维度 | **Pull(拉取)** | **Push(推送)** |
|------|-----------------|-----------------|
| 主动方 | Consumer 主动拉取 | Broker 主动推送 |
| 消费节奏 | Consumer 自己控制,想拉多少拉多少 | Broker 控制推送速率,Consumer 被动接收 |
| 适用场景 | 消费能力不均匀、需要精确控速 | 低延迟要求、实时性敏感 |
| 典型代表 | Kafka、RocketMQ(默认) | RabbitMQ、MQTT |
**简单理解**:Pull 是"我去超市买东西"(我决定什么时候买、买多少),Push 是"外卖送到家门口"(商家决定什么时候送)。
> [!tip]
> 大多数现代 MQ 默认采用 Pull 模式,因为 Consumer 更清楚自己的处理能力,天然具备背压(Backpressure)能力。详细的流控与背压机制见 [[31-MQ-背压与流控]]。
### 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 吗?
### MQ 引入的代价
MQ 不是银弹,引入它就意味着接受以下挑战:
1. **运维复杂度上升**:多了一个需要监控、扩容、容灾的中间件。Broker 挂了怎么办?消息积压怎么告警?这些都是新增的运维命题。
2. **消息丢失与重复**:网络不可靠,消息可能丢、可能重复投递。你需要设计 ACK 机制、幂等消费、甚至 Exactly-Once 语义——这些都不简单。
3. **系统可用性转移**:原本你的可用性取决于自己,现在还取决于 Broker。Broker 成了新的单点风险。
4. **调试链路变长**:同步调用出了问题,顺着调用栈就能排查。引入 MQ 后,消息可能在队列里"躺"了很久才被消费,排查问题的难度显著增加。
> [!warning]
> 在决定引入 MQ 之前,先问自己:**我真正需要的是解耦、异步还是削峰?** 如果答案都是"不太需要",直接调用可能是更简单可靠的选择。何时该用 MQ 见 [[2-MQ-适用场景与选型原则]]。
> [!tip]
> **一句话总结**:MQ 通过引入"中间人"角色,实现了生产者与消费者之间的解耦、异步和削峰,但代价是更高的系统复杂度和最终一致性。理解这个 trade-off,是用好 MQ 的第一步。
## 关联笔记
- [[2-MQ-适用场景与选型原则]]
- [[3-MQ-消息模型]]
- [[12-MQ-消息确认与持久化]] — Ack 机制的详细设计
- [[28-MQ-Competing-Consumers-模式]] — Partition 并行消费的实战模式