736 lines
23 KiB
Markdown
736 lines
23 KiB
Markdown
|
|
# 消息队列设计:从原理到实战 (Interactive Guide to Message Queues)
|
|||
|
|
|
|||
|
|
> 💡 **学习指南**:本章节带你深入理解后端系统的"缓冲器"——消息队列。我们将从最基础的"为什么要用队列"讲起,一步步掌握消息队列的核心模式、可靠性保证、以及实战中的架构设计。
|
|||
|
|
|
|||
|
|
<MessageQueueDemo />
|
|||
|
|
|
|||
|
|
## 0. 引言:系统的"缓冲器"
|
|||
|
|
|
|||
|
|
你在淘宝买完东西,为什么点击"支付"后,不会立刻收到短信通知?
|
|||
|
|
你在抖音发了一条评论,为什么点赞数不是瞬间就增加?
|
|||
|
|
|
|||
|
|
这背后都有一个功臣:**消息队列 (Message Queue)**。
|
|||
|
|
|
|||
|
|
如果同步调用是"打电话",那消息队列就是"发微信"。
|
|||
|
|
打电话要求对方**立即响应**(同步),发微信可以等对方**稍后处理**(异步)。
|
|||
|
|
|
|||
|
|
### 0.1 为什么要用消息队列?
|
|||
|
|
|
|||
|
|
只有一个理由:**解耦和削峰**。
|
|||
|
|
|
|||
|
|
- **解耦**:A 不需要直接调用 B,把消息扔给队列就完事了。
|
|||
|
|
- _例子_:用户下单后,订单服务不需要直接调用库存、积分、通知服务,而是发一条"下单成功"消息。
|
|||
|
|
- **削峰**:把瞬间的高峰流量"摊平",避免系统被打爆。
|
|||
|
|
- _例子_:秒杀活动,1 秒内有 10 万个请求,但数据库只能处理 1000 个。队列把这 10 万个请求暂存起来,慢慢处理。
|
|||
|
|
|
|||
|
|
<PeakShavingDemo />
|
|||
|
|
|
|||
|
|
**关键点**:消息队列的本质是**异步通信**,通过把"立即执行"变成"稍后处理",提升系统的吞吐量和可用性。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 第一步:理解消息队列的核心概念
|
|||
|
|
|
|||
|
|
### 1.1 消息队列的三要素
|
|||
|
|
|
|||
|
|
1. **生产者 (Producer)**:发送消息的一方。
|
|||
|
|
- _例子_:订单服务(下单成功后发送消息)。
|
|||
|
|
2. **消息代理 (Broker)**:存储和转发消息的中介。
|
|||
|
|
- _例子_:RabbitMQ、Kafka、RocketMQ。
|
|||
|
|
3. **消费者 (Consumer)**:接收并处理消息的一方。
|
|||
|
|
- _例子_:库存服务(扣减库存)、短信服务(发送通知)。
|
|||
|
|
|
|||
|
|
<MessageQueueComponentsDemo />
|
|||
|
|
|
|||
|
|
### 1.2 消息模式 (Messaging Patterns)
|
|||
|
|
|
|||
|
|
#### 点对点 (Point-to-Point)
|
|||
|
|
|
|||
|
|
一条消息只能被**一个消费者**消费。
|
|||
|
|
|
|||
|
|
- _场景_:任务分配(如批量导入 Excel,分发给多个工作节点处理)。
|
|||
|
|
- _特点_:负载均衡,多个消费者竞争消费。
|
|||
|
|
|
|||
|
|
#### 发布订阅 (Pub/Sub)
|
|||
|
|
|
|||
|
|
一条消息可以被**多个消费者**同时消费。
|
|||
|
|
|
|||
|
|
- _场景_:事件通知(如用户注册后,同时发邮件、发短信、发放优惠券)。
|
|||
|
|
- _特点_:广播,每个订阅者都能收到完整消息。
|
|||
|
|
|
|||
|
|
<PointToPointVsPubSubDemo />
|
|||
|
|
|
|||
|
|
**关键点**:点对点是"任务分配",发布订阅是"事件通知"。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 主流消息队列对比
|
|||
|
|
|
|||
|
|
| 特性 | RabbitMQ | Kafka | RocketMQ | Redis Stream |
|
|||
|
|
| :----------- | :----------------- | :----------------- | :------------------ | :--------------- |
|
|||
|
|
| **定位** | 传统消息队列 | 分布式日志系统 | 电商级消息队列 | 轻量级队列 |
|
|||
|
|
| **吞吐量** | ~1 万/秒 | ~100 万/秒 | ~10 万/秒 | ~5 万/秒 |
|
|||
|
|
| **延迟** | 微秒级 | 毫秒级 | 毫秒级 | 毫秒级 |
|
|||
|
|
| **可靠性** | 高(持久化) | 高(多副本) | 高(同步/异步刷盘) | 中(AOF 持久化) |
|
|||
|
|
| **消息顺序** | 支持(单队列) | 支持(分区内) | 支持 | 支持 |
|
|||
|
|
| **消息回溯** | 不支持 | 支持 | 支持 | 支持 |
|
|||
|
|
| **学习曲线** | 中 | 高 | 高 | 低 |
|
|||
|
|
| **适用场景** | 传统业务、任务队列 | 日志收集、流式处理 | 电商、金融 | 小规模、简单队列 |
|
|||
|
|
|
|||
|
|
### 2.1 如何选择?
|
|||
|
|
|
|||
|
|
- **RabbitMQ**:
|
|||
|
|
- ✅ 需要复杂的路由规则(如根据订单类型分发到不同队列)。
|
|||
|
|
- ✅ 对延迟敏感(要求微秒级响应)。
|
|||
|
|
- ✅ 团队熟悉 AMQP 协议。
|
|||
|
|
|
|||
|
|
- **Kafka**:
|
|||
|
|
- ✅ 吞吐量极大(百万级 TPS)。
|
|||
|
|
- ✅ 需要消息回溯(重新消费历史数据)。
|
|||
|
|
- ✅ 大数据生态(Flink、Spark 集成)。
|
|||
|
|
|
|||
|
|
- **RocketMQ**:
|
|||
|
|
- ✅ 电商、交易场景(事务消息、顺序消息)。
|
|||
|
|
- ✅ 金融级可靠性要求。
|
|||
|
|
- ✅ 需要定时消息、延迟消息。
|
|||
|
|
|
|||
|
|
- **Redis Stream**:
|
|||
|
|
- ✅ 小团队、MVP 项目。
|
|||
|
|
- ✅ 已经有 Redis,不想引入新组件。
|
|||
|
|
- ⚠️ 不适合对可靠性要求极高的场景。
|
|||
|
|
|
|||
|
|
<MessageQueueComparisonDemo />
|
|||
|
|
|
|||
|
|
**关键点**:没有最好的消息队列,只有最适合的。初学者可以从 RabbitMQ 或 Redis Stream 入手。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 核心设计模式
|
|||
|
|
|
|||
|
|
### 3.1 异步处理 (Asynchronous Processing)
|
|||
|
|
|
|||
|
|
把同步调用改成异步,提升响应速度。
|
|||
|
|
|
|||
|
|
**场景**:用户注册流程
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 同步方式(总耗时 = 1500ms)
|
|||
|
|
def register(username, password):
|
|||
|
|
save_user(username, password) # 300ms
|
|||
|
|
send_email(username) # 500ms
|
|||
|
|
send_sms(username) # 400ms
|
|||
|
|
give_coupon(username) # 300ms
|
|||
|
|
return {"status": "success"}
|
|||
|
|
|
|||
|
|
# 异步方式(总耗时 = 300ms)
|
|||
|
|
def register(username, password):
|
|||
|
|
save_user(username, password) # 300ms
|
|||
|
|
|
|||
|
|
# 发送消息到队列,立即返回
|
|||
|
|
mq.publish("user.registered", {
|
|||
|
|
"username": username,
|
|||
|
|
"timestamp": time.time()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return {"status": "success"}
|
|||
|
|
|
|||
|
|
# 后台消费者(慢慢处理)
|
|||
|
|
def handle_user_registered(data):
|
|||
|
|
send_email(data["username"])
|
|||
|
|
send_sms(data["username"])
|
|||
|
|
give_coupon(data["username"])
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**效果**:接口响应时间从 1500ms 降到 300ms,用户体验大幅提升。
|
|||
|
|
|
|||
|
|
### 3.2 削峰填谷 (Peak Shaving)
|
|||
|
|
|
|||
|
|
用队列缓冲高峰流量。
|
|||
|
|
|
|||
|
|
**场景**:秒杀活动
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户请求 (10 万/秒)
|
|||
|
|
↓
|
|||
|
|
[网关层] 限流:只放行 1 万/秒
|
|||
|
|
↓
|
|||
|
|
[消息队列] 缓冲 9 万/秒
|
|||
|
|
↓
|
|||
|
|
[订单服务] 持续处理 1000/秒
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 生产者:秒杀接口
|
|||
|
|
def seckill(user_id, product_id):
|
|||
|
|
# 快速校验
|
|||
|
|
if not redis.is_available(product_id):
|
|||
|
|
return {"error": "已售罄"}
|
|||
|
|
|
|||
|
|
# 扔进队列,立即返回
|
|||
|
|
mq.publish("seckill.order", {
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"product_id": product_id,
|
|||
|
|
"timestamp": time.time()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return {"status": "排队中"}
|
|||
|
|
|
|||
|
|
# 消费者:后台处理订单
|
|||
|
|
def handle_seckill_order(data):
|
|||
|
|
user_id = data["user_id"]
|
|||
|
|
product_id = data["product_id"]
|
|||
|
|
|
|||
|
|
# 扣减库存(数据库可以慢慢处理)
|
|||
|
|
success = db.deduct_stock(product_id, user_id)
|
|||
|
|
if success:
|
|||
|
|
create_order(user_id, product_id)
|
|||
|
|
mq.publish("order.created", {...})
|
|||
|
|
else:
|
|||
|
|
mq.publish("order.failed", {...})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<SeckillQueueDemo />
|
|||
|
|
|
|||
|
|
**关键点**:用户不需要等待真实处理完成,只要"排队成功"就满足预期。
|
|||
|
|
|
|||
|
|
### 3.3 系统解耦 (Decoupling)
|
|||
|
|
|
|||
|
|
消除服务之间的直接依赖。
|
|||
|
|
|
|||
|
|
**场景**:订单系统 → 通知系统
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 紧耦合(不好)
|
|||
|
|
def create_order(user_id, product_id):
|
|||
|
|
order = db.create_order(user_id, product_id)
|
|||
|
|
|
|||
|
|
# 直接调用,如果通知服务挂了,订单就创建失败
|
|||
|
|
notification_service.send_sms(user_id, "订单创建成功")
|
|||
|
|
notification_service.send_email(user_id, "订单创建成功")
|
|||
|
|
|
|||
|
|
return order
|
|||
|
|
|
|||
|
|
# 松耦合(好)
|
|||
|
|
def create_order(user_id, product_id):
|
|||
|
|
order = db.create_order(user_id, product_id)
|
|||
|
|
|
|||
|
|
# 发送消息,不管通知服务是否在线
|
|||
|
|
mq.publish("order.created", {
|
|||
|
|
"order_id": order.id,
|
|||
|
|
"user_id": user_id
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return order
|
|||
|
|
|
|||
|
|
# 通知系统独立消费
|
|||
|
|
def handle_order_created(data):
|
|||
|
|
# 如果通知服务挂了,消息会暂存在队列里,等它恢复后再处理
|
|||
|
|
send_sms(data["user_id"], "订单创建成功")
|
|||
|
|
send_email(data["user_id"], "订单创建成功")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**好处**:
|
|||
|
|
|
|||
|
|
- 订单系统不依赖通知系统。
|
|||
|
|
- 可以随时增加新的消费者(如积分系统、大数据分析)。
|
|||
|
|
- 通知系统升级不影响订单系统。
|
|||
|
|
|
|||
|
|
<CouplingDemo />
|
|||
|
|
|
|||
|
|
### 3.4 数据分发 (Data Distribution)
|
|||
|
|
|
|||
|
|
一条消息分发给多个消费者。
|
|||
|
|
|
|||
|
|
**场景**:用户行为分析
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 用户点击了商品
|
|||
|
|
def on_product_click(user_id, product_id):
|
|||
|
|
mq.publish("user.action", {
|
|||
|
|
"type": "click",
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"product_id": product_id,
|
|||
|
|
"timestamp": time.time()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 消费者 1:推荐系统(更新用户画像)
|
|||
|
|
def update_user_profile(data):
|
|||
|
|
if data["type"] == "click":
|
|||
|
|
profile.add_interest(data["user_id"], data["product_id"])
|
|||
|
|
|
|||
|
|
# 消费者 2:实时统计(点击量计数)
|
|||
|
|
def increment_click_count(data):
|
|||
|
|
redis.incr(f"product:{data['product_id']}:clicks")
|
|||
|
|
|
|||
|
|
# 消费者 3:数据仓库(离线分析)
|
|||
|
|
def save_to_data_warehouse(data):
|
|||
|
|
warehouse.insert("user_actions", data)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<PubSubDemo />
|
|||
|
|
|
|||
|
|
**关键点**:发布订阅模式让数据可以"一写多读",每个系统各取所需。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 可靠性保证
|
|||
|
|
|
|||
|
|
### 4.1 消息不丢失
|
|||
|
|
|
|||
|
|
从三个维度保证:
|
|||
|
|
|
|||
|
|
#### 生产者不丢
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 确认机制 (ACK)
|
|||
|
|
try:
|
|||
|
|
mq.publish_with_confirm("order.created", order_data)
|
|||
|
|
# 收到 Broker 确认后才认为发送成功
|
|||
|
|
except Exception as e:
|
|||
|
|
# 发送失败,重试或记录日志
|
|||
|
|
log.error(f"发送失败: {e}")
|
|||
|
|
retry_later(order_data)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Broker 不丢
|
|||
|
|
|
|||
|
|
- **持久化**:消息写入磁盘,而不是只存在内存。
|
|||
|
|
- **多副本**:Kafka 的多副本机制,保证一台机器挂了数据不丢。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# Kafka 配置示例
|
|||
|
|
# acks=all: 所有副本都确认才算成功
|
|||
|
|
producer.send(
|
|||
|
|
topic="orders",
|
|||
|
|
value=order_data,
|
|||
|
|
acks="all" # 或 -1
|
|||
|
|
).get()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 消费者不丢
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 手动确认 (Manual ACK)
|
|||
|
|
def process_message(msg):
|
|||
|
|
try:
|
|||
|
|
# 处理业务逻辑
|
|||
|
|
handle_order(msg.body)
|
|||
|
|
|
|||
|
|
# 业务成功后才确认消息
|
|||
|
|
msg.ack()
|
|||
|
|
except Exception as e:
|
|||
|
|
# 业务失败,拒绝消息(会重新投递)
|
|||
|
|
msg.nack(requeue=True)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<MessageReliabilityDemo />
|
|||
|
|
|
|||
|
|
### 4.2 消息不重复
|
|||
|
|
|
|||
|
|
消息可能会重复投递(网络抖动、消费者重启),所以需要**幂等性**。
|
|||
|
|
|
|||
|
|
**什么是幂等性?**
|
|||
|
|
|
|||
|
|
- 执行一次和执行多次,结果相同。
|
|||
|
|
- _例子_:`SET x = 1` 是幂等的,`INCREMENT x` 不是。
|
|||
|
|
|
|||
|
|
**实现幂等性**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 方案 1: 数据库唯一约束
|
|||
|
|
def create_order(order_id, user_id, product_id):
|
|||
|
|
try:
|
|||
|
|
db.execute(
|
|||
|
|
"INSERT INTO orders (id, user_id, product_id) VALUES (?, ?, ?)",
|
|||
|
|
order_id, user_id, product_id
|
|||
|
|
)
|
|||
|
|
except DuplicateKeyError:
|
|||
|
|
# 订单已存在,直接返回(幂等)
|
|||
|
|
return get_order(order_id)
|
|||
|
|
|
|||
|
|
# 方案 2: Redis 去重表
|
|||
|
|
def process_message(msg):
|
|||
|
|
message_id = msg.id
|
|||
|
|
|
|||
|
|
# 检查是否已处理
|
|||
|
|
if redis.set(f"processed:{message_id}", "1", nx=True, ex=3600):
|
|||
|
|
# 第一次处理
|
|||
|
|
handle_business(msg.body)
|
|||
|
|
else:
|
|||
|
|
# 已处理过,跳过
|
|||
|
|
log.info(f"消息 {message_id} 已处理,跳过")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<IdempotenceDemo />
|
|||
|
|
|
|||
|
|
### 4.3 消息顺序性
|
|||
|
|
|
|||
|
|
某些场景需要保证消息的顺序(如订单状态:创建 → 支付 → 发货)。
|
|||
|
|
|
|||
|
|
**问题**:多个消费者并发消费,可能导致顺序错乱。
|
|||
|
|
|
|||
|
|
**解决方案**:
|
|||
|
|
|
|||
|
|
1. **单分区 / 单队列**:
|
|||
|
|
- 把需要有序的消息发到同一个分区/队列。
|
|||
|
|
- 一个分区只能被一个消费者消费。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# Kafka 示例:根据 user_id 分区
|
|||
|
|
producer.send(
|
|||
|
|
topic="orders",
|
|||
|
|
value=order_data,
|
|||
|
|
partition_key=order_data["user_id"] # 同一个用户的消息会进入同一个分区
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **内存排序**:
|
|||
|
|
- 消费者在内存中缓存消息,排序后再处理。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from collections import defaultdict
|
|||
|
|
|
|||
|
|
messages = defaultdict(list)
|
|||
|
|
|
|||
|
|
def process_message(msg):
|
|||
|
|
sequence_number = msg.sequence_number
|
|||
|
|
user_id = msg.user_id
|
|||
|
|
|
|||
|
|
# 缓存消息
|
|||
|
|
messages[user_id].append((sequence_number, msg))
|
|||
|
|
|
|||
|
|
# 排序并处理
|
|||
|
|
messages[user_id].sort()
|
|||
|
|
for seq, m in messages[user_id]:
|
|||
|
|
if not is_processed(m):
|
|||
|
|
handle_business(m)
|
|||
|
|
mark_processed(m)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<MessageOrderingDemo />
|
|||
|
|
|
|||
|
|
**关键点**:全局有序性能差,通常只需要**局部有序**(如单个用户的消息有序)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 高级特性
|
|||
|
|
|
|||
|
|
### 5.1 死信队列 (DLQ, Dead Letter Queue)
|
|||
|
|
|
|||
|
|
处理无法消费的消息。
|
|||
|
|
|
|||
|
|
**场景**:消息格式错误、业务逻辑失败(重试 N 次后仍失败)。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# RabbitMQ 示例
|
|||
|
|
queue_args = {
|
|||
|
|
"x-dead-letter-exchange": "dlx", # 死信交换机
|
|||
|
|
"x-dead-letter-routing-key": "dlq", # 死信队列
|
|||
|
|
"x-max-retries": 3 # 最大重试次数
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def process_message(msg):
|
|||
|
|
try:
|
|||
|
|
handle_business(msg.body)
|
|||
|
|
msg.ack()
|
|||
|
|
except Exception as e:
|
|||
|
|
msg.retries += 1
|
|||
|
|
if msg.retries >= 3:
|
|||
|
|
# 超过重试次数,发送到死信队列
|
|||
|
|
msg.reject(requeue=False)
|
|||
|
|
else:
|
|||
|
|
# 重新入队,稍后重试
|
|||
|
|
msg.nack(requeue=True)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**死信队列的作用**:
|
|||
|
|
|
|||
|
|
- 隔离异常消息,避免阻塞正常消息。
|
|||
|
|
- 保留失败消息,方便后续人工介入或分析。
|
|||
|
|
|
|||
|
|
<DeadLetterQueueDemo />
|
|||
|
|
|
|||
|
|
### 5.2 延迟消息 (Delayed Message)
|
|||
|
|
|
|||
|
|
指定时间后才消费消息。
|
|||
|
|
|
|||
|
|
**场景**:
|
|||
|
|
|
|||
|
|
- 订单 30 分钟后自动取消。
|
|||
|
|
- 定时提醒(明天 9 点提醒我开会)。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# RocketMQ 示例
|
|||
|
|
def send_delay_message(order_id, delay_level):
|
|||
|
|
# delay_level = 1 表示 1s, 2 表示 5s, ... 16 表示 2h
|
|||
|
|
producer.send(
|
|||
|
|
topic="order.cancel",
|
|||
|
|
body={"order_id": order_id},
|
|||
|
|
delay_level=14 # 15 分钟后取消
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Redis + 定时任务方案
|
|||
|
|
def schedule_order_cancellation(order_id, delay_seconds):
|
|||
|
|
redis.zadd(
|
|||
|
|
"order.cancellations",
|
|||
|
|
{order_id: time.time() + delay_seconds}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 定时扫描(每秒执行一次)
|
|||
|
|
def cancel_expired_orders():
|
|||
|
|
now = time.time()
|
|||
|
|
expired_orders = redis.zrangebyscore(
|
|||
|
|
"order.cancellations",
|
|||
|
|
0,
|
|||
|
|
now
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
for order_id in expired_orders:
|
|||
|
|
cancel_order(order_id)
|
|||
|
|
redis.zrem("order.cancellations", order_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<DelayedMessageDemo />
|
|||
|
|
|
|||
|
|
### 5.3 事务消息 (Transactional Message)
|
|||
|
|
|
|||
|
|
保证本地事务和消息发送的一致性。
|
|||
|
|
|
|||
|
|
**场景**:订单创建成功 → 发送"扣减库存"消息。
|
|||
|
|
|
|||
|
|
**问题**:订单创建了,但消息没发送成功(网络故障)。
|
|||
|
|
|
|||
|
|
**解决方案**(RocketMQ 事务消息):
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 1. 发送半消息(half message)
|
|||
|
|
producer.send_half_message(topic="order.deduct_stock", body=order_data)
|
|||
|
|
|
|||
|
|
# 2. 执行本地事务
|
|||
|
|
def execute_local_transaction(msg):
|
|||
|
|
try:
|
|||
|
|
create_order_in_db(msg.body)
|
|||
|
|
return COMMIT # 本地事务成功,提交消息
|
|||
|
|
except Exception as e:
|
|||
|
|
return ROLLBACK # 本地事务失败,回滚消息
|
|||
|
|
|
|||
|
|
# 3. RocketMQ 回查(如果长时间未收到确认)
|
|||
|
|
def check_local_transaction(msg):
|
|||
|
|
order = db.get_order(msg.body["order_id"])
|
|||
|
|
if order:
|
|||
|
|
return COMMIT # 订单存在,说明本地事务成功
|
|||
|
|
else:
|
|||
|
|
return ROLLBACK
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<TransactionMessageDemo />
|
|||
|
|
|
|||
|
|
**关键点**:事务消息保证了"要么都成功,要么都失败"。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 实战:设计一个秒杀系统
|
|||
|
|
|
|||
|
|
### 6.1 需求分析
|
|||
|
|
|
|||
|
|
- **高并发**:1 秒内有 10 万个请求。
|
|||
|
|
- **不超卖**:库存 100 个,不能卖出 101 个。
|
|||
|
|
- **用户体验**:立即返回"排队中",而不是让用户等待。
|
|||
|
|
|
|||
|
|
### 6.2 架构设计
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户请求
|
|||
|
|
↓
|
|||
|
|
[网关] 限流:只放行 1 万/秒
|
|||
|
|
↓
|
|||
|
|
[Redis] 预扣减库存(原子操作)
|
|||
|
|
↓ 成功
|
|||
|
|
[消息队列] 缓冲订单请求
|
|||
|
|
↓
|
|||
|
|
[订单服务] 慢慢创建订单
|
|||
|
|
↓
|
|||
|
|
[消息队列] 订单完成通知
|
|||
|
|
↓
|
|||
|
|
[通知服务] 发送短信/推送
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.3 代码实现
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 秒杀接口
|
|||
|
|
def seckill(user_id, product_id):
|
|||
|
|
# 1. Redis 预扣减库存(原子操作)
|
|||
|
|
stock_key = f"seckill:stock:{product_id}"
|
|||
|
|
success = redis.eval(
|
|||
|
|
"""
|
|||
|
|
if redis.call('get', KEYS[1]) > 0 then
|
|||
|
|
redis.call('decr', KEYS[1])
|
|||
|
|
return 1
|
|||
|
|
else
|
|||
|
|
return 0
|
|||
|
|
end
|
|||
|
|
""",
|
|||
|
|
1,
|
|||
|
|
stock_key
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not success:
|
|||
|
|
return {"error": "库存不足"}
|
|||
|
|
|
|||
|
|
# 2. 发送消息到队列
|
|||
|
|
mq.publish(
|
|||
|
|
"seckill.orders",
|
|||
|
|
{
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"product_id": product_id,
|
|||
|
|
"timestamp": time.time()
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 3. 立即返回
|
|||
|
|
return {"status": "排队中", "queue_position": get_queue_position()}
|
|||
|
|
|
|||
|
|
# 订单服务消费者
|
|||
|
|
def handle_seckill_order(data):
|
|||
|
|
user_id = data["user_id"]
|
|||
|
|
product_id = data["product_id"]
|
|||
|
|
|
|||
|
|
# 1. 创建订单(数据库)
|
|||
|
|
try:
|
|||
|
|
order = db.create_order(user_id, product_id, status="PROCESSING")
|
|||
|
|
except Exception as e:
|
|||
|
|
# 创建失败,恢复库存
|
|||
|
|
redis.incr(f"seckill:stock:{product_id}")
|
|||
|
|
log.error(f"创建订单失败: {e}")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 2. 发送"订单创建成功"消息
|
|||
|
|
mq.publish(
|
|||
|
|
"seckill.order.created",
|
|||
|
|
{
|
|||
|
|
"order_id": order.id,
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"product_id": product_id
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 通知服务消费者
|
|||
|
|
def handle_order_created(data):
|
|||
|
|
order_id = data["order_id"]
|
|||
|
|
user_id = data["user_id"]
|
|||
|
|
|
|||
|
|
# 1. 发送短信
|
|||
|
|
sms.send(user_id, f"您的订单 {order_id} 已创建成功")
|
|||
|
|
|
|||
|
|
# 2. 发送推送
|
|||
|
|
push.send(user_id, {"title": "订单创建成功", "body": "..."})
|
|||
|
|
|
|||
|
|
# 3. 更新订单状态
|
|||
|
|
db.update_order_status(order_id, "NOTIFIED")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.4 监控与告警
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 监控指标
|
|||
|
|
metrics = {
|
|||
|
|
"queue_length": mq.get_queue_length("seckill.orders"), # 队列长度
|
|||
|
|
"processing_speed": mq.get_processing_speed(), # 处理速度
|
|||
|
|
"success_rate": calculate_success_rate(), # 成功率
|
|||
|
|
"average_latency": calculate_average_latency(), # 平均延迟
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 告警规则
|
|||
|
|
if metrics["queue_length"] > 10000:
|
|||
|
|
alert("队列积压过多,请增加消费者")
|
|||
|
|
|
|||
|
|
if metrics["success_rate"] < 0.95:
|
|||
|
|
alert("成功率过低,请检查业务逻辑")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<SeckillSystemDemo />
|
|||
|
|
|
|||
|
|
**关键点**:
|
|||
|
|
|
|||
|
|
- 用 Redis 做第一道防线(快速拦截)。
|
|||
|
|
- 用消息队列做缓冲(削峰)。
|
|||
|
|
- 异步处理真正的业务逻辑。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 总结与学习路线
|
|||
|
|
|
|||
|
|
消息队列是后端系统的"核心基础设施",掌握它能让你的系统更可靠、更高效。
|
|||
|
|
|
|||
|
|
### 7.1 核心知识点
|
|||
|
|
|
|||
|
|
| 知识点 | 重要程度 | 难度 | 实战频率 |
|
|||
|
|
| :--------------------- | :--------- | :--- | :------- |
|
|||
|
|
| **点对点 / 发布订阅** | ⭐⭐⭐⭐⭐ | 低 | 极高 |
|
|||
|
|
| **削峰填谷** | ⭐⭐⭐⭐⭐ | 中 | 极高 |
|
|||
|
|
| **消息可靠性(不丢)** | ⭐⭐⭐⭐⭐ | 高 | 极高 |
|
|||
|
|
| **幂等性** | ⭐⭐⭐⭐⭐ | 中 | 极高 |
|
|||
|
|
| **消息顺序** | ⭐⭐⭐⭐ | 高 | 中 |
|
|||
|
|
| **死信队列** | ⭐⭐⭐⭐ | 低 | 高 |
|
|||
|
|
| **延迟消息** | ⭐⭐⭐⭐ | 中 | 中 |
|
|||
|
|
| **事务消息** | ⭐⭐⭐ | 高 | 低 |
|
|||
|
|
|
|||
|
|
### 7.2 学习路线
|
|||
|
|
|
|||
|
|
1. **入门**(1-2 天):
|
|||
|
|
- 理解消息队列的核心概念(生产者、消费者、Broker)。
|
|||
|
|
- 掌握点对点和发布订阅两种模式。
|
|||
|
|
- 用 Redis Stream 或 RabbitMQ 实现简单的异步任务。
|
|||
|
|
|
|||
|
|
2. **进阶**(1 周):
|
|||
|
|
- 实现削峰填谷(如秒杀系统)。
|
|||
|
|
- 保证消息可靠性(持久化、ACK、重试)。
|
|||
|
|
- 实现幂等性(唯一 ID、去重表)。
|
|||
|
|
|
|||
|
|
3. **实战**(2-4 周):
|
|||
|
|
- 设计一个完整的异步处理系统(订单、通知、积分)。
|
|||
|
|
- 接入监控,实时观测队列长度、消费速度。
|
|||
|
|
- 处理异常场景(死信队列、重试策略)。
|
|||
|
|
|
|||
|
|
4. **深入**(持续):
|
|||
|
|
- 学习 Kafka 的高可用架构(多副本、分区)。
|
|||
|
|
- 研究 RocketMQ 的事务消息。
|
|||
|
|
- 探索消息队列在流式处理中的应用(Flink、Spark)。
|
|||
|
|
|
|||
|
|
### 7.3 推荐资源
|
|||
|
|
|
|||
|
|
- **书籍**:
|
|||
|
|
- 《Kafka 权威指南》
|
|||
|
|
- 《RabbitMQ 实战指南》
|
|||
|
|
- **文章**:
|
|||
|
|
- RabbitMQ 官方文档: https://www.rabbitmq.com/getstarted.html
|
|||
|
|
- Kafka 官方文档: https://kafka.apache.org/documentation/
|
|||
|
|
- **工具**:
|
|||
|
|
- RabbitMQ Management Plugin (Web 管理界面)
|
|||
|
|
- Kafka Tool (Kafka 可视化)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 名词速查表 (Glossary)
|
|||
|
|
|
|||
|
|
| 名词 | 全称 | 解释 |
|
|||
|
|
| :---------------------- | :---------------- | :-------------------------------------------------------------------- |
|
|||
|
|
| **MQ** | Message Queue | **消息队列**。用于异步通信的中间件,实现生产者和消费者的解耦。 |
|
|||
|
|
| **Producer** | - | **生产者**。发送消息的一方。 |
|
|||
|
|
| **Consumer** | - | **消费者**。接收并处理消息的一方。 |
|
|||
|
|
| **Broker** | - | **消息代理**。存储和转发消息的服务端程序。 |
|
|||
|
|
| **Topic** | - | **主题**。消息的逻辑分类(如 "orders")。 |
|
|||
|
|
| **Queue** | - | **队列**。存储消息的物理容器。 |
|
|||
|
|
| **Partition** | - | **分区**。Kafka 的概念,一个 Topic 可以分成多个 Partition,提升并发。 |
|
|||
|
|
| **ACK** | Acknowledgment | **确认**。消费者处理完消息后,向 Broker 确认。 |
|
|||
|
|
| **Pub/Sub** | Publish/Subscribe | **发布订阅**。一种消息模式,一条消息可被多个消费者接收。 |
|
|||
|
|
| **P2P** | Point-to-Point | **点对点**。一种消息模式,一条消息只能被一个消费者接收。 |
|
|||
|
|
| **DLQ** | Dead Letter Queue | **死信队列**。存放无法消费的消息。 |
|
|||
|
|
| **Idempotence** | - | **幂等性**。多次执行结果相同。 |
|
|||
|
|
| **Throughput** | - | **吞吐量**。单位时间内处理的消息数量。 |
|
|||
|
|
| **Latency** | - | **延迟**。消息从发送到被接收的时间差。 |
|
|||
|
|
| **Persistence** | - | **持久化**。消息写入磁盘,而非仅存内存。 |
|
|||
|
|
| **Replication** | - | **副本**。为了高可用,消息被复制到多个节点。 |
|
|||
|
|
| **Transaction Message** | - | **事务消息**。保证本地事务和消息发送的一致性。 |
|