Chapter 06

消息队列与异步处理

解耦、削峰、异步。消息队列是构建弹性系统的基石。
从 Kafka 核心概念到 Saga 分布式事务,掌握异步架构的精髓。

同步 vs 异步:为什么需要解耦

同步架构(紧耦合): 用户下单 │ ├──▶ 订单服务(~10ms) │ │ │ ├──▶ 库存服务(~20ms) │ │ └── 扣减库存 │ ├──▶ 支付服务(~200ms) │ │ └── 处理支付 │ ├──▶ 通知服务(~50ms) │ │ └── 发短信 │ └──▶ 积分服务(~30ms) │ └── 加积分 │ 用户等待:10+20+200+50+30 = 310ms 才能收到响应! 任何一个服务超时 → 整个下单请求失败 ────────────────────────────────────────────── 异步架构(消息队列解耦): 用户下单 │ ├──▶ 订单服务(~10ms) │ └── 写订单到 DB │ └── 发消息到 MQ → 立即返回!用户响应 10ms │ │ 消息队列(Kafka) │ │ │ ┌─────────┼─────────┐ │ ▼ ▼ ▼ │ 库存服务 支付服务 通知服务 │ (异步处理) (异步处理) (异步处理) 优势: ① 用户响应快(10ms vs 310ms) ② 服务解耦(通知服务宕机不影响下单) ③ 削峰填谷(突发流量由MQ缓冲) ④ 重试容易(消息可以重新消费)

Kafka 核心概念

Apache Kafka 是目前最主流的分布式消息队列,最初由 LinkedIn 开发,每天处理超过 7 万亿条消息。

Kafka 架构全景: Producer Kafka Cluster Consumer ┌──────────┐ ┌─────────────────────────────┐ ┌──────────────┐ │ Service │ │ Topic: order-events │ │ Inventory │ │ A │───▶│ ┌──────┐ ┌──────┐ ┌──────┐ │───▶│ Service │ │ │ │ │ P-0 │ │ P-1 │ │ P-2 │ │ │ (Group A) │ └──────────┘ │ └──────┘ └──────┘ └──────┘ │ └──────────────┘ ┌──────────┐ │ │ ┌──────────────┐ │ Service │ │ Topic: payment-events │ │ Notification │ │ B │───▶│ ┌──────┐ ┌──────┐ │───▶│ Service │ │ │ │ │ P-0 │ │ P-1 │ │ │ (Group A) │ └──────────┘ │ └──────┘ └──────┘ │ └──────────────┘ └─────────────────────────────┘ ┌──────────────┐ │ Analytics │ │ (Group B) │ └──────────────┘ 同一 Topic 可以被多个 Consumer Group 独立消费!

核心概念详解

Topic(主题)
消息的分类,类似数据库的表。Producer 向 Topic 写入消息,Consumer 从 Topic 读取消息。
Partition(分区)
Topic 的物理分片。每个 Partition 是一个有序的、不可变的消息序列(追加写入)。Partition 是 Kafka 并行处理的基本单位。
Offset(偏移量)
Partition 中每条消息的唯一序号(从 0 开始递增)。Consumer 通过 Offset 追踪消费进度,可以回溯重放历史消息。
Consumer Group
一组共同消费同一 Topic 的消费者。每个 Partition 只能被同一 Group 中的一个 Consumer 消费(并行消费)。不同 Group 独立消费,互不影响。
Broker(代理)
Kafka 集群中的一个节点,负责存储 Partition 数据和处理读写请求。
Replication Factor
每个 Partition 的副本数。RF=3 表示数据在 3 个 Broker 上各有一份,2 个节点宕机仍不丢数据。
Partition 内部结构(有序 + 可回放): Partition 0: ┌──────┬──────┬──────┬──────┬──────┬──────┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ ← Offset │order │order │order │order │order │order │ │#1001 │#1002 │#1003 │#1004 │#1005 │#1006 │ └──────┴──────┴──────┴──────┴──────┴──────┘ ↑ Consumer A 当前消费到这里(Offset=3) 可以 Seek 回到 Offset=0 重新消费(回溯重放!) Consumer Group 并行消费(3个Partition,3个Consumer): Partition 0 ──▶ Consumer 1(并行) Partition 1 ──▶ Consumer 2(并行) Partition 2 ──▶ Consumer 3(并行) 吞吐量 = 单 Consumer 吞吐 × Partition 数量

