每年毕设季,Java秒杀系统这个选题都会毫无悬念地进入热门榜单。你可以说它有些老套,但它能持续火爆,确实有不可替代的理由。作为计算机毕设,它不像纯管理系统那样单薄,也不像纯算法项目那样容易陷入“只跑通Demo、讲不清原理”的尴尬。一个完整的秒杀系统,技术上横跨SpringBoot微服务架构、Redis高并发缓存、RabbitMQ消息异步削峰、MySQL数据库事务与索引优化,业务上又贴近电商真实场景,从开题报告到答辩演示,每个环节都有扎实的内容可以讲。这篇经验帖适合正在选毕设题、已经定下秒杀系统方向但不知从何下手、以及做完Demo但担心答辩过不了的同学参考。
面对这个大题,你不需要一开始就慌着搜代码、找源码。先把事情想透——秒杀系统最核心的价值在哪?它解决了什么问题?技术架构为什么这么搭?把这些逻辑理顺,再来谈代码实现和源码复现,你会发现自己写出来的系统比“白嫖”的更有底气。
1. 为什么每年都有人选“Java秒杀系统”当毕设——选题价值拆解
1.1 技术覆盖面广,适合展示综合能力
先别急着问“这个选题是不是太老”,你要问的是“这个选题能不能展示我的真实水平”。秒杀系统的秒杀场景非常特殊:短时间内巨大流量冲击、极高的并发读写、严格的库存一致性要求。为了应对这些,你几乎能用上Java后端开发中所有主流组件。
一般毕业设计的标准配置是这样:
- 前端:Vue或Thymeleaf模板引擎,页面展示加上倒计时交互
- 后端:SpringBoot全家桶,业务逻辑、接口开发都在这里
- 缓存层:Redis,负责预减库存、缓存热点数据
- 消息队列:RabbitMQ,负责削峰填谷、异步完成下单
- 数据库:MySQL,负责存最终订单和扣减持久化库存
- 中间件:Docker部署,Nginx做负载均衡
这不是为了堆技术而堆技术。而是秒杀这个具体业务真的需要这些组件协同工作。你要是做个学生管理系统,硬上MQ和Redis反而会被评委问“为什么需要”,但放在秒杀场景下,这些组件个个都有明确的使用理由。这就是这个选题最大的优势——技术架构和业务场景完美自洽。
1.2 业务场景真实,答辩时不愁没话说
很多毕设项目答辩时容易冷场,原因出在业务太单薄。比如图书管理系统,评委问“你觉得这个系统最大的难点是什么”,你说“实现了增删改查”,那气氛就很尴尬。但秒杀系统不一样,业务本身自带话题。
评委可能问的问题,你心里可以预演一遍:
- 瞬时高并发下怎么防止库存超卖?
- Redis挂在业务中扮演什么角色?如果Redis宕机了怎么办?
- 1000个人同时抢10件商品,数据库能扛得住吗?
- 你的系统怎么防黄牛刷单?
这些问题本身就是秒杀系统的核心痛点。你只要在毕设里真正解决过其中两三个,答辩现场就能滔滔不绝。项目本身有深度,你展示的空间就大,评委也不会觉得你是在背稿子。
1.3 和热搜中那些“热门方向”比,秒杀系统优势在哪
不少同学在选题时会被其他方向干扰,比如Python爬虫、PHP后台甚至小程序开发。这些方向确实也是热搜常客,但作为毕设选题,各有各的坑:
- Python爬虫方向:爬虫项目技术深度容易集中在数据采集与反爬对抗上,但你说破天它本质上是单脚本行为,很难展示完整工程能力。除非你做成爬虫管理系统,否则撑不起一份毕业设计的质量。
- PHP后台方向:PHP做管理后台确实效率高,框架成熟,但这样一来核心工作变成“拼凑框架+写前端页面”,技术难度和Java高并发场景不在一个量级。
- 小程序/APP方向:适合有完整前后端联调需求的项目,但真正的基础设施还是后端接口设计,如果只是把页面套上去,后端一马平川,答辩时一样心虚。
不是说这些方向不行,而是如果你想选择一个能够集中体现后端功底、并发处理能力和系统设计能力的题目,Java秒杀系统确实是性价比最高的那条路。技术栈主流,学习资料丰富,踩坑也容易找到解决方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统整体设计:先画清楚业务蓝图再动手写代码
2.1 秒杀核心业务链路拆解
拿到题目别急着搜源码。先想清楚,一个用户从进入秒杀页面到收到订单通知,中间经历了什么:
- 用户浏览秒杀商品页面,看到商品信息、秒杀倒计时、库存剩余量
- 秒杀时间到,用户点击“立即抢购”,系统校验登录状态和秒杀资格
- 系统校验库存是否充足,如果库存足够则扣减库存并生成订单
- 订单通过消息队列异步处理,用户页面上显示“排队中”
- 用户收到下单结果,如果成功则跳转到订单详情/支付页面
这个链路看着简单,但每个环节在不同并发规模下都会出问题。单机版“校验库存-扣库存-生成订单”这套同步逻辑,一旦压力上来,数据库连接池直接被打满,整个系统就瘫痪了。所以设计目标就清晰了:尽可能把流量拦截在数据库之前。
2.2 技术选型:每个组件都有明确分工
很多人不理解为什么秒杀系统需要那么多组件,这里我按照流量从进入到落地的顺序画一条线:
- Nginx:请求入口的负载均衡,把流量分散到多台Tomcat
- Redis:承担“第一道闸门”,负责预减库存、存放秒杀令牌、存储热点商品信息。注意,这里Redis干的是拦截动作,不是最终的扣减。
- RabbitMQ:请求到了后端后,不是马上写数据库,而是把秒杀请求封装成消息丢到队列里。这样能保证数据库瞬间只能接收到与自身处理能力匹配的请求量。
- MySQL:真正持久化订单数据和最终库存扣减,因为MQ消费端是单线程或可控多线程处理的,数据库受到的并发压力大大降低。
这套架构的核心逻辑是“层层缓冲、逐级拦截”。你不需要让每个请求都直捅数据库,而是让大部分无效请求在走到数据库之前就被拦住,或者排队等待处理。
提示:做毕设的时候,如果机器配置有限,不一定要真的部署多台Tomcat。完整版演示可以用Docker模拟多实例部署,并把Nginx的负载均衡效果在答辩PPT中展示出来。
2.3 数据库设计与Redis缓存的一致性
表结构不用太复杂,但核心表不能少。我建议至少包含这几张表:
- 秒杀商品表(seckill_product):秒杀商品的id、名称、标题、原价、秒杀价、库存数量、开始时间、结束时间
- 秒杀订单表(seckill_order):订单id、用户id、商品id、下单时间、状态
- 用户表(user):uid、手机号、昵称、密码
这里有个非常经典的设计陷阱:库存字段到底放哪张表?答案很明确——存商品表的一个独立字段,不要在业务中临时统计。秒杀场景中库存是热点数据,每次请求都要读和写,把它独立出来用Redis实现预扣减,数据库只做最终校验,效率才高。
设计Redis缓存时,通常还要处理“缓存不一致”的问题。秒杀开始前,把商品库存预先加载到Redis。实际请求来了,先对Redis执行decr操作,如果扣减之后小于0,说明库存已经没有了,直接返回“已抢光”。如果库存充足,再封装秒杀消息丢给MQ。数据库里的真实库存会在MQ消费端异步扣减。这里要留意的是:数据库中的库存最终扣减必须在事务内完成,防止消息重复消费导致库存重复扣减。
3. 三个必考的高并发难点:超卖、缓存击穿、接口防刷
3.1 超卖问题:从SQL到Redis再到分布式锁层层设防
超卖是秒杀系统的头号Bug。假如库存是10件,但恰好有15个用户在同一毫秒发起请求,系统绝不能卖出第11件。要解决这个问题,可以从三个层面逐层加防线。
第一层:SQL层面的原子性。很多初学者的代码如下:
code复制先SELECT查库存,判断大于0,然后UPDATE库存减1
在高并发下,两个线程同时查到库存为1,同时判断可以扣减,然后同时执行UPDATE,最终库存变成-1,这就超卖了。正解是把扣减动作做成一条原子SQL:
sql复制UPDATE seckill_product SET stock = stock - 1 WHERE id = ? AND stock > 0
这条SQL的意思是:只有库存大于0时才允许扣减,并且扣减是原子操作,数据库行锁保证同一时间只有一个请求能成功扣减。返回受影响行数为1表示扣减成功,为0表示没库存了。
第二层:Redis预减库存。如果所有请求全部打到MySQL执行UPDATE,数据库很快撑不住。所以正常流程是:请求先到Redis,执行:
bash复制DECR seckill_count:productId:1001
DECR命令本身是原子的,但如果库存只剩下1个,1000个请求同时执行DECR,前一个请求把库存从1变成0,其他请求继续执行就变成负数了。这就要求在业务逻辑里判断:DECR之后的返回值如果小于0,说明已经售罄,直接返回失败并需要把库存补充回来或用INCR恢复,同时客户端不再走后续逻辑。如果大于等于0,才允许请求继续进入秒杀队列。
第三层:分布式锁兜底。在多实例部署的情况下,Redis预减库存已经能拦住绝大多数流量,但为了避免并发场景下的极端竞态条件,可以在真正写订单和扣减数据库库存时加上分布式锁(Redisson或Redis SETNX实现)。保证同一用户的多个请求、或者多个用户的并发请求,最终只有一个人能成功拿到锁并完成下单。
提示:毕设答辩时,超卖问题不要只讲一层方案。你要把“没有防护会怎样 -> SQL原子扣减 -> Redis预减 -> 分布式锁兜底”这个过程完整讲出来,评委才会觉得你是真懂,而不是背了个概念。
3.2 缓存与数据库的一致性:先更新谁、什么时候失效
秒杀商品的详细信息(价格、标题、库存展示)不能每次都查数据库,一般会提前缓存到Redis。这就引出一个问题:缓存中库存更新了,但数据库还没来得及扣减;或者数据库扣减成功了,但缓存中还是旧值,用户看到库存不准怎么办?
在这个场景下,核心结论是:秒杀系统的缓存一致性不用做得那么复杂,因为库存数据本身就是高动态的,短暂的不一致是可以接受的。用户看到页面上显示“还剩99件”,实际数据库可能已经卖出去了101件,差一两秒完全没问题。你真正要保证的是最终一致性:Redis中库存扣到0了,数据库最终也一定是0;订单最终一定能在数据库落下来。
比较稳妥的方案是“先更新数据库,再删除或更新缓存”。正常情况下,秒杀请求通过MQ异步消费,消费端在事务中更新数据库库存并创建订单,事务提交后更新Redis中的库存和商品信息。如果某个操作偶发失败,可以通过定时任务扫描Redis和DB的库存差异做补偿。
3.3 限流与风控:令牌桶、接口防刷、验证码
真正的秒杀系统还要防两类问题:普通用户手速太快点了很多次、黄牛脚本在毫秒级发海量请求。这两个问题在毕设中最好也处理一下。
接口防刷最常用的方案是“同一用户限流”。你可以用Redis记录用户ID对应的请求时间戳数组,在一个时间窗口内超过阈值就拒绝服务。比如5秒内同一个用户最多请求3次接口。实现上可以用滑动窗口或令牌桶算法,令牌桶的示意逻辑是:
java复制// 伪代码,令牌桶核心逻辑
public boolean tryAcquire(String userId) {
String key = "token_bucket:" + userId;
Long token = redis.opsForList().leftPop(key);
return token != null;
}
按照固定速率往队列里放令牌,请求来了必须拿到令牌才能继续访问。这样可以稳定控制每个用户的访问速率。实际毕设中也可以简单一些:用INCR加EXPIRE统计用户在时间窗口内的请求次数,超过阈值直接返回“操作过于频繁”。
另外,验证码也是个很实用的兜底方案。秒杀前要求输入验证码或者图形滑块,能有效拦截一部分脚本流量。不过要注意,你别把验证码做到必须用OCR才能过的那种程度,毕设不是做对抗研究,验证码只是展示你的“防刷”设计思路。
4. 核心技术落地方案:从Controller到MQ消费端的完整闭环
4.1 秒杀接口的前置校验链路
写代码之前,先把整体流程理清。秒杀请求进来之后,Controller层不要急着写下单逻辑,而是要做一串前置校验,任何一步不过直接返回失败,减少无效请求流入系统。
典型的校验顺序是:
- 用户是否登录:校验Session或JWT Token,没有登录直接返回“请先登录”
- 秒杀时段校验:确认当前时间是否在秒杀开始时间之后、结束时间之前
- 接口限流校验:检查该用户是否访问过于频繁,超限返回“操作过于频繁”
- Redis预减库存:执行DECR,返回值小于0时返回“商品已抢光”
- 唯一性校验:判断该用户是否已经成功抢购过该商品(用Redis Set或数据库唯一索引实现)
这几步全部通过之后,才把请求封装成消息发送到MQ。
4.2 Redis预减库存与消息队列削峰
Redis预减库存的代码逻辑大概是这样的:
java复制public Result doSeckill(Long userId, Long productId) {
// 1. 校验秒杀时间
SeckillProduct product = getProductFromCache(productId);
if (product.getStartTime().after(new Date()) || product.getEndTime().before(new Date())) {
return Result.error("秒杀未开始或已结束");
}
// 2. 检查用户是否重复购买
Boolean isSuccess = stringRedisTemplate.opsForSet().isMember("seckill_success:" + productId, userId.toString());
if (Boolean.TRUE.equals(isSuccess)) {
return Result.error("您已经参与过该商品的秒杀");
}
// 3. 限制每个用户访问频率
long count = stringRedisTemplate.opsForValue().increment("seckill_limit:" + userId, 1);
if (count == 1) {
stringRedisTemplate.expire("seckill_limit:" + userId, 5, TimeUnit.SECONDS);
}
if (count > 3) {
return Result.error("操作过于频繁");
}
// 4. Redis预减库存
long stock = stringRedisTemplate.opsForValue().decrement("seckill_stock:" + productId);
if (stock < 0) {
return Result.error("商品已抢光");
}
// 5. 发送消息到MQ
SeckillMessage message = new SeckillMessage(userId, productId);
rabbitTemplate.convertAndSend("seckillExchange", "seckillRoutingKey", message);
return Result.success("排队中,请等待下单结果");
}
这里有两个细节点。第一,秒杀时间校验必须同时查数据库配置和Redis缓存,避免Redis中时间数据缓存过期。第二,Redis预减库存和发送MQ消息不是原子操作,如果发送失败需要做好补偿回滚库存,否则库存扣了但订单没生成,会造成大量死库存。
4.3 异步下单:消息消费端的业务逻辑
MQ消费端是整个系统的“心脏”,真正的订单生成和库存扣减在这里完成:
java复制@Component
public class SeckillMessageListener {
@Autowired
private SeckillOrderService seckillOrderService;
@RabbitListener(queues = "seckillQueue")
public void receiveMessage(SeckillMessage message) {
// 消费消息,创建订单
seckillOrderService.createOrder(message.getUserId(), message.getProductId());
}
}
createOrder方法内部需要加事务注解@Transactional,执行以下核心步骤:
- 再次检查用户是否已经下过单(防止MQ重复消费)
- 执行数据库层面的原子扣库存SQL:
UPDATE seckill_product SET stock = stock - 1 WHERE id = ? AND stock > 0 - 如果扣减失败,说明库存已经在数据库层面不足,抛异常回滚
- 如果扣减成功,插入秒杀订单记录
- 把用户ID加入Redis的“已抢购成功”集合,方便用户查询结果
这里还要注意消息重复消费问题。RabbitMQ的默认机制是自动ACK,如果消费消息时抛了异常,消息会被重新投递。如果扣减库存后、插入订单前抛异常,二次消费时不能再次扣减库存,所以就要用“用户+商品”的唯一索引或Redis集合来做防重标识。把这个机制在论文里讲清楚,就是一个很大的加分项。
5. 演示录像怎么制作,才能真正打动评委
5.1 演示录像的脚本设计
拿到一套源码后,很多同学犯的第一个错误就是把演示录像当成了“录屏走一遍”。我在线下答辩见过太多这样的演示:打开系统、登录、点几下按钮、录完。评委全程面无表情,因为什么都看不到技术含量。
演示录像至少要覆盖这些环节:
- 环境启动:展示MySQL、Redis、RabbitMQ、Nginx已经启动,SpringBoot应用正常注册
- 登录注册环节:演示用户登录,展示登录后返回的Token
- 秒杀商品页:展示商品详情、库存剩余量、倒计时功能
- 秒杀核心流程:点击秒杀按钮,页面展示“排队中”,后台日志展示消息队列消费记录,MySQL中订单表新增数据、库存表数值变化
- 超卖验证:使用多个账号并发抢购(可以用JMeter压测工具模拟),展示最终生成的订单数不超过库存数
- 限流效果:快速点击多次,验证超出限次后接口被拦截
- 数据面板:通过Actuator或者自己写的统计接口展示QPS、吞吐量、请求成功率
录制演示视频时,记得注意界面文字要够大,因为评委经常是投影看的,字体太小时根本看不清楚。操作步骤之间留1-2秒停顿,让评委跟上节奏。
5.2 压测与数据可视化:用真实数字说服评委
演示录像里最有力度的部分是压测。我强烈建议你在答辩之前用JMeter或Postman做一次有记录的压测,把结果截图放到答辩PPT和论文测试章节里。
最简单的压测方法:
bash复制jmeter -n -t seckill_test.jmx -l result.jtl -e -o report/
用JMeter创建线程组:模拟500个线程同时请求秒杀接口,压测结束后把聚合报告截图。你需要关注这几个指标:
- 请求总数与失败请求数:失败率必须非常低
- 平均响应时间:最好控制在200ms以内
- 吞吐量(TPS):展示系统处理能力
- 库存与订单数量对比:验证超卖是否发生
库存变化是最直观的证据:秒杀前库存显示“100”,压测后数据库订单表统计100条成功订单,库存归零。把这两组数据同时截到一张图里,比你说一百句“我的系统没有超卖”都有说服力。
如果要再增加一点可视化效果,还可以自己写一个简单接口,把实时的秒杀成功数、队列积压量返回给前端,用Vue+ECharts做一个实时数据看板,动态刷新倒计时结束瞬间的抢购数据。这个设计在答辩时特别有视觉冲击力。
5.3 演示中常见翻车点,提前踩一遍
演示录像翻车大多不是技术上的大问题,而是环境问题:
- 本机启动Redis或RabbitMQ时忘了开服务管理端,录像录到一半报连接失败
- MySQL数据库时区问题导致秒杀时间显示不对
- MQ消费端没有添加
@EnableRabbit注解,消息发出去却消费不了 - 演示时把Redis里的库存数量手动改错了,造成库存为负数
- Docker容器内存分配太小,压测时OOM
我的建议是:录制之前跑一遍全流程,把踩到的坑修复后再正式录。而且录完一定要完整看一遍,特别是检查数据库中的库存数量和订单数量,确保数据一致。
6. 关于源码和文档:如何从“白嫖”走向真正的二次开发
6.1 获取源码的正规渠道与版权提示
“白嫖源码”这句话在同学间很流行,但实际操作中要提醒几句。现在网上流传的“免费源码”质量参差不齐,有的确实能跑通,有的缺依赖缺配置,跑起来能把你急到失眠。比较靠谱的渠道有这几种:
- 学校毕设项目库或学长学姐的遗留项目(注意确认对方是否愿意分享)
- GitHub上的开源秒杀系统项目,特点是规范、依赖完整、注释较全
- 购买二手课程或源码分享平台的资源,注意看好技术栈和Java版本要求
不管是哪种来源,拿到源码之后不要直接改个名字就交。如果答辩老师或查重系统发现你的代码和网上开源版本一字不差,后果很严重。把源码“变成自己的东西”,必须经历一次重构和二次开发。
提示:特别要小心那种打包了一堆附属广告、甚至夹带了不必要脚本的“免费源码”。下载后先全局扫描一遍,特别是检查模块里有没有多余的启动程序、可疑的网络请求地址。安全第一,永远不要用生产或学校服务器跑来历不明的源码。
6.2 拿到源码后如何高效阅读和改造
源码阅读要遵循“从入口到出口”的思路,别一头扎进细节:
- 先看
pom.xml,搞清楚项目用了哪些依赖、什么版本 - 再看
application.yml,理解端口、数据库、Redis、MQ的配置 - 找到Controller层的秒杀接口,顺着方法调用链走下去,弄清楚接口从参数校验到返回结果的完整流程
- 找到MQ的队列声明和消费监听器,理解异步流程
- 最后看数据库建表脚本,把所有表和核心字段的功能搞清楚
这个阅读过程走完,你对整个系统的骨架就有数了。然后再开始改造,我的建议是至少改这四处中的两处:
- 加一个功能模块:比如优惠券秒杀、签到抽奖、定时上架
- 改一套前端界面:换掉自带的简单页面,用Vue3+Element Plus重写一个Bootstrap风格的新页面
- 增加一种监控方式:把日志接入Spring Boot Actuator,或者自写一个Redis队列积压监控接口
- 升级一项基础设施:比如把RabbitMQ换成RocketMQ,或把单机部署改成Docker Compose编排集群
每次改造都要对应地在毕业论文里新增一节“系统实现与改进”,把改造动机、设计思路、实现过程和测试数据写清楚。这样一来,你的论文创新点就有了落地的支撑。
6.3 代码规范与论文查重中容易忽略的细节
很多同学以为代码写好就万事大吉了,其实论文和代码之间需要互相呼应。有几个细节需要注意:
- 类名、方法名、变量名用有意义的名字,别出现
a1、b2这种。 - 关键业务逻辑处写注释,尤其是锁、MQ、事务这些核心位置,注释是必需品。
- 建表语句要有索引设计,特别是订单表要加
user_id和product_id的联合索引。 - 论文“系统测试”章节必须贴真实测试数据和截图,不能只写“测试通过”。
- 核心代码不要大段贴到论文正文,改成“核心代码见附录”,正文只放关键代码片段,降低论文查重率。
7. 答辩高频问题速查清单
7.1 技术层面的必问问题
答辩前把这几个问题准备好,基本就能应对大多数提问:
Q1:Redis预减库存和MySQL扣减库存不一致怎么办?
答:先讲不一致发生的场景,再讲最终一致性方案。Redis扣减成功但MQ消费失败时,通过定时补偿任务扫描Redis和数据库的差异,对超卖部分进行退款或置为无效订单。
Q2:为什么用RabbitMQ而不用Kafka?
答:从业务场景出发。秒杀系统消息量虽然瞬时峰值高,但总量不大,RabbitMQ基于AMQP协议,支持多种路由策略和消息确认机制,对于订单这种需要可靠投递的业务更契合。Kafka优势在大吞吐量日志处理场景,用在秒杀系统属于杀鸡用牛刀。
Q3:如果Redis宕机了怎么办?
答:正常流程是Redis是前端拦截层,MySQL里也保存了商品库存和秒杀开始结束时间。Redis宕机后熔断开关可以自动切换为直接访问数据库的降级模式,只是并发能力下降,但系统不允许不可用。
Q4:库存超卖问题是怎么设计的?
答:把三层方案拆开讲,每一层各解决什么侧重点、哪一层是最终兜底,都讲清楚。
7.2 业务与扩展层面的加分问题
评委经常还会追问一些开放性问题,比如“你觉得这个系统如果上线,还需要哪些改进?”这种问题不要慌,给出有思路的回答即可:
- 增加服务熔断和降级:热点接口通过Sentinel或Hystrix实现流量控制,超出阈值直接返回降级页面
- 增加秒杀白名单机制:提前发放秒杀资格码,只有持有资格的用户才能进入秒杀环节
- 增加防作弊模块:基于设备指纹识别、IP限流规则,封禁恶意账号
- 增加分布式事务方案:用Seata实现跨服务事务一致性,防止下单和积分发放不一致
每个方向你只要说出“为什么需要、我是怎么考虑的、大体怎么实现”,就已经超出大部分同届学生的水平了。
8. 从一个真实踩坑案例说起:MQ消费后库存没扣成
分享一个我在做秒杀系统时真实踩过的坑,希望能让你的调试之路少走点弯路。
现象是:JMeter压测之后,部分请求显示“排队中”,但最后数据库订单表里的记录比实际扣减的库存少了很多。也就是说,Redis预减成功了,消息也发出去了,但消费端创建订单时发生了部分失败。
排查链路是这样的:
第一步,看RabbitMQ管理界面。队列积压数很高,说明消费端处理不过来或者消费逻辑有问题。发现队列里有大量unacked消息,说明消费者可能处理超时或崩溃。
第二步,看SpringBoot日志。发现消费端抛出了DeadlockLoserDataAccessException。原因是多线程消费同一个商品时,同时执行UPDATE seckill_product SET stock = stock - 1 WHERE stock > 0,MySQL行锁竞争激烈,部分事务等待锁超时回滚。
第三步,修复方案。我在消费端为每个商品加了分布式锁(Redisson的RLock),这样同一时间只有拿到锁的线程能执行数据库扣减。锁粒度不是用户维度,而是商品维度,避免不同用户抢同一商品时造成的锁重复竞争。
修复后重新压测,订单数和库存扣减数完全一致。这个问题在普通管理系统里根本遇不到,但在秒杀系统的高并发场景下非常典型。把这个排错过程讲给评委听,价值比“我的系统没有Bug”高得多。
最后提醒一句:秒杀系统当毕设,别停留在“能跑就行”的程度。你花在架构思考和并发细节上的每一分钟,最终都会变成答辩现场的一份从容。希望能帮到正在为毕设头秃的你,也提前祝你答辩顺利。
