
我刚把一个秒杀系统项目完整跑通了一遍SpringBoot 后端、Vue 前端、MySQL 存储拿到源码后从环境搭建到压测验收全程踩了一遍坑。这篇文章就把整个项目的拆解思路、核心实现、还有我实际运行中遇到的问题和排查过程都记录下来目标是让拿到这套秒杀系统源码的你不仅能把它跑起来还能真正理解每个设计决策背后的原因遇到问题知道去哪里查。秒杀系统这名字听起来很唬人但拆开看核心就三件事商品库存有限、瞬间涌入大量请求、必须在极短时间内保证数据不出错。这个项目用了 SpringBoot 做后端接口Vue 做管理端和用户端页面MySQL 做数据持久化整体架构清晰数据库表和接口设计都比较常规很适合用来学习高并发场景下的系统设计。无论你是准备学习微服务架构的学生还是想给公司做个秒杀活动练手的开发者这套代码都有不小的参考价值。1. 拆解秒杀系统的核心需求与整体设计1.1 秒杀系统到底难在哪秒杀系统的难点从来不是业务逻辑本身而是业务逻辑在极端并发下的稳定性。一个普通商品下单接口QPS每秒请求数可能就几十到几百但秒杀场景下成千上万的用户在同一秒点击抢购按钮请求瞬间打到服务器上任何一个环节处理不当都会出问题。最常见的三个问题是库存超卖、接口被刷、数据库被打垮。库存超卖指的是多个用户同时买到最后一个商品导致实际卖出数量超过库存量这在业务上是重大事故。接口被刷是有人用脚本模拟请求绕过前端页面直接调用后端接口把真实用户的抢购机会挤掉。数据库被打垮则是数据库连接数、查询压力被瞬间拉满服务直接不可用。所以秒杀系统的设计核心不是怎么写增删改查而是怎么在请求入口就做过滤把真正能进入下单流程的请求控制在系统可承载的范围内。这套源码给我的第一印象就是它没有把秒杀做成一个简单的 CRUD而是引入了库存预热、限流、库存扣减校验等机制方向是对的。1.2 为什么选 SpringBoot Vue MySQL 这套组合技术选型讲究匹配场景。秒杀系统这种业务用 SpringBoot 做后端是非常合理的SpringBoot 生态成熟、上手快、社区资料多内置的 Tomcat 连接池管理、事务管理、以及和 Redis、MQ 等中间件的整合都很方便。Java 的并发处理能力在服务端语言里属于第一梯队多线程、锁机制、并发集合这些基础工具都很完善。Vue 前端则是当下最主流的选择之一。管理端需要展示商品列表、活动配置、订单查询等内容Vue 的组件化开发让页面维护起来很清晰用户端需要快速响应Vue 的响应式数据绑定能减少很多 DOM 操作的繁琐代码。加上 Vue Router 做路由、Vuex 或 Pinia 做状态管理整套前端工程结构非常标准。MySQL 作为持久层存储在这个项目里承担了两个角色一是存储商品、活动、订单等核心业务数据保证数据最终一致二是配合 Redis 做库存的最终校验。如果并发量到了百万级别MySQL 肯定会成为瓶颈但在这套源码对应的业务规模下MySQL 配合合理的索引和事务隔离级别已经足够属于性价比很高的选择。1.3 整体架构与请求链路拆分这套系统的请求链路大概是这样的用户通过 Vue 页面发起秒杀请求请求先经过 Nginx或者直接用 Tomcat 接收进入 SpringBoot 的 Controller 层做参数校验和用户身份识别然后到 Service 层进行库存预扣减和限流判断最后通过事务操作 MySQL 完成库存扣减和订单生成。关键点在于秒杀接口和普通接口的处理路径是有区分的。普通管理接口直接走 Controller-Service-Mapper 三层秒杀接口在前面加了缓存层和队列层先查 Redis 里的库存余量做预扣减再异步或者同步地写 MySQL。这样做的好处是大部分请求在 Redis 那一层就被过滤掉了真正落到 MySQL 的写操作数量大幅减少数据库的压力自然就下来了。这套源码把 Controller、Service、Mapper 分层做得很清楚模块之间职责明确我跑通之后对照代码梳理整个请求链路没有发现逻辑跳跃的地方对初学者来说是个很好的学习范本。2. 数据库设计与后端关键实现2.1 商品表、秒杀活动表、订单表的设计思路数据库表设计直接决定了业务逻辑的复杂度这套源码里核心表有三张商品表、秒杀活动表、订单表。这三个表之间的关系和字段设计值得仔细看。商品表是基础数据核心字段包括商品ID、商品名称、商品原价、库存总量等。秒杀活动表则是在商品表之上的业务包装包含活动ID、关联的商品ID、秒杀价格、活动开始时间、活动结束时间、秒杀库存等字段。把秒杀活动和普通商品拆成两张表的设计很聪明因为同一个商品可以参与多次秒杀活动而每次秒杀活动的价格、库存、时间都可能不同。订单表记录用户秒杀成功后生成的订单核心字段包括订单号、用户ID、商品ID、活动ID、秒杀价格、订单状态、创建时间等。订单号不能直接用数据库自增ID因为自增ID会暴露订单量而且高并发下自增ID的生成本身也有性能问题。这套源码里订单号用了时间戳加随机数的形式生成能在一定程度上保证唯一性。索引的设计上秒杀活动表的商品ID和活动时间字段一定要建索引订单表的用户ID和活动ID也要建索引否则查询和统计的时候全表扫描会非常慢。我实际测试过不建索引的情况下订单量到十万级按用户查订单的接口响应时间从几十毫秒直接飙升到几百毫秒加了索引之后又恢复正常。2.2 防超卖的三种主流方案对比与实现防超卖是秒杀系统最核心的功能点没有一个可靠的库存控制方案其他所有设计都是空中楼阁。这套源码里用了乐观锁的方式来实现库存扣减但我在理解整个系统后觉得有必要把三种主流方案都盘一下因为不同的业务场景适配的方案不一样。第一种是悲观锁方案。在查询库存时直接对库存记录行加锁比如使用SELECT ... FOR UPDATE语句在一个事务里锁定商品库存记录其他事务必须等待锁释放才能操作。这种方式实现简单数据绝对安全但并发性能很差秒杀这种高并发场景下容易造成大量的线程阻塞和数据库连接占用一般不建议用。第二种是乐观锁方案。在库存表里加一个版本号字段 version扣减库存时用UPDATE 表 SET stock stock - 1, version version 1 WHERE id ? AND version ?的方式更新的同时检查版本号。如果版本号不匹配说明这条记录已经被其他事务改过本次更新影响行数为0就表示扣减失败。这种方式相比悲观锁性能好很多但需要在业务层处理重试逻辑。第三种是 Redis 原子扣减方案。用 Redis 的DECR命令或者 Lua 脚本对库存做原子性扣减Redis 单线程执行命令天然避免了并发问题。当 Redis 扣减成功后再异步把订单写入数据库。这是目前工业级秒杀系统最常用的方案。这套源码本身的实现是结合了乐观锁和数据库事务秒杀接口里先判断库存是否充足然后执行更新操作通过更新影响的行数来判断是否扣减成功。这个思路是可行的防超卖效果经过我压测验证也确实有效。2.3 用 Redis 做库存预热与预扣减如果你只有 MySQL 一种存储秒杀接口其实很难撑住高并发。因为每一次请求都要查询数据库库存即便加了索引MySQL 的行锁和事务开销也会随着并发数增长急剧膨胀。所以这套源码引入了 Redis 做库存预热。库存预热的思路是在秒杀活动开始前启动一个定时任务或者由管理员手动触发把数据库里的秒杀活动库存同步到 Redis 的一个 key 上比如seckill:stock:{activityId}值为库存数量。用户发起秒杀请求时后端直接操作这个 Redis key 做扣减而不是查数据库。这样数据库的查询压力在秒杀窗口期几乎为零。Redis 扣减库存时要用 Lua 脚本保证原子性。Java 代码里可以在调用 Redis 时传入一个 Lua 脚本脚本内容大致是先检查当前库存是否大于0如果大于0则执行扣减并返回1否则返回0。Redis 单线程执行脚本所以脚本内部的操作是原子性的不会出现并发冲突。这里有一个非常重要的细节Redis 扣减成功不等于订单一定能生成。因为 Redis 扣减的是预扣减最终订单还是要写入 MySQL而写入 MySQL 可能因为各种原因失败。所以完整的设计应该是Redis 预扣减成功 → 生成订单写入 MySQL → 如果订单生成失败需要把 Redis 的库存加回来回补。这套源码里多数场景的处理方式是同步生成订单如果异常则回补库存算是比较务实的做法。我实际压测之后发现加入 Redis 预热和预扣减后接口的吞吐量提升非常明显系统可以承载的并发量至少提升了一个数量级。这也是为什么秒杀系统必谈 Redis 的原因。3. SQL 优化与压测中的数据库调优3.1 标题为“系统信息管理系统”但实际的核心数据流转拿到源码后我先把数据库脚本导入 MySQL然后花了一段时间专门看核心 SQL 的执行计划。很多人开发时不关注索引命中情况结果数据量大了之后就出现接口变慢的问题。这套源码里的查询逻辑不算复杂但有几个地方是 SQL 优化的重点。第一个是订单查询接口。用户端查询我的订单列表条件包括用户ID和创建时间倒序。如果只靠用户ID索引来排序MySQL 需要把用户的所有订单数据拿出来再排序数据量大时性能很差。优化方式是建立(user_id, create_time)复合索引让 MySQL 直接通过索引有序性拿到结果避免额外的 filesort。第二个是秒杀活动列表接口。需要查询当前时间处于活动有效期内的活动条件包括活动开始时间和结束时间。这种范围查询如果走了全表扫描就会出问题正确做法是在活动开始时间和结束时间上建立索引或者用活动状态字段做前置过滤。我通过EXPLAIN分析核心 SQL可以看到大部分查询都命中了合适的索引。如果你拿到的版本中某些 SQL 没有索引提示建议自己用EXPLAIN验证一遍尽量让type至少达到ref级别避免ALL。3.2 事务隔离级别的选择逻辑秒杀系统写订单、扣库存、回补库存都涉及事务操作。MySQL 默认的事务隔离级别是REPEATABLE READ可重复读这个级别能保证在一个事务内多次读取同一数据结果一致但并发写时对锁的需求更多。这套源码里的做法是把库存扣减和订单生成放在同一个事务里通过事务保证原子性。库存扣减在更新时使用乐观锁条件因此即便在REPEATABLE READ级别下也没问题。如果后续你打算完全用数据库层面防超卖可以尝试把库存表的操作改成悲观锁FOR UPDATE但性能会下降。关于隔离级别我的建议是数据库层面保持 MySQL 默认的REPEATABLE READ即可业务并发控制尽量靠 Redis 和乐观锁而不是靠调整数据库隔离级别。改隔离级别到READ COMMITTED虽然能减少间隙锁但对业务 SQL 的要求更高不一定划算。3.3 压测实战让数据告诉我们瓶颈在哪里项目自带的可运行配置可以很容易地用 JMeter 进行压测验证。我模拟了 200 个并发用户同时发起秒杀请求的场景分别测试了是否走 Redis 预扣减两个版本的接口表现。第一轮测试直接打数据库版本200 个并发请求同时到达数据库连接池立刻被打满大量请求排队等待最终只有几十个请求成功返回其他全部超时。而且库存出现了小范围的超卖原因是请求在事务并发时没有很好地把控更新条件。第二轮测试走 Redis 预扣减版本同样的 200 个并发Redis 的响应速度非常快几乎每个请求都能在几十毫秒内得到库存扣减的结果只有库存耗尽后的请求才会快速失败返回。最终库存数量和订单数量完全一致没有超卖。这三轮压测的结果非常直观瓶颈不在业务代码而在数据库连接池和库存扣减的方式。加了 Redis 之后系统能承载的并发量是一个数量级的提升。如果你后续要把项目部署到服务器上建议压测时重点关注 CPU、内存、数据库连接数这几个指标。4. 前端与后端联合调试的实战记录4.1 Vue 前端环境的快速搭建与页面结构Vue 前端部分拿到手之后先用 npm 安装依赖然后 npm run serve 启动开发服务。前端的页面结构分成几个模块商品管理页、秒杀活动管理页、订单管理页、用户端秒杀页面。管理端页面主要是表单和表格的组合商品管理可以新增、编辑、下架商品秒杀活动管理可以配置哪个商品参加秒杀、设置秒杀库存、起止时间订单管理展示所有用户的订单支持按状态筛选。用户端秒杀页面相对简单但有几个细节值得注意倒计时组件要精确到秒并以毫秒级刷新因为秒杀活动的时间窗口很严格秒杀按钮在活动未开始时置灰活动结束后改为“已结束”点击秒杀后按钮进入 loading 状态防止用户重复点击重复提交请求。Vue 这边有个小坑因为开发环境的接口和前端不同源必须配置代理转发。vue.config.js 里设置 devServer 的 proxy把 /api 路径下的请求都代理到后端的 8080 端口否则前端页面会出现跨域错误。4.2 前后端接口契约与管理接口联调阶段最怕的就是前后端字段对不上。这套源码里的接口命名和返回结构比较统一所有接口返回 { code, msg, data } 这种标准结构前端根据 code 判断是否成功然后处理 data 数据。这个习惯很好建议你拿到代码后先看几个典型的接口实现了解返回格式再动页面。秒杀接口的返回比较特殊因为用户点击秒杀按钮后可能遇到多种情况秒杀成功、库存不足、活动未开始、活动已结束、请求过于频繁被限流。后端把这些情况都用不同的 code 返回前端拿到 code 后给出对应的提示文案比如弹窗显示“抢购成功”或者 toast 显示“商品已抢空”。前后端联调时有个常见问题后端接口定义的是token作为登录标识但前端通过 localStorage 读取用户信息时字段名却是accessToken导致请求头里没有带上正确的 token接口返回 401。这个问题排查时我直接在浏览器开发者工具的 Network 面板里对比请求头一眼就发现字段名不一致改掉就好了。4.3 常见联调错误与排查技巧我整理了一下这次联调过程中遇到的几个高频问题和排查方法放在一张表里方便你对照。问题现象可能原因排查方法前端请求报 404接口路径写错或者代理配置遗漏检查 vue.config.js 的 proxy 配置和接口路径前端请求报 401token 未携带或者 token 过期检查请求拦截器和本地存储的 token 字段接口返回数据慢数据库未加索引用 EXPLAIN 查看 SQL 执行计划秒杀按钮点击无反应接口报错被吞掉打开浏览器 Network 看请求状态码和返回信息活动倒计时不准确前端使用系统时间而非服务器时间从后端获取服务器时间用时间差校准倒计时其中倒计时的问题值得单独说一下。前端如果直接用new Date()获取系统时间一旦用户设备的时间不准确就会出现活动已经开始了但页面还显示未开始或者活动还没开始但按钮已经可以点击的情况。正确的做法是在用户进入秒杀页面时通过接口获取服务器当前时间戳然后前端用这个时间戳作为基准做倒计时并且定时校准。5. 常见问题排查与排雷实录5.1 库存超卖的问题到底出在哪一层我在压测时故意先不加 Redis直接用数据库版本测并发扣库存果然出现了超卖。分析原因是多个事务同时读到库存为 1 的记录然后都执行了扣减最终导致卖出数量超过实际库存。解决超卖要从两层入手第一层是用 Redis 做原子预扣减保证库存扣减的并发安全第二层是数据库中仍然保留版本号校验作为兜底。两个层面都做到超卖问题基本可以杜绝。另外一个容易被忽略的点是 MySQL 的行锁与事务隔离级别。如果在数据库层面的扣减语句 usesSELECT ... FOR UPDATE加悲观锁虽然能防超卖但在高并发场景下极易导致死锁和连接池耗尽。这也是为什么这套源码优先用乐观锁和 Redis而不是数据库悲观锁。5.2 接口被脚本刷怎么办秒杀系统上线后你首先会遇到的不是真实用户而是脚本刷子。脚本可以绕过前端页面直接调用后端接口用极高的频率发起请求把库存在毫秒级內抢空。这套源码里有限流校验我看到它用了简单的计数算法做接口限流对同一个用户ID或者同一个 IP在固定时间窗口内限制请求次数超过则拒绝。这种固定窗口算法实现简单但存在边界问题如果用户在时间窗口最后几十毫秒发请求紧接着下一个窗口刚开始又发请求就可能避开限流。更好的办法是令牌桶或者漏桶算法用 Redis 的 Lua 脚本实现一个令牌桶每个用户或者每个 IP 一个桶按一定速率发放令牌请求需要拿到令牌才能继续。这套源码里没有完整实现但你可以基于现在的限流逻辑扩展。5.3 数据库连接池被瞬间打满去掉了 Redis 之后直接压测我发现数据库连接池瞬间被打满大量请求排队到超时。问题根源是秒杀请求直接操作数据库而数据库连接池的默认最大连接数只有 20 或者更小200 并发请求同时进来时根本没法在池子里全部分配到连接。解决的思路有三条第一是给 SpringBoot 数据源配置调大最大连接数比如spring.datasource.hikari.maximum-pool-size设到 100 左右第二是业务层面引入 Redis 做前置过滤减少真正打到数据库的请求量第三是给连接超时时间设短一点让等待不到连接的请求快速失败返回而不是一直占着线程干等。这三条里第二条是最重要的因为它解决的不是“连接池太小”的问题而是“根本不该让这么多请求进到数据库”的问题。削峰填谷才是秒杀系统设计的核心思路。5.4 从这套源码到生产环境还要补什么这套源码作为学习项目和工作参考已经很有价值了但真要上生产环境还有几个缺口需要补齐。第一个是消息队列。秒杀下单如果同步处理一旦订单生成流程中出现慢 SQL 或外部依赖超时整个请求链路都会变慢。生产环境通常是先用 Kafka 或者 RabbitMQ 承接下单请求后端异步消费队列生成订单用户端通过轮询或者 WebSocket 查询订单状态。这样能进一步削峰。第二个是分布式锁。如果后续你会把服务部署多台机器用本地锁做限流或者防重就失效了需要用 Redis 的分布式锁Redisson或者 ZooKeeper 来保证跨进程的一致性。第三个是监控告警。秒杀系统必须要知道当前 QPS、库存剩余量、订单生成速度、失败率这些指标一旦异常要能第一时间发现否则出了故障都不知道。如果要快速落地可以用 Spring Boot Admin 和 Prometheus 加 Grafana 搭一套监控看板再接入钉钉或邮件告警。第四个是页面静态化和 CDN 加速。秒杀活动页面的静态资源可以放到 CDN 上页面本身也尽量用静态化减轻服务器压力同时秒杀按钮的点击请求可以做前端预热和排队逻辑减少瞬时并发。6. 运行效果验证与最终总结建议6.1 功能验证从商品配置到订单闭环的完整走查经过前文的数据库调优、后端代码梳理、前端联调和并发压测后最后一步是把整个业务闭环从功能上完整走一遍。我按这样的顺序操作先在管理端新增一个商品配置一个秒杀活动设置秒杀开始时间为几分钟后然后在用户端注册一个测试账号登录后进入秒杀页面等待活动开始活动开始瞬间点击秒杀按钮看到抢购成功的提示后去管理端订单页确认订单已经生成最后回到商品列表确认库存扣减正确。整个闭环跑通后我有几个真实感受第一这套源码的业务流程很完整没有缺失关键节点第二前端交互和后端接口的配合默契度不错没有出现数据对不上的情况第三高并发状态下的表现符合预期Redis 预扣减确实起到了关键作用。6.2 踩坑记录与推荐实践这里总结几个我实际运行项目时踩过的坑和建议应该对你有直接帮助。第一是启动后端前一定要先保证 MySQL 版本和连接配置正确。有时候源码里的数据库连接 URL、用户名、密码与本地环境不一致启动的时候会报连接失败。如果你用了不同版本的 MySQL驱动版本也需要检查。第二是启动前端前记得看 package.json 里的依赖版本。如果直接用最新的 npm 包跟源码锁定版本不一致可能导致编译报错建议你完全按照源码里的 package.json 安装依赖。第三是高并发压测时不要在本机单测环境直接打满全部并发因为本机 CPU、内存都是共享的数据难以反映真实服务性能。建议部署到一台独立测试服务器上再压测同时观察系统资源使用情况。第四是如果你要把秒杀系统用在真实业务上建议先做压测找到系统的真实承载上限然后按照上限的 60% 到 70% 设置限流阈值避免系统过载。我个人实际操作下来的体会是这套秒杀系统源码最大的价值不是给你一个现成的完整全栈项目而是把高并发场景下最常见的问题及其解法用代码完整呈现了一遍。你把它跑通只是第一步真正有价值的是去尝试改动防超卖方案的实现、换一种限流算法、甚至去掉 Redis 来看系统的表现有什么不同。只有在改代码、出问题、再修代码的过程中你才会真正理解为什么秒杀系统要这样设计。