Idempotency-Key头+服务端幂等校验最稳妥:客户端生成唯一key(如UUID v4),服务端用Redis原子SET NX判重并缓存结果,单机可用IMemoryCache但需设过期时间,资金类等关键操作须叠加业务状态校验。
Idempotency-Key 头 + 服务端幂等校验最稳妥高并发下用户快速双击、网络重试、前端防抖失效,都会导致重复请求。光靠前端限制不可靠,必须后端兜底。最通用的做法是让客户端每次请求带上唯一 Idempotency-Key(比如 UUID v4),服务端用该 key 做原子性判重和结果缓存。
关键点:这个 key 必须由客户端生成(避免服务端生成时并发冲突),且在重试时复用同一 key。
Idempotency-Key 值建议长度 ≥ 32 字符,避免哈希碰撞SET key value EX 3600 NX)确保“检查+写入”不被并发打断IMemoryCache 做轻量幂等控制(仅限单节点)如果 API 部署在单台服务器且 QPS 不超高(IMemoryCache 快速落地。注意它不跨进程,集群部署必须换 Redis。
public class IdempotentHandler
{
private readonly IMemoryCache _cache;
public IdempotentHandler(IMemoryCache cache) => _cache = cache;
public bool TryRegister(string idempotencyKey, out string cachedResult)
{
if (_cache.TryGetValue(idempotencyKey, out cachedResult))
return false; // 已存在,拒绝执行
_cache.Set(idempotencyKey, "", new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
Priority = CacheItemPriority.High
});
return true; // 可执行
}
}
配合中间件或 ActionFilter 使用,拦截 Idempotency-Key 头,调用 TryRegister;返回 409 Conflict 或直接返回缓存结果。
IMemoryCache,只存状态码和简要结果(如 {"orderId":"xxx"})AbsoluteExpirationRelativeToNow,避免内存泄漏$"idemp_{idempotencyKey}",防止和其他业务 key 冲突集群部署必须用 Redis。核心逻辑是:用 SET key value EX 3600 NX 原子写入,成功则执行业务,失败则查 GET key 返回历史结果。
注意 Redis 的 NX 是严格原子的,比先 EXISTS 再 SET 安全得多。
status(Success/Failed)、response(原始响应体)、timestamp
is 失败时,应降级为拒绝请求(503 Service Unavailable),而不是跳过幂等校验不是所有接口都适合只靠 Idempotency-Key 拦截。以下情况必须叠加业务层校验:
POST /api/payments):需查数据库确认订单当前状态,防止“已退款又重复支付”POST /api/orders):即使 key 重复,也要检查订单号是否已存在,避免因 key 泄露导致恶意构造expireAt):key 相同但参数不同,应视为不同请求,不能直接返回旧结果真正难的不是加一层缓存,而是厘清“什么算重复”——这取决于业务语义,不是技术能自动推断的。