消息幂等性

在分布式系统中,消息投递存在三种语义,每种都有代价:

At-Most-Once
至多一次
消息可能丢失,但绝不重复。发出去不确认,出错就丢弃。适合:日志、监控数据(丢一条无所谓)。
At-Least-Once
至少一次
消息一定会被投递,但可能重复。失败就重试,直到收到确认。这是 Kafka 的默认行为。
Exactly-Once
恰好一次
消息恰好被处理一次。实现最复杂,需要幂等 Producer + 事务。Kafka 0.11+ 原生支持(同一集群内)。

业务层实现幂等性

场景:订单支付消息可能被重复消费 方案一:幂等 Key + 去重表 ┌─────────────────────────────────────────┐ │ 消费消息前,先查去重表 │ │ │ │ SELECT id FROM processed_messages │ │ WHERE message_id = 'msg-uuid-123' │ │ │ │ IF 存在 → 跳过(已处理过) │ │ IF 不存在 → 处理 + INSERT 去重记录 │ └─────────────────────────────────────────┘ 方案二:数据库唯一约束 ┌─────────────────────────────────────────┐ │ INSERT INTO payment_records │ │ (order_id, amount, status) │ │ VALUES (123, 100.00, 'PAID') │ │ ON CONFLICT (order_id) DO NOTHING │ │ │ │ 重复消费 → INSERT 失败 → 安全忽略 │ └─────────────────────────────────────────┘ 方案三:乐观锁版本号 ┌─────────────────────────────────────────┐ │ UPDATE orders SET status='PAID', │ │ version=version+1 │ │ WHERE id=123 AND version=5 │ │ │ │ 只有当前版本匹配才更新,重复处理无效 │ └─────────────────────────────────────────┘

事件驱动架构(EDA)

以「电商下单」为例的事件驱动流程: ┌──────────┐ │ Order │──发布──▶ OrderCreated { orderId, userId, items, total } │ Service │ └──────────┘ │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │ Inventory │ │ Payment │ │ Notification │ │ Service │ │ Service │ │ Service │ │ │ │ │ │ │ │ 订阅事件 │ │ 订阅事件 │ │ 订阅事件 │ │ 扣减库存 │ │ 发起支付 │ │ 发邮件/短信 │ └────────────┘ └──────────────┘ └────────────────┘ 事件驱动的好处: ① Publisher 不需要知道 Subscriber 是谁(解耦) ② 新增服务只需订阅 Topic,不修改原有代码(开闭原则) ③ 审计日志天然完整(所有事件都记录在 Kafka 中)

Saga 模式:分布式事务

微服务场景下,一个业务操作跨越多个服务,每个服务有独立的数据库,无法使用传统的 2PC(两阶段提交)。Saga 是主流的解决方案。

Saga 编排模式(Choreography): ① CreateOrder → 发布 OrderCreated 事件 ② InventoryService 订阅 → 扣减库存 → 发布 InventoryReserved ③ PaymentService 订阅 → 扣款 → 发布 PaymentCompleted ④ NotificationService 订阅 → 发短信 失败补偿: ③ 如果 PaymentService 失败 → 发布 PaymentFailed ② InventoryService 订阅 PaymentFailed → 回补库存(补偿事务) ① OrderService 订阅 → 将订单标记为失败 优点:无中心控制器,真正去中心化 缺点:流程难以追踪,Debug 困难 ────────────────────────────────────────────────── Saga 指挥模式(Orchestration): Saga Orchestrator(指挥者) │ ├──① 调用 InventoryService.reserveItem() │ ✓ 成功 ├──② 调用 PaymentService.charge() │ ✗ 失败 ├──③ 补偿:调用 InventoryService.cancelReservation() │ ✓ 补偿成功 └──④ 将订单标记为失败,通知用户 优点:流程清晰,易于监控和 Debug 缺点:引入中心化的 Orchestrator,可能成为瓶颈 实际应用: Uber 使用 Saga 处理行程创建(跨行程、司机、支付三个服务) Amazon 用 Saga 处理订单履行(跨库存、支付、物流)
▶ 面试要点