高并发场景下秒杀下单超卖问题

模拟扣减库存方法(JVM级别锁,分布式环境下异常)

  1. @RequestMapping("/deduct_stock")
  2. public String deductStock(){
  3. synchronized (this) {
  4. int stock=Integer.parseInt((stringRedisTemplate.opsForValue().get("stock")));//jedis.get("stock");
  5. if(stock>0){
  6. int realStock=stock-1;
  7. stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value);
  8. System.out.println("扣减成功,剩余库存:"+realStock);
  9. }else{
  10. System.out.println("扣减失败,库存不足");
  11. }
  12. }
  13. return "end";
  14. }

在集群环境下高并发场景下出现超卖问题()

1.使用redis的setnx实现分布式锁

  1. @RequestMapping("/deduct_stock")
  2. public String deductStock(){
  3. String lockKey="product_101"
  4. Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zz");
  5. if(!result) {
  6. return "error_code";
  7. }
  8. try {
  9. int stock = Integer.parseInt((stringRedisTemplate.opsForValue().get("stock")));//jedis.get("stock");
  10. if (stock > 0) {
  11. int realStock = stock - 1;
  12. stringRedisTemplate.opsForValue().set("stock", realStock + "");//jedis.set(key,value);
  13. System.out.println("扣减成功,剩余库存:" + realStock);
  14. } else {
  15. System.out.println("扣减失败,库存不足");
  16. }
  17. }finally {
  18. stringRedisTemplate.delete(lockKey);//锁释放
  19. }
  20. return "end";
  21. }

问题:系统宕机finall未执行,则锁未释放
解决办法:设置超时时间

  1. stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

问题:设置完锁突然异常,未成功设置超时时间
解决办法:原子操作设置锁和超时时间

  1. Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zz",10, TimeUnit.SECONDS);

问题:线程2删除了线程1的锁,锁失效
image.png
解决办法:只能删除clientId自身的锁
image.png
问题:锁判断、删除也需要原子执行
image.png
解决办法:锁续命

2.Redisson分布式锁方案

image.png

原理

image.png