大数跨境
0
0

幂等性设计的 7 种常见模式:从请求到事件总线

幂等性设计的 7 种常见模式:从请求到事件总线 小何出海
2025-10-22
10
导读:“在分布式系统中,最难的不是并发,而是重复。”—— 一位被幂等坑过的工程师在高并发与分布式系统中,“重复请求”“消息重放”“定时任务重复执行”都是常态。
“在分布式系统中,最难的不是并发,而是重复。”
—— 一位被幂等坑过的工程师

在高并发与分布式系统中,“重复请求”“消息重放”“定时任务重复执行”都是常态。
如果处理不当,轻则数据错乱,重则「重复扣款」「订单多生成」「库存为负」😱

这时,我们需要的就是——幂等性设计(Idempotency Design)

本文将系统梳理 7 种常见的幂等性模式,从请求到事件总线,带你一步步掌握系统级防重的最佳实践。

🧩 一、什么是幂等性?

幂等性(Idempotency)指:

一次或多次执行操作,结果保持一致。

换句话说,即使你多点几次按钮、接口多重发几次、消息被多消费几次——系统结果依然正确。

例如:

  • ✅ 点击「支付确认」两次,不会重复扣款

  • ✅ 消息队列重试 3 次,仍只入库一次

  • ✅ 定时任务触发两次,只更新一次状态

🧱 二、幂等性设计的 7 种常见模式

下面从请求入口 → 业务逻辑 → 数据存储 → 消息事件四个层面,看实际工程中常用的幂等性设计手段。

① 请求级:Idempotency Key(请求幂等键)

📦 典型场景: 用户重复点击「提交订单」或「支付」。
💡 做法:
客户端为每次操作生成一个唯一请求 ID(如 UUID),放在请求头或参数中:

POST /api/orderHeaders:  Idempotency-Key: 7f5c9b2c-6f7d-4d9e-a1b3-xxxxxx

服务端逻辑:

  1. 检查该 Key 是否已处理(查 Redis / 数据库);

  2. 若已存在 → 直接返回之前的结果;

  3. 若不存在 → 执行逻辑并记录 Key 结果。

🧠 优点: 通用、跨语言、业务无侵入。
⚠️ 注意: Key 存储需带 TTL,防止无限增长。

② 数据级:唯一约束(Unique Constraint)

📦 典型场景: 创建订单、注册用户、发放优惠券。
💡 做法:
依赖数据库层唯一索引来保证物理幂等:

CREATE UNIQUE INDEX idx_order_no ON orders(order_no);

当重复插入同一订单号时,数据库报 Duplicate Key 错误,业务捕获后返回已有结果。

🧠 优点: 简洁高效、数据库原生支持。
⚠️ 注意: 仅适用于“确定性唯一”的场景(如订单号、业务号)。

③ 状态机约束(State Transition Check)

📦 典型场景: 防止订单状态反复更新,如 CREATED → PAID → DONE
💡 做法:
在更新时检查当前状态是否允许转换:

if (order.getStatus() == PAID) {    return// 已支付,无需重复}
🧠 优点: 与业务逻辑天然一致。
⚠️ 注意: 要用分布式锁,状态必须落地存储,避免多节点状态不一致。

④ 幂等日志表(Idempotent Log Table)

📦 典型场景: 处理第三方回调(如支付平台通知)、Webhook、MQ 消息。
💡 做法:
建立一张幂等日志表,记录业务操作的唯一 ID:

CREATE TABLE idempotent_log (  id VARCHAR(64PRIMARY KEY,  biz_type VARCHAR(32),  processed_at TIMESTAMP);

处理逻辑:

  1. 收到请求,先查表是否存在此 ID;

  2. 若存在 → 忽略;

  3. 若不存在 → 执行逻辑并插入日志。

🧠 优点: 可追溯、通用性强。
⚠️ 注意: 表需定期清理,避免日志膨胀。

⑤ 消息级:去重消费(Exactly-once-like)

📦 典型场景: 消息队列(Kafka / RocketMQ / RabbitMQ)重复投递。
💡 做法:
记录已消费的 messageId 或 offset,重复消息直接跳过。

Kafka 还支持 事务性生产 + 幂等消费

Producer 端带事务 ID,Consumer 提交 offset 时保证“生产消费一致性”。

🧠 优点: 在消息系统内部实现端到端幂等。
⚠️ 注意: 实际效果通常为 “At-least-once + 幂等消费”,完全的 Exactly-once 成本极高。

⑥ 缓存控制:去重 + TTL(Redis 防抖锁)

📦 典型场景: 高频点击、接口防抖、防重复下单。
💡 做法:
利用 Redis 原子操作实现短期幂等:

SETNX order:lock:123 true EX 30

若返回 OK → 首次执行;
否则说明已有操作在执行。

🧠 优点: 性能高、实现简单。
⚠️ 注意: Redis 宕机会失效,仅适合临时控制。

⑦ 事件总线级:事件幂等(Event Idempotency)

📦 典型场景: 微服务间事件驱动架构中,事件重放或重试。
💡 做法:
为每个事件附加全局唯一 eventId,在事件消费者端记录处理状态(数据库 / Redis)。

消费者逻辑:

  1. 收到事件,检查 eventId 是否已处理;

  2. 若已处理 → 跳过;

  3. 若未处理 → 执行业务并标记完成。

🧠 优点: 事件重放、补偿场景下保持一致性。
⚠️ 注意: 事件幂等要与事件溯源机制配合使用(Event Sourcing)。

🧮 三、幂等设计的选型建议
层级
模式
适用场景
优点
注意事项
请求级
Idempotency Key
API 幂等提交
通用性强
需存储 key
数据级
唯一约束
创建类操作
简洁高效
仅适唯一场景
业务级
状态机校验
状态流转
与语义一致
状态需持久化
日志级
幂等表
Webhook / 回调
易追踪
表需清理
消息级
去重消费
MQ 重试
高可靠
成本较高
缓存级
Redis 防抖锁
高频接口
快速实现
临时控制
事件级
Event Id
微服务事件流
系统一致性
与溯源结合

🧭 四、总结

幂等性不是一个功能点,而是一种系统稳定性设计哲学
它存在于:

  • 请求入口的「防重复提交」

  • 数据存储的「唯一约束」

  • 消息流通的「去重机制」

  • 乃至事件总线的「一致性重放」

真正的架构师,应该在系统设计的每个环节都考虑幂等性

💬 写在最后

在真实的分布式世界中,失败与重试是常态,幂等是优雅地应对混乱的艺术

【声明】内容源于网络
0
0
小何出海
跨境分享阁 | 长期积累行业知识
内容 41133
粉丝 1
小何出海 跨境分享阁 | 长期积累行业知识
总阅读235.9k
粉丝1
内容41.1k