1. 秒杀系统架构设计解析
秒杀系统作为电商领域的高并发典型场景,其核心挑战在于如何在瞬间流量洪峰下保证系统稳定。这套基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的技术栈组合,经过多个线上项目验证,能够支撑万级QPS的秒杀场景。先看整体架构的分层设计:
前端层采用Vue3组合式API开发,通过axios实现请求拦截和响应封装。特别优化了秒杀按钮的防重复点击机制——在点击后立即禁用按钮并显示倒计时,即使用户刷新页面也会通过localStorage保持禁用状态。这种客户端限流能有效减轻服务端压力。
网关层使用Nginx做负载均衡,配置了针对秒杀接口的特殊限流策略。我们为/api/seckill路径设置了单个IP每秒5次的请求限制,超过阈值直接返回503状态码。同时启用Nginx缓存,对商品详情等静态化内容设置30秒的浏览器缓存。
服务层的SpringBoot2应用采用多级缓存设计:
- 第一层:本地Caffeine缓存(有效期2秒)
- 第二层:Redis集群缓存(有效期5秒)
- 第三层:MySQL数据库
这种阶梯式缓存策略使得90%的读请求在本地缓存层就能返回,实测在8核16G服务器上可支撑1.2万QPS。对于库存扣减这类写操作,我们采用Redis原子操作+Lua脚本保证原子性:
lua复制local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
数据层的MySQL8.0配置了以下优化:
- 使用InnoDB集群实现读写分离
- 秒杀商品表添加
version字段实现乐观锁 - 事务隔离级别设置为READ_COMMITTED
- 针对
seckill_order表做了分库分表(按用户ID哈希)
2. 核心业务流程实现
2.1 秒杀令牌发放机制
在高并发场景下直接暴露秒杀接口会导致系统被刷爆。我们的解决方案是采用动态令牌机制:
- 用户进入详情页时,前端调用
/api/seckill/token接口 - 服务端校验用户资格后,生成包含用户ID和商品ID的JWT令牌
- 令牌设置5秒有效期并存入Redis
- 实际秒杀请求必须携带有效令牌才能处理
这种机制有效拦截了脚本刷单,实测可以将无效请求降低83%。令牌生成的核心代码如下:
java复制public String generateSeckillToken(Long userId, Long goodsId) {
String key = "seckill:token:" + userId + ":" + goodsId;
String token = JwtUtil.generateToken(userId, goodsId);
redisTemplate.opsForValue().set(key, token, 5, TimeUnit.SECONDS);
return token;
}
2.2 库存预热与扣减
秒杀开始前,通过定时任务将商品库存加载到Redis:
java复制@Scheduled(cron = "0 0/30 * * * ?")
public void preheatStock() {
List<SeckillGoods> goodsList = seckillGoodsMapper.selectList(null);
goodsList.forEach(goods -> {
String key = "seckill:stock:" + goods.getId();
redisTemplate.opsForValue().set(key, goods.getStockCount());
});
}
扣减库存时采用Redis原子操作+数据库乐观锁的双重保证:
java复制public boolean reduceStock(Long goodsId) {
String redisKey = "seckill:stock:" + goodsId;
Long remain = redisTemplate.opsForValue().decrement(redisKey);
if (remain < 0) {
redisTemplate.opsForValue().increment(redisKey); // 回滚
return false;
}
// 异步更新数据库
seckillExecutor.execute(() -> {
int updated = seckillGoodsMapper.reduceStock(goodsId);
if (updated == 0) {
log.warn("库存扣减失败 goodsId:{}", goodsId);
}
});
return true;
}
2.3 订单创建流程优化
传统的事务型订单创建在高并发下会成为瓶颈。我们采用"预订单+异步确认"的二级模式:
- 先创建状态为"处理中"的预订单
- 将订单信息发送到RocketMQ
- 消费者异步完成库存校验、支付预处理等操作
- 最终更新订单状态
这种设计使得下单接口的响应时间从原来的300ms降低到80ms。订单表的核心字段设计如下:
sql复制CREATE TABLE `seckill_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL,
`goods_id` bigint NOT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-处理中 1-已创建 2-已取消',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_goods` (`user_id`,`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 前端性能优化实践
3.1 静态资源优化方案
Vue3项目通过以下手段提升加载速度:
- 使用
vite构建工具替代webpack - 配置路由懒加载
- 对图片进行WebP格式转换
- 启用HTTP/2服务器推送
实测首屏加载时间从2.1s降低到1.3s。vite配置的关键片段:
javascript复制export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
})
3.2 倒计时精准同步
秒杀场景对时间同步要求极高。我们采用以下方案保证所有客户端时间一致:
- 页面加载时获取服务端时间
- 计算客户端与服务端的时间差
- 倒计时基于服务端时间计算
- 每30秒重新同步一次时间
核心实现代码:
javascript复制let serverTimeDiff = 0
async function syncServerTime() {
const start = Date.now()
const res = await axios.get('/api/time')
const end = Date.now()
serverTimeDiff = res.data.time - (start + end)/2
}
function getCurrentServerTime() {
return Date.now() + serverTimeDiff
}
3.3 请求防抖与错误处理
针对网络不稳定的情况,前端做了多重保护:
- 按钮点击添加500ms防抖
- 接口失败时自动重试3次
- 特殊错误码统一处理(如库存不足提示)
请求拦截器的关键配置:
javascript复制axios.interceptors.response.use(null, (error) => {
if (error.config && error.response?.status >= 500) {
error.config.__retryCount = error.config.__retryCount || 0
if (error.config.__retryCount < 3) {
error.config.__retryCount++
return new Promise(resolve =>
setTimeout(() => resolve(axios(error.config)), 1000)
)
}
}
return Promise.reject(error)
})
4. 高可用保障措施
4.1 熔断降级策略
通过Sentinel实现以下保护机制:
- 秒杀接口QPS阈值设置为5000
- 异常比例超过50%时触发熔断
- 降级后返回预设的兜底数据
Sentinel配置示例:
java复制@SentinelResource(
value = "seckill",
blockHandler = "handleBlock",
fallback = "handleFallback"
)
public Result seckill(Long goodsId) {
// 业务逻辑
}
public Result handleBlock(Long goodsId, BlockException ex) {
return Result.fail("系统繁忙,请稍后再试");
}
public Result handleFallback(Long goodsId, Throwable t) {
return Result.fail("秒杀活动太火爆了");
}
4.2 压测与性能调优
使用JMeter进行全链路压测时,重点关注以下指标:
- 订单创建成功率(要求>99.9%)
- 99线响应时间(要求<1s)
- 系统负载(CPU<70%)
调优过程中发现的主要问题及解决方案:
- MySQL连接池瓶颈:将HikariCP的maxPoolSize从50调整到200后,TPS提升40%
- Redis大Key问题:将商品详情从string改为hash存储,内存占用减少65%
- Full GC频繁:调整JVM参数为
-XX:+UseG1GC -Xmx4g -Xms4g后,GC时间减少80%
4.3 监控告警体系
搭建的监控体系包括:
- Prometheus采集JVM/Redis/MySQL指标
- Grafana展示实时数据
- 关键异常通过企业微信机器人告警
重点监控项阈值设置:
- CPU使用率 > 80%持续5分钟
- 订单创建失败率 > 1%
- Redis内存使用 > 90%
告警信息示例:
code复制[秒杀系统告警]
主机: 10.0.0.1
指标: CPU使用率
当前值: 92%
阈值: 80%
时间: 2023-08-15 14:30:00
这套监控体系在上次大促中提前10分钟发现了Redis连接泄漏问题,避免了系统崩溃。
