Spring Boot 3.x + Vue3 + Redis + RabbitMQ:从零搭建高并发秒杀商城系统(附完整源码)
Spring Boot 3.x + Vue3 + Redis + RabbitMQ:从零搭建高并发秒杀商城系统(附完整源码)
前言
“秒杀”是互联网技术领域皇冠上的明珠。在短短几秒内,数百万流量如海啸般涌入,如何保证系统不崩溃、数据不超卖(库存不出现负数)、用户体验流畅,是对全栈工程师架构能力的终极考验。
传统的 Spring Boot MVC + MySQL 架构在秒杀场景下完全无法支撑。本文将带你基于 Spring Boot 3.x 和 Vue 3,深度整合 Redis(做库存预热与超卖控制)和 RabbitMQ(做流量削峰与异步解耦),从零搭建一个生产级的高并发秒杀系统。
源码获取方式: 关注公众号【全栈技术精选】,回复关键词 “Seckill2024” 获取完整前后端代码、SQL 脚本及部署文档。
一、 系统架构设计
1.1 核心技术栈
| 模块 | 技术选型 | 版本 | 作用 |
|---|---|---|---|
| 后端 | Spring Boot | 3.2+ | 基础框架,拥抱 JDK 17+ |
| 缓存 | Redis | 7.0+ | 库存预热、原子性扣减、防重复点击 |
| 消息队列 | RabbitMQ | 3.12+ | 削峰填谷,将同步下单转为异步处理 |
| 前端 | Vue 3 + Element Plus | Latest | 高性能页面,轮询秒杀结果 |
| 安全 | Spring Security + JWT | Latest | 用户认证与接口保护 |
1.2 整体架构图
通过分层架构,我们将流量挡在数据库之外。
持久层
消费者层
消息队列层
缓存层
接入层
客户端
秒杀请求
1. 原子扣减2. 发送消息3. 软削峰4. 异步写入
海量用户
Vue3 秒杀页面
Spring Boot 3.x 秒杀接口
Redis 集群
Lua 脚本保证原子性
RabbitMQ
下单消费者服务
MySQL 8.0
二、 数据库设计
秒杀系统的核心在于“快”,表结构设计应遵循最小化原则,减少复杂的 JOIN 关联。
2.1 ER 实体关系图
被购买
下单
PRODUCT
bigint
id
PK
string
name
int
stock_count
真实库存
int
seckill_stock
秒杀库存
SECKILL_ORDER
bigint
id
PK
bigint
user_id
FK
bigint
goods_id
FK
string
state
0:无效 1:成功 2:支付
datetime
create_time
USER
bigint
id
PK
string
nickname
三、 核心业务流程
这是整个系统的大脑。当用户点击“秒杀”按钮时,系统不会直接操作数据库,而是先经过 Redis 的“安检”。
3.1 秒杀业务时序图
MySQLRabbitMQRedis秒杀后端Vue3 前端用户MySQLRabbitMQRedis秒杀后端Vue3 前端用户步骤1: Redis 预扣库存步骤2: 异步解耦步骤3: 消费者落库alt[库存不足 或 重复秒杀][扣减成功]点击“立即秒杀”1POST /seckill/do2执行 Lua 脚本扣减3返回结果4提示“手慢了/已购买”5发送下单消息6返回“排队中” (前端轮询)7消费消息8INSERT INTO seckill_order9更新前端轮询状态10
四、 核心代码实现
4.1 Redis Lua 脚本:解决超卖问题
为什么用 Lua?因为 Redis 是单线程的,执行 Lua 脚本期间其他命令必须等待,这保证了“判断库存”和“扣减库存”的原子性,彻底解决超卖问题。
脚本位置:src/main/resources/seckill.lua
local stockKey = KEYS[1]local userKey = ARGV[1]..":".. ARGV[2]-- 用户ID:商品ID-- 1. 判断是否重复购买if redis.call('get', userKey)=='1'thenreturn-1end-- 2. 判断库存local stock =tonumber(redis.call('get', stockKey))if stock ==nilor stock <=0thenreturn0end-- 3. 扣库存 redis.call('decrby', stockKey,1)-- 4. 标记用户已买 redis.call('set', userKey,'1') redis.call('expire', userKey,300)return14.2 Spring Boot 后端实现
1. 配置依赖
确保引入 Web, Data Redis, AMQP (RabbitMQ) 依赖。
2. 核心服务类
@ServicepublicclassSeckillService{@AutowiredprivateStringRedisTemplate redisTemplate;@AutowiredprivateRabbitTemplate rabbitTemplate;privateDefaultRedisScript<Long> redisScript;@PostConstructpublicvoidinit(){ redisScript =newDefaultRedisScript<>(); redisScript.setScriptSource(newResourceScriptSource(newClassPathResource("seckill.lua"))); redisScript.setResultType(Long.class);}publicStringdoSeckill(Long userId,Long goodsId){String stockKey ="seckill:stock:"+ goodsId;// 执行 Lua 脚本Long result = redisTemplate.execute( redisScript,Collections.singletonList(stockKey),String.valueOf(userId),String.valueOf(goodsId));if(result ==null)return"系统异常";if(result ==0)return"库存不足";if(result ==-1)return"不能重复购买";// 发送消息到 MQ 队列SeckillMessage message =newSeckillMessage(userId, goodsId); rabbitTemplate.convertAndSend("seckillExchange","seckill.key", message);return"排队中...";// 告诉前端等待,前端轮询查询结果}}4.3 Vue3 前端轮询实现
用户点击秒杀后,前端收到“排队中”,此时开始轮询后端接口查询订单状态,而不是无限等待。
// src/views/Seckill.vue<script setup>import{ ref }from'vue';import axios from'axios';const isSeckilling =ref(false);const status =ref('立即秒杀');conststartSeckill=async()=>{ isSeckilling.value =true;const res =await axios.post('/api/seckill/do',{goodsId:1});if(res.data.includes('排队中')){ status.value ='正在排队...';startPolling();// 开始轮询}else{ status.value = res.data; isSeckilling.value =false;}};conststartPolling=()=>{const timer =setInterval(async()=>{const checkRes =await axios.get('/api/order/status');if(checkRes.data ==='SUCCESS'){clearInterval(timer); status.value ='秒杀成功!'; isSeckilling.value =false;alert('恭喜抢到商品!');}},1000);// 每秒查询一次};</script>五、 总结与优化
通过 Spring Boot 3.x + Redis + RabbitMQ 的组合,我们将高并发流量在 Redis 层面拦截,利用 MQ 将瞬时流量平滑推送到数据库,实现了高性能与数据一致性的平衡。
后续优化方向:
- JVM 级别:使用 Spring Boot 3.x 的 虚拟线程来处理 MQ 消费,进一步提升并发吞吐量。
- 前端优化:使用 CDN 加速静态资源,防止首页加载慢导致用户无法点击。
- 安全加固:在 Controller 层增加 图形验证码或 滑块验证,防止机器脚本刷单。
源码获取:
关注公众号【全栈技术精选】,回复关键词 “Seckill2024”,获取完整的前后端源码、SQL 脚本及 Lua 文件!