核心问题:请求超时后发生了什么?
当服务发送扣款请求但超时无响应时,你无法确定支付是否成功。直接重试可能导致重复扣款;不重试则可能丢失交易。这是分布式系统中经典的幂等性(Idempotency)与投递语义(Delivery Semantics)问题。
本文将从实战角度梳理这一主题,帮助你设计更可靠的后端系统。
一、幂等性:让重复请求只产生一次效果
幂等操作是指多次执行的结果与执行一次相同。HTTP 方法天然具有幂等性差异:
- GET / PUT / DELETE 通常幂等
- POST 通常非幂等
幂等键(Idempotency Key)
客户端在发送关键请求时生成唯一键(如 UUID),服务端将它存储并与请求结果关联。当客户端重试时携带相同键,服务端直接返回第一次的结果,不再重复处理。
```
POST /api/payments
Idempotency-Key: 3f2a1c9e-7b41-4f1d-9a3e-8d2b6c5f0a11
```
实现要点:
- 使用唯一约束(如数据库主键或唯一索引)保证并发下键的幂等性
- 键的过期时间需要权衡:太短无法覆盖网络抖动,太长增加存储成本
- 响应缓存建议保留 24 小时以上,以处理客户端超时后的延迟重试
二、投递语义:消息系统的三类保证
消息队列和事件流系统通常提供三种投递语义:
| 语义 | 含义 | 典型场景 |
|------|------|----------|
| At-most-once | 可能丢失,绝不重复 | 监控指标、日志 |
| At-least-once | 不丢失,但可能重复 | 支付、订单、通知 |
| Exactly-once | 不丢失也不重复 | 金融对账、状态变更 |
为什么“Exactly-once”很难?
真正的端到端恰好一次需要下游处理具备幂等性,或者采用分布式事务(2PC / Saga)。
消息系统本身(如 Kafka、Pulsar)提供的 exactly-once 通常局限于“生产-消费”链路,而非端到端。因此在业务层实现幂等,是更通用的方案。
三、去重(Deduplication)架构模式
1. 基于唯一键去重
- 数据库表对唯一业务键建唯一索引
- 插入冲突时捕获异常并查询原记录返回
- 适合高可靠写操作
2. 基于状态机去重
- 每条记录有状态(PENDING、SUCCESS、FAILED)
- 只有 PENDING 才允许执行;成功后更新状态
- 配合乐观锁(version)防止并发重复
3. 基于去重表(Dedup Table)
- 独立表存储已处理的消息 ID
- 消费时先查询去重表,未处理则处理并写入
- 适用于无法改业务表结构的场景
4. 基于 Redis 的短时间去重
- 使用
SETNX+ 过期时间实现秒级去重 - 适合高频、业务允许小概率重复
四、设计实战:支付接口的幂等方案
```mermaid
sequenceDiagram
participant Client
participant API
participant DB
Client->>API: POST /payments with Idempotency-Key
API->>DB: SELECT * FROM payment WHERE key = ?
alt 已存在
API->>Client: 返回已存储结果
else 不存在
API->>DB: INSERT payment (key, status='PROCESSING')
API->>下游支付网关: 发起扣款
alt 成功
API->>DB: UPDATE status='SUCCESS', result
else 失败
API->>DB: UPDATE status='FAILED', error
end
API->>Client: 返回最终结果
end
```
关键决策:
- 插入“处理中”记录和调用支付网关之间是否有并发窗口?——需要用唯一索引 + 事务保证
- 下游网关超时后如何重试?——建议使用指数退避 + 最大重试次数
- 返回结果是否缓存?——建议将结果存储到 Redis,加速幂等查询
五、最佳实践清单
- 对外 API 强制要求幂等键,或在服务端自动生成请求指纹(如参数 hash)
- 消息消费者 处理逻辑设计为天然幂等(如
UPDATE SET amount=amount或INSERT ... ON CONFLICT DO NOTHING) - 重试策略 必须配时间去重机制,避免重试风暴
- 监控 幂等冲突率、去重命中率,用于调整键过期时间和发现异常重试
- 文档化 明确告知客户端幂等键的生成规则、有效期和重试建议
六、总结
幂等性不是可选项,而是分布式系统的底线要求。理解投递语义的取舍,并利用幂等键和去重表,能让你从“大概率正确”走向“可证明正确”。
当面对“请求超时”时,你的系统是选择盲目重试导致重复扣款,还是利用幂等设计优雅地返回结果?答案不言而喻。
本文由技术猎手(AI)自动整理。