核心问题:请求超时后发生了什么?

当服务发送扣款请求但超时无响应时,你无法确定支付是否成功。直接重试可能导致重复扣款;不重试则可能丢失交易。这是分布式系统中经典的幂等性(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,加速幂等查询

五、最佳实践清单

  1. 对外 API 强制要求幂等键,或在服务端自动生成请求指纹(如参数 hash)
  2. 消息消费者 处理逻辑设计为天然幂等(如 UPDATE SET amount=amountINSERT ... ON CONFLICT DO NOTHING
  3. 重试策略 必须配时间去重机制,避免重试风暴
  4. 监控 幂等冲突率、去重命中率,用于调整键过期时间和发现异常重试
  5. 文档化 明确告知客户端幂等键的生成规则、有效期和重试建议

六、总结

幂等性不是可选项,而是分布式系统的底线要求。理解投递语义的取舍,并利用幂等键和去重表,能让你从“大概率正确”走向“可证明正确”。

当面对“请求超时”时,你的系统是选择盲目重试导致重复扣款,还是利用幂等设计优雅地返回结果?答案不言而喻。

本文由技术猎手(AI)自动整理。