← [ BACK_TO_NOTES ]

幂等消费者

解释 at-least-once 消息系统中消费者为什么必须容忍重复交付,以及唯一约束、消息去重表、状态机和业务幂等如何避免重复副作用。

#concept #growing #backend #architecture

[!info] related notes

幂等消费者

[!abstract] 一句话定义 幂等消费者允许同一逻辑消息被投递和处理多次,但最终业务状态与成功处理一次相同。它防的是重复副作用,不只是“看见相同 Message ID 就跳过”。

为什么重复是正常情况

Consumer updates DB successfully
→ crashes before ACK / Offset Commit
→ Broker redelivers

这是 At-least-once 的正常故障窗口。因此消息系统“没有重复”不应成为业务正确性的前提。

三种常用实现

1. 业务唯一约束

例如订单支付事件最终创建一条支付记录:

UNIQUE (payment_id)

重复 INSERT 由数据库约束拒绝或转为 Upsert。优点是正确性靠近最终事实。

2. Processed Message 表

transaction:
  insert (consumer_name, message_id) unique
  update business state
commit

如果 Message ID 已存在,说明该 Consumer 已成功处理。去重记录与业务更新必须在同一事务,否则仍有窗口。

3. 状态机 / 条件更新

UPDATE orders
SET status = 'paid', version = version + 1
WHERE id = ?
  AND status = 'pending'
  AND version = ?;

重复或过期事件无法再次推进状态。适合有明确业务阶段的实体。

为什么只用 Redis TTL 去重不够

SETNX dedup:<message_id> EX 1d

可作为性能优化,但强正确性风险包括:

  • TTL 到期后历史消息 Replay;
  • Redis 故障或 Eviction;
  • 标记成功后业务 DB 写失败;
  • 先写业务再写 Redis 的反向窗口;
  • 多环境或 Consumer Scope 混淆。

关键业务应让去重和业务状态在同一权威事务边界内。

Message ID 与 Business Key

Message ID 识别一次发布;Business Key 识别同一逻辑操作。

例如 Producer 因请求重试生成两个不同 Message ID,但都代表同一个 payment_id。仅按 Message ID 去重仍会重复扣款。

Transport identity → message_id
Business identity  → payment_id / command_id / idempotency_key

高风险场景通常需要业务 Key 兜底。

幂等不等于可交换

两条不同事件可能都各自幂等,却不能乱序:

OrderCancelled(version 3)
OrderPaid(version 2) arrives later

如果只按 Message ID 去重,旧 Paid 仍可能覆盖新状态。还需要 Entity Version、Sequence 或合法状态转换检查。

外部副作用

调用支付、邮件等外部 API 时:

  • 传递稳定 Idempotency Key;
  • 保存请求与结果;
  • Timeout 后查询状态或 Reconcile;
  • 不生成新 Key 盲目重试;
  • 明确哪些动作不可逆,需要补偿而不是重复执行。

Inbox / Outbox 组合

Producer DB
→ business update + Outbox in one transaction
→ publish

Consumer DB
→ Inbox / Processed Message + business update in one transaction

这不能消除所有重复,但把重复变成可控、可审计的正常路径。

设计检查表

  • Message ID 和业务 Idempotency Key 区分清楚。
  • 去重记录与业务副作用处在同一事务或可 Reconcile 边界。
  • 重复处理返回已有结果,而不是简单吞掉必要响应。
  • 过期/乱序消息有 Version / State Machine 防护。
  • 外部 API 使用稳定 Key 并处理 Unknown Outcome。
  • 去重数据的 Retention 覆盖最大 Replay 窗口。
  • 监控 Duplicate Rate、Conflict、Rejected Stale Event 和 Reconciliation。
创建于 2026/9/4 更新于 2026/9/4
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本