Golang 做秒杀踩坑实录:直接 Gin+MySQL 写抢购,上线直接崩了

之前做项目的时候,接到一个需求,要做商品秒杀、预约抢购功能。最开始图省事,直接用 Gin + MySQL 来实现,没有引入消息队列。压测的时候就暴露出一堆致命问题:大量并发请求打过来,数据库连接池瞬间耗尽,出现超卖、数据错乱,接口大量超时报错,服务直接接近雪崩。

经过这次踩坑,我完整学习了 RabbitMQ 在 Go 项目中的落地(学习地址:https://www.itying.com/goods-1199.html ),理解了削峰填谷、异步解耦的真正价值。今天站在后端开发实战角度,聊聊秒杀类高并发场景会遇到的坑,以及 RabbitMQ 结合 Golang 如何解决这类业务问题。

一、传统 Gin+MySQL 实现秒杀,到底会出哪些问题

很多新手写秒杀逻辑,代码逻辑看起来逻辑没问题:接收抢购请求,判断库存 > 0,开启事务扣减库存,生成订单返回结果。 但是高并发压测下隐患全部暴露出来:

  1. 超卖问题:多个 goroutine 同时读取库存,都读到库存大于 0,同时执行扣减,库存变成负数,出现超卖,业务数据错乱。
  2. 数据库锁冲突严重:同一行库存记录被大量请求争抢,行锁竞争激烈,大量请求阻塞等待,数据库 CPU、IO 飙升,连接池被打满。
  3. 接口响应超时:大量请求同步操作数据库,接口 RT 飙升,大量用户请求直接超时,用户体验极差。
  4. 没有容错能力:数据库一旦压力过载,整个抢购功能直接不可用,没有缓冲降级手段。

不是 MySQL 不能做扣库存,不能让大量抢购请求直接同步打到数据库。短时间爆发的流量,必须要有一层中间件做流量缓冲。

二、RabbitMQ 核心能力,为什么适合秒杀抢购场景

RabbitMQ 作为成熟 AMQP 协议消息中间件,在业务里主要承担 4 个核心作用:

  1. 削峰填谷:瞬时爆发的抢购请求先存入消息队列,消费者按照数据库处理能力,匀速消费消息,避免流量直接冲击数据库。
  2. 异步提速:前端提交请求,立刻返回排队提示;真正的扣库存、生成订单后置异步处理,大幅降低接口响应时间。
  3. 应用解耦:抢购请求接收、订单生成、库存扣减逻辑互相隔离,某一部分故障不会直接让整个业务完全挂掉。
  4. 消息分发:支持多种交换机模式,可以灵活分发不同业务消息,适配预约、订票、抢购等多种业务场景。

三、RabbitMQ 五种工作模式,在 Go 业务中分别适合什么场景

很多同学学 RabbitMQ 上来直接搞秒杀,但是基础五种模式没有搞懂,写代码到处踩坑。

  1. 简单模式:一对一生产者消费者,适合简单通知、简单任务下发。
  2. 工作队列模式:一个队列,多个消费者并行处理任务,适合耗时任务并发处理;重点要掌握手动 Ack 确认、预取计数 prefetch,防止消息丢失、任务分配不均RabbitMQ。
  3. 发布订阅模式:Fanout 交换机,消息广播给多个队列,适合事件通知,比如抢购成功后,同时推送短信、站内信。
  4. 路由模式 Direct:根据 routing-key 精确匹配,定向投递消息。
  5. 主题 Topic 模式:通配符匹配路由,适合多类型业务消息过滤;还有 RPC 模式,实现基于消息队列的请求‑应答调用。

秒杀系统底层主要基于工作队列模式实现削峰;而抢购成功后的通知推送,会使用发布订阅模式。

四、Golang 结合 RabbitMQ 实现秒杀的整体流程

  1. 用户提交抢购请求,接口做参数校验、用户资格校验;不直接操作数据库。
  2. 将抢购消息(用户 ID、商品 ID)投递到 RabbitMQ 队列,接口直接返回提示 “正在排队,请稍后查看结果”。
  3. 后端启动多个 Go 消费者协程,按照数据库可承受速率消费队列消息。
  4. 消费者拿到消息,执行库存判断、事务扣减库存、生成订单。
  5. 处理成功或失败,记录结果,用户轮询查询抢购结果。

这里有几个生产环境必须要处理的关键点:

  • 消息可靠性保障:消息持久化、生产者确认、消费者手动 ack,防止服务重启消息丢失。
  • 消费端限流:设置 Qos prefetch_count,防止瞬间推送海量消息压垮消费者服务RabbitMQ。
  • 消息过期处理:超时未处理消息做丢弃或者死信队列处理。
  • 不能完全依赖队列:队列只做流量缓冲,库存判断、扣减依然要在数据库层做校验,不能只信任队列消息。

五、压测对比:传统方案 VS RabbitMQ 优化方案

  • 传统 Gin+MySQL:几百并发,数据库压力就拉满,大量超时,出现超卖。
  • RabbitMQ 缓冲架构:接口层可以承接大量用户请求;消费端控制处理速率,数据库压力平稳,不会出现雪崩,同时规避大部分超卖风险。

注意:RabbitMQ 不是银弹。百万级超大流量单纯依靠 RabbitMQ 也不够,还需要叠加 Redis 限流、负载均衡等多层防护。

六、学习过程中容易踩的坑总结

  1. 忘记手动 Ack 确认消息,服务重启消息大量重放,或者消息堆积内存暴涨RabbitMQ。
  2. 不开启消息持久化,RabbitMQ 重启之后消息全部丢失,抢购消息直接没了。
  3. 消费者不做限流,队列瞬间推送成千上万条消息,Go 服务内存飙升。
  4. 误以为消息队列可以完全解决超卖:队列只是削峰,库存扣减业务层依然要做校验。
  5. 只写 Demo,没有模拟消息丢失、重复消费等异常场景,上线才暴雷。

总结

秒杀、抢购、预约、订票这类短时间大量写请求的业务,核心思路就是 “不要让瞬时流量直接打到底层数据库”。

Golang 结合 RabbitMQ,把突发流量做异步缓冲,是中小型项目性价比很高的方案。学习的时候,不要直接复制秒杀 Demo,先把五种消息模式、消息可靠性、ack、持久化这些基础啃透,再去写业务代码。


更多关于Golang 做秒杀踩坑实录:直接 Gin+MySQL 写抢购,上线直接崩了的实战教程也可以访问 https://www.itying.com/category-94-b0.html

回到顶部