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