幂等性必须由业务层显式控制,不能依赖框架或http方法;防重入关键在于唯一标识、校验时机与兜底闭环,redis setnx+ttl是常用方案,但需配合db唯一索引兜底,且idempotency-key须稳定生成、全链路校验。

幂等性不是框架自动给的,是业务层必须显式控制的逻辑;防重入不是加个锁就行,关键在于标识唯一性、校验时机和兜底手段是否闭环。
为什么不能只靠HTTP方法判断幂等
GET/PUT在协议层面“语义幂等”,但微服务里完全不可信。比如一个 POST /v1/payments 接口,前端重复点击、Istio网关超时重试、Feign客户端自动重试,都会触发多次调用。数据库里可能多出两条扣款记录,而用户余额已实际扣除两次。类似地,PUT /v1/orders/{id} 若实现为“覆盖更新”,但每次传入的 updated_at 时间戳不同,就可能让审计日志或下游事件重复触发。
常见错误现象包括:Duplicate entry for key 'idx_user_id_order_no'、insufficient balance、日志中同一 trace_id 出现多次但订单状态不一致。
Redis SetNX + TTL 是最常用且落地成本最低的防重入方案
核心是用客户端携带的 idempotency-key(如 user_123:pay:20260630:abc456)作为 Redis key,通过原子操作判断是否首次请求。
-
SetNX(ctx, key, value, 30*time.Second)必须带 TTL,否则服务崩溃后 key 永久残留,合法请求会被永久拦截 -
value建议填request_id或trace_id,便于排查冲突来源 - 若
SetNX返回false,直接返回409 Conflict,不要进 DB 查一遍再拒绝——这会放大延迟且无法解决并发窗口问题 - 注意
github.com/go-redis/redis/v9的连接池配置,超时或失败会导致整个幂等逻辑被跳过
Redis 只是第一道闸,DB 唯一索引才是最终防线
Redis 和 DB 不同源,无法保证原子性。两个请求几乎同时通过 SetNX(极小竞争窗口),或 Redis 故障降级,都可能导致重复写入。此时必须靠数据库兜底。
- 订单表应建联合唯一索引:
UNIQUE INDEX idx_user_id_order_no (user_id, order_no) -
order_no必须是服务端生成的全局唯一号(如雪花 ID),不能直接用 UUID —— 它不携带业务上下文,无法区分“同一用户重复下单”和“不同用户正常下单” - 捕获
ErrDuplicateEntry(MySQL)或unique_violation(PostgreSQL)后,要查库确认该记录真实存在,并原样返回原始响应(含原始订单号、创建时间等),而不是生成新数据 - 不要在事务内先查 Redis 再决定是否执行 DB 操作——Redis 查询不在事务上下文中,无法保证一致性
最容易被忽略的三个细节
一是 idempotency-key 不能基于请求参数哈希生成,因为参数里常含 timestamp、random、trace_id 等非幂等字段,哈希值每次不同;二是缓存穿透风险:空结果也要缓存短时间(如 2s),避免恶意刷不存在的 key 打垮 DB;三是全链路一致性:如果调用链涉及多个服务(如订单 → 库存 → 支付),每个环节都得校验自己的 idempotency-key,不能只在入口层拦一次。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











