必须用lua脚本在redis服务端原子执行“判断库存≥需求数并扣减”,因beego无内置支持,需用go-redis/v8预加载脚本、分片key、超时控制及严格返回值判断(-1/0/1),扣减逻辑须绑定订单创建而非prepare或filter。

Beego 框架本身不提供库存扣减能力,高并发秒杀场景下直接在 Beego Controller 里调 MySQL 或 Redis 原生命令,十有八九会超卖或打挂服务。必须绕过框架默认流程,在中间件或 service 层嵌入原子预扣减逻辑,且不能依赖 Beego 的 ORM 或 Session 机制做库存控制。
Beego 中怎么安全调用 Redis Lua 脚本扣库存
Beego 默认没封装 Lua 脚本执行支持,得自己用 github.com/go-redis/redis/v8(注意 v8 不是 v9)手动加载脚本并传参。别用 Beego 的 cache.RedisCache,它不支持 EVALSHA,每次执行都发完整脚本,网络开销大、易超时。
-
deductScript := redis.NewScript(<em>lua内容</em>)必须在应用启动时预加载,不能每次请求都 new - KEYS[1] 推荐设为
stock:1001:shard0这种分片 key,别用stock:1001单 key - 调用时用
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)主动限超时,避免整个 HTTP 请求 hang 住 - 返回值判断要严格:-1(key 不存在)、0(库存不足)、1(成功),别只看 err 是否为 nil
为什么不能在 Beego 的 Prepare() 或 Filter 里做库存检查
Prepare() 是每个请求都执行的钩子,但库存预扣减必须「一次成功即锁定」,如果在这里扣减又没后续下单动作,会导致库存被误占;Filter 更危险——它可能被多个路由复用,而库存 key 是商品维度的,混用会串货。
- 库存扣减必须和「创建订单」强绑定,放在业务 service 方法里,而不是框架生命周期方法中
- Beego 的
this.Data["json"]或this.ServeJSON()不能在扣减失败后直接返回,得先 rollback 或补偿,否则状态不一致 - 若用了 Beego 的
orm.ReadOrCreate写订单,务必确认事务已关闭——ORM 默认开启事务,会拖慢 Redis 扣减后的异步落库
Beego 配合 Kafka 异步落库时的常见坑
Beego 没内置消息客户端,得自己集成 github.com/segmentio/kafka-go。问题在于:Controller 返回 HTTP 响应后,goroutine 可能还没把消息发出去,进程就重启了,导致 Redis 扣了但 DB 没更新。
- 别在 HTTP handler 里直接
go kwriter.WriteMessages(...),要用带 buffer 的 channel + 后台 worker 持久化 - Kafka 消息体里必须带
item_id、deduct_amount、redis_key和时间戳,方便后续对账 - 消费端不能简单
UPDATE stock SET left = left - ? WHERE id = ?,要加AND left >= ?条件,防消息重放导致负库存 - Beego 的
AppConfig.String("kafka::brokers")读配置时,别漏掉端口(如localhost:9092),否则连接失败静默丢消息
最易被忽略的是分片 key 的一致性哈希策略——Beego 项目里往往多个 service 实例,如果分片算法写死在某个 controller 里,扩容后新实例算出的 shard 编号和老实例不一致,同一商品的扣减会打到不同 Redis key,造成库存分散、校验失效。分片逻辑必须抽成独立包,所有地方 import 同一份实现。











