商品秒杀项目(思路版)
需要的数据库表
- 商品表
- 订单表
- 秒杀商品表
- 秒杀订单表 包括秒杀商品id和用户id
简单流程
- 点击秒杀
- 是否登录
- 查询库存(库存=0,商品被抢空)
- 查询重复购买(每人只允许在秒杀订单表中只有一条数据)
- 可以秒杀
- 秒杀商品库存-1
- 生成订单
- 生成秒杀订单
- 至此秒杀成功
库存超卖
JMeter
一款测压工具
测试发现,1000个线程即1000个用户同时秒杀,会产生大量的订单
流程分析
- 在高并发下,很多请求同时查询到商品库存信息
- 当两个用户并发SELECT库存后,两个用户获取到的库存是相等的,再UPDATE设置库存-1,相当于只减少了一件
- 因为查询库存和库存-1的操作不是一个原子操作
- 所以解决方案主要就是对查库存修改库存两步操作的原子化
超卖解决方案
悲观锁(syncronized)
在秒杀service方法上添加syncronized来上锁,来保证秒杀服务是串行执行的(有问题,看注意)。需要把syncronized加在controller层,这样就比事务的范围大了
注意:如果事务注解@Transactional添加状态下,对方法进行加锁,仍然会出现错误,因为当一个加锁方法执行完成后,由于事务的存在,当前线程的服务并没有被提交,这时新的线程到来,拿到的仍然是旧的库存,从而产生超卖
问题:悲观锁同一时间只允许一个线程运行,对用户体验很差
乐观锁
利用数据库定义的version字段,表示当前修改的版本,很多JUC的API都使用了类似的思想,添加stamp表示版本信息(此内容查看我的JUC专题)
获取库存的同时获取当前版本号,修改库存时修改version=version+1并且WHERE 中添加 version = #{versionId} ,如果在两步操作之间其他用户修改了库存也会修改version,就会让本线程修改失败(因为根据WHERE version = #{versionId} 已经查不到该条数据了,就会返回0),这样多线程并发情况下,只有一个线程能完成对库存的修改