商品秒杀项目(思路版)

需要的数据库表

  • 商品表
  • 订单表
  • 秒杀商品表
  • 秒杀订单表 包括秒杀商品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),这样多线程并发情况下,只有一个线程能完成对库存的修改