秒杀系统需从Redis原子操作、channel限流、异步落单三层面硬控:DECR须校验返回值并回滚;channel容量需压测确定;成功后立即响应,后续全异步。

秒杀系统不是“加个锁就能跑”,高并发下超卖、雪崩、OOM 几乎是默认行为,必须从 Redis 原子操作、channel 限流、异步落单三个层面硬控,缺一不可。
redis.Decr 必须校验返回值并立即回滚
直接 redis.Decr 后不看结果就创建订单,等于主动送库存进负数。DECR 返回的是扣减后的值,不是操作是否成功的布尔值。
- 遇到
redis.Nil(key 不存在)要区分是活动未开始还是配置漏了,不能 panic - 返回值
表示已售罄,此时必须立刻 <code>redis.Incr回滚,否则网络中断或 panic 会导致库存永久缺失 - 别用 Lua 脚本封装“先 GET 再 DECR”——这破坏原子性;正确脚本只做
DECR+ 条件判断,且必须返回新值供 Go 层校验
channel 限流的容量不能拍脑袋定
make(chan struct{}, 100) 这个 100 是系统生死线,不是经验值。它直接决定你 Redis 的 p99 延迟、MySQL 的 Threads_running 和机器内存水位。
- 压测时盯紧
Redis INFO commandstats中cmdstat_decr的延迟,超过 5ms 就得降 channel 容量 - MySQL
SHOW STATUS LIKE 'Threads_running'持续 > 200,说明 DB 已排队,channel 得缩容 - CPU 长期 > 70% 或内存使用率逼近 90%,不是加机器,而是 channel 已超载,再撑下去会触发 OOM killer
异步落单不是优化,是防止 503 的底线
秒杀成功那一刻,只要 redis.Decr 返回 ≥ 0、订单号生成完成,就必须立刻 HTTP 响应。后续所有操作——写 MySQL 订单表、发风控 MQ、更新销量统计——全扔进异步队列。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 同步写库一旦卡在 MySQL 的行锁或主从延迟上,goroutine 就卡住不归还
chan令牌,新请求直接 503 - 别用
sync.WaitGroup等异步任务结束——它不释放 channel 令牌,起不到限流作用 - 异步任务失败必须有重试+死信兜底,但重试不能影响用户侧响应时间,这是两层逻辑
幂等下单必须用 SETNX,禁用本地缓存和唯一索引
前端 disabled、session 标记、JWT claim 里塞 flag,对脚本重放完全无效。幂等控制必须服务端强约束。
-
SETNX seckill:order:123:user456的 key 必须含场次 ID + 用户 ID,value 存毫秒时间戳或随机 token,过期时间设为支付超时 + 30s -
SETNX返回 0 就直接返回 “您已参与本场秒杀”,不走任何后续逻辑 - 本地
map或sync.Map在多实例部署下彻底失效;MySQL 唯一索引写入太慢,扛不住瞬时洪峰,会把 DB 打挂
真正难的不是写对一行 Decr,而是让 channel 容量、Redis 过期时间、SETNX 过期时间、异步队列重试策略这四组参数在压测中形成闭环咬合——差一个毫秒,整条链就可能滑向超卖或雪崩。

















