必须用SET key value EX 300 NX,因其是原子操作:NX确保key不存在时才写入,EX设置5分钟过期,避免高并发下GET+SET引发的竞态条件;拆分执行会导致重复校验失效。

为什么 Gin 里不能直接用 db.AutoIncrement 做幂等校验
因为数据库自增 ID 在分库分表或读写分离场景下无法保证全局唯一,更无法作为请求指纹参与幂等判断。你传一个 order_id 进来,后端拿它查数据库发现不存在,就新建一条——这看似合理,但一旦出现网络超时重试,两次请求携带的 order_id 是不同值(比如都是 MySQL 自增),结果生成两条订单。雪花算法生成的 ID 才具备「全局唯一 + 时间趋势递增」双重属性,适合作为幂等键的底层载体。
Snowflake 生成的 ID 必须带业务前缀才能用于幂等键吗
不必须,但强烈建议加。纯数字 ID(如 1234567890123456789)作为 Redis key 或数据库唯一索引字段时,容易与其他模块 ID 冲突;加前缀(如 "pay_req_" + snowflakeID)能隔离命名空间。更重要的是:幂等键本质是「业务动作 + 请求标识」的组合,只靠 ID 无法区分「创建订单」和「支付回调」这两类操作。所以实际存入 Redis 的 key 应该是 "pay:create:" + snowflakeID 或 "order:submit:" + snowflakeID,前缀明确语义,避免跨接口误判。
Gin 中间件里怎么安全调用 Snowflake.NextID() 并绑定到上下文
别在中间件里每次 new 一个 Snowflake 实例——它内部依赖时间戳和序列号计数器,多实例会导致 ID 冲突或时钟回拨误判。正确做法是启动时初始化单例:
var sf *snowflake.Snowflake
func init() {
var err error
sf, err = snowflake.NewNode(1) // workerID=1,需按部署节点分配
if err != nil {
log.Fatal(err)
}
}
然后在中间件中直接调用并注入 c.Set("req_id", sf.Generate().Int64())。注意两点:
• 不要用 c.Param("id") 或 c.Query("id") 作为幂等键来源,那是客户端可控输入,不可信
• 若业务要求前端透传 ID(如防重 Token),应校验其格式是否符合雪花 ID 范围(64 位无符号整数,且高位时间戳不能早于服务启动时间)
Redis 写入幂等键时为什么必须用 SET key value EX 300 NX
因为 NX 保证只有 key 不存在时才写入,EX 300 设置 5 分钟过期(根据业务有效期调整),二者合起来才是原子性幂等判定。如果拆成 GET + SET 两步,高并发下会击穿校验,导致重复执行。Gin 中间件里推荐用 redis.Client.SetNX(ctx, key, value, 5*time.Minute),不要自己拼命令。另外注意:
• key 的 value 不要设为空字符串,应设为具体值(如 "processing"),便于后续排查状态
• 如果业务需要记录首次请求时间,可用 SET key value EX 300 NX 成功后,再异步写入一张 idempotent_log 表,但不要阻塞主流程
• 别把整个请求 Body 当 value 存 Redis,既浪费内存又无必要
workerID 分配策略:K8s 环境下不能硬编码,得从环境变量或配置中心动态加载,否则多个 Pod 启动时用了同一个 workerID,雪花 ID 就不再唯一。


















