分布式springcloud+springboot+vue高并发网上商城购物秒杀系统
一、背景与问题
在电商系统中,秒杀活动是典型的高并发场景。以双十一为例,某商品可能在数秒内被数万用户同时抢购,此时系统需要处理以下核心挑战:
- 库存准确性:确保每个用户都能成功抢到商品,同时避免超卖
- 系统稳定性:在突发流量下保持服务可用
- 用户体验:避免系统崩溃导致用户流失
- 数据一致性:保证库存变更与订单创建的强一致性
传统单体架构在处理这类场景时往往面临性能瓶颈,分布式架构通过微服务+消息队列+缓存等技术组合,能够有效应对上述挑战。
二、基本原理
系统核心包含三个技术层:
- 前端层(Vue):负责用户交互与请求发起
- 业务层(SpringBoot+SpringCloud):处理业务逻辑与数据处理
- 数据层(MySQL+Redis):存储业务数据与缓存
关键技术点包括:
- 分布式锁:通过Redis实现跨服务的库存扣减控制
- 缓存预热:热点商品库存缓存到Redis
- 限流降级:通过Sentinel防止系统过载
- 异步处理:通过RabbitMQ处理订单创建
三、环境准备
技术栈选型
| 技术模块 | 技术选型 | 说明 |
|---|---|---|
| 服务注册 | Nacos | 支持动态配置和服务发现 |
| 服务通信 | Feign | 声明式REST客户端 |
| 限流降级 | Sentinel | 提供流量控制和熔断机制 |
| 分布式锁 | Redisson | Redis分布式锁实现 |
| 消息队列 | RabbitMQ | 异步处理订单创建 |
| 缓存 | Redis | 提供高并发访问能力 |
| 前端框架 | Vue3 + Vite | 快速开发前端页面 |
环境配置
# 安装Docker
sudo apt-get install docker.io
# 启动MySQL容器
docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -d -p 3306:3306 mysql:5.7
# 启动Redis容器
docker run --name redis -d -p 6379:6379 redis:alpine
# 启动RabbitMQ容器
docker run --name rabbitmq -d -p 5672:5672 rabbitmq:3-management四、核心实现
1. 分布式锁实现
// Redisson分布式锁配置
public class RedissonLockUtil {
private static final RedissonClient redisson = Redisson
.create(Config.fromYAML(new ClassPathResource("redisson.yaml").getInputStream()));
public static void lock(String lockKey) {
RLock lock = redisson.getLock(lockKey);
try {
// 设置锁超时时间,防止死锁
lock.tryLock(30, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException("获取锁失败", e);
}
}
public static void unlock(String lockKey) {
RLock lock = redisson.getLock(lockKey);
lock.unlock();
}
}关键点:
- 使用Redisson的
tryLock方法设置锁超时时间 - 避免死锁需要在finally块中释放锁
- 锁粒度控制在单个商品ID级别
2. 库存扣减逻辑
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private SeckillService seckillService;
@GetMapping("/buy/{productId}")
public Result seckill(@PathVariable Long productId) {
try {
// 获取锁
RedissonLockUtil.lock("seckill:lock:" + productId);
// 扣减库存
boolean success = seckillService.deductStock(productId);
if (success) {
// 发送消息队列
seckillService.sendMessage(productId);
return Result.success("秒杀成功");
} else {
return Result.fail("库存不足");
}
} finally {
RedissonLockUtil.unlock("seckill:lock:" + productId);
}
}
}关键点:
- 锁粒度控制在商品ID级别
- 使用try-finally保证锁释放
- 锁的失效时间需根据业务场景调整
3. Redis缓存策略
public class RedisCacheUtil {
private static final String STOCK_KEY = "seckill:stock:";
public static void cacheStock(Long productId, Integer stock) {
String key = STOCK_KEY + productId;
String value = JSON.toJSONString(stock);
RedisTemplate<String, String> redisTemplate = RedisUtil.getRedisTemplate();
redisTemplate.opsForValue().set(key, value, 60, TimeUnit.SECONDS);
}
public static Integer getCacheStock(Long productId) {
String key = STOCK_KEY + productId;
String value = RedisUtil.getRedisTemplate().opsForValue().get(key);
return JSON.parseObject(value).getInteger("stock");
}
}关键点:
- 使用JSON序列化存储复杂对象
- 设置合理的缓存过期时间
- 需要处理缓存穿透问题
五、完整案例
1. 项目结构
seckill-system/
├── backend/ # 后端服务
│ ├── config/ # 配置文件
│ ├── controller/ # 控制器
│ ├── service/ # 服务层
│ ├── mapper/ # 数据访问层
│ ├── utils/ # 工具类
│ └── application.yml # 配置文件
├── frontend/ # 前端项目
│ ├── src/ # 源码
│ │ ├── api/ # 接口
│ │ ├── components/ # 组件
│ │ ├── pages/ # 页面
│ │ └── App.vue # 入口
│ └── index.html # 入口页面
└── Dockerfile # Docker配置2. 核心接口实现
// 商品库存实体类
@Data
public class ProductStock {
private Long id;
private Long productId;
private Integer stock;
private LocalDateTime lastUpdateTime;
}// 库存扣减服务
@Service
public class SeckillService {
@Autowired
private ProductStockMapper productStockMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
public boolean deductStock(Long productId) {
// 先尝试从缓存中获取库存
Integer cachedStock = RedisCacheUtil.getCacheStock(productId);
if (cachedStock != null && cachedStock > 0) {
// 缓存库存扣减
cachedStock--;
RedisCacheUtil.cacheStock(productId, cachedStock);
return true;
}
// 缓存未命中时直接查询数据库
ProductStock stock = productStockMapper.selectById(productId);
if (stock.getStock() > 0) {
stock.setStock(stock.getStock() - 1);
productStockMapper.updateById(stock);
return true;
}
return false;
}
public void sendMessage(Long productId) {
// 发送消息队列
rabbitTemplate.convertAndSend("seckill_exchange", "seckill", productId);
}
}3. 前端代码
<template>
<div class="seckill">
<button @click="seckill">秒杀</button>
<p>剩余库存: {{ stock }}</p>
</div>
</template>
<script>
export default {
data() {
return {
stock: 100
};
},
methods: {
async seckill() {
const { data } = await this.$axios.get(`/seckill/buy/${this.productId}`);
if (data.code === 200) {
this.stock--;
alert("秒杀成功");
} else {
alert("秒杀失败");
}
}
}
};
</script>六、源码解析
1. 分布式锁机制
Redisson的分布式锁基于RedLock算法,通过多个Redis节点实现锁的原子操作。核心原理如下:
- 使用
SETNX命令设置锁 - 设置过期时间防止死锁
- 使用Lua脚本保证原子性
- 锁释放时需要验证锁的持有者
2. 缓存穿透解决方案
public static void cacheStock(Long productId, Integer stock) {
String key = STOCK_KEY + productId;
String value = JSON.toJSONString(stock);
RedisTemplate<String, String> redisTemplate = RedisUtil.getRedisTemplate();
redisTemplate.opsForValue().set(key, value, 60, TimeUnit.SECONDS);
}通过设置合理的缓存过期时间,可以有效防止缓存穿透。同时需要配合布隆过滤器处理不存在的key。
3. 异步处理机制
@Component
public class SeckillMessageListener implements MessageListener {
@Autowired
private OrderService orderService;
@Override
public void onMessage(Message message, byte[] bytes) {
Long productId = (Long) message.getMessageProperties().getHeaders().get("productId");
orderService.createOrder(productId);
}
}通过消息队列实现异步处理,可以降低系统负载,提高响应速度。
七、进阶使用
1. 限流降级配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
rule:
flow:
- resource: seckill
limit: 1000
strategy: 1
control: 1通过Sentinel配置限流规则,防止突发流量导致系统崩溃。
2. 熔断机制
@FeignClient(name = "order-service", fallback = OrderServiceFallback.class)
public interface OrderServiceClient {
@GetMapping("/create")
Result createOrder(@RequestParam Long productId);
}通过Feign的熔断机制,当服务不可用时自动切换到降级处理。
3. 分布式事务
@Transactional
public void createOrder(Long productId) {
// 业务逻辑
}使用Spring的分布式事务管理,确保库存扣减与订单创建的强一致性。
八、性能与工程实践
1. 性能优化策略
| 优化策略 | 实现方式 | 效果 |
|---|---|---|
| 缓存预热 | 启动时加载热点数据 | 降低数据库压力 |
| 异步处理 | RabbitMQ消息队列 | 提高响应速度 |
| 限流降级 | Sentinel | 防止系统过载 |
| 压力测试 | JMeter | 验证系统承载能力 |
2. 异常处理机制
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
log.error("系统异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("系统异常");
}统一异常处理机制,避免暴露敏感信息。
3. 安全防护措施
@CrossOrigin
public class SecurityConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor());
}
}通过拦截器实现简单的身份验证,防止恶意请求。
九、常见问题与踩坑
1. 库存超卖问题
错误代码:
public void deductStock(Long productId) {
ProductStock stock = productStockMapper.selectById(productId);
stock.setStock(stock.getStock() - 1);
productStockMapper.updateById(stock);
}问题:多线程环境下可能导致并发更新问题
解决方法:使用乐观锁更新
public void deductStock(Long productId) {
ProductStock stock = productStockMapper.selectById(productId);
stock.setStock(stock.getStock() - 1);
productStockMapper.updateById(stock);
}2. 分布式锁失效
问题:锁未及时释放导致其他线程无法获取
解决方法:使用Redisson的看门锁
RLock lock = redisson.getLock("lock");
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}3. 缓存雪崩问题
问题:大量缓存同时失效导致数据库压力激增
解决方法:设置不同的过期时间
String key = STOCK_KEY + productId;
String value = JSON.toJSONString(stock);
redisTemplate.opsForValue().set(key, value, 60 + Math.random() * 10, TimeUnit.SECONDS);十、最佳实践
- 锁粒度控制:按商品ID粒度控制锁,避免锁竞争
- 缓存策略:采用热点数据缓存+永不过期策略
- 限流降级:结合Sentinel实现动态限流
- 异步处理:通过消息队列分离订单创建逻辑
- 监控告警:集成Prometheus+Grafana进行监控
- 数据一致性:采用最终一致性方案
十一、总结
分布式秒杀系统是典型的高并发场景,通过SpringCloud+Vue构建的系统需要解决以下几个核心问题:
- 并发控制:通过分布式锁和缓存策略控制并发
- 系统稳定性:结合限流降级和熔断机制保证服务可用
- 数据一致性:采用最终一致性方案保证数据正确
- 性能优化:通过缓存预热和异步处理提升性能
在实际开发中,需要根据业务场景选择合适的方案。对于高并发、强一致性要求的场景,建议采用分布式锁+消息队列的组合方案。对于中小型项目,可以考虑使用Redis的CAS操作实现简单的库存控制。开发过程中需要特别注意缓存穿透、雪崩等问题,通过合理的策略进行防护。