分布式锁实战
分布式锁应用场景
1、互联网秒杀
2、抢优惠券
3、接口幂等性校验
redis实现分布式锁
1.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @GetMapping("/deduct_stock") public String deductStock() throws InterruptedException { String lockKey = "product_001"; String locKValue = "product_001";
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, locKValue); if(!lock){ return "fail"; } int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("stock", realStock + ""); System.out.println("扣减成功,剩余库存:" + realStock + ""); } else { System.out.println("扣减失败,库存不足"); }
stringRedisTemplate.delete(lockKey); return "success"; }
|
存在问题:
1、没有设置锁过期时间
2、锁不保证得到释放
2.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @GetMapping("/deduct_stock_2") public String deductStock2() throws InterruptedException { String lockKey = "product_001"; String locKValue = "product_001";
try { Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, locKValue,15, TimeUnit.SECONDS); if(!lock){ return "fail"; } int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("stock", realStock + ""); System.out.println("扣减成功,剩余库存:" + realStock + ""); } else { System.out.println("扣减失败,库存不足"); } }finally { stringRedisTemplate.delete(lockKey); } return "success"; }
|
存在问题:
1、高并发场景下,可能释放了别人的锁
3.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @GetMapping("/deduct_stock_3") public String deductStock3() throws InterruptedException { String lockKey = "product_001"; String locKValue = "product_001"; locKValue = UUID.randomUUID().toString();
try { Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, locKValue,15, TimeUnit.SECONDS); if(!lock){ return "fail"; } int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("stock", realStock + ""); System.out.println("扣减成功,剩余库存:" + realStock + ""); } else { System.out.println("扣减失败,库存不足"); } }finally { if(locKValue.equals(stringRedisTemplate.opsForValue().get(lockKey))){ stringRedisTemplate.delete(lockKey); } } return "success"; }
|
存在问题:
1、释放锁时,存在非原子操作
对于释放锁时的非原子操作问题,可以使用 Redis 的 Lua 脚本确保释放锁的操作是原子性(Lua脚本内能保证redis命令连续且完整的执行)的。例如:
1 2 3
| String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(lockKey),locKValue);
|
2、线程1还没跑完,锁就释放了,线程2获取到锁,分布式锁失效(不知道该设置锁超时时间多少合适)
解决办法:
1、使用已经封装好的Redisson,自动续期key失效时间
2、合理定义超时时间,减少耗时,且设置锁过期时间稍稍长一点
3、自己实现第一点逻辑,采用“看门狗”机制:启动一个单独的线程或定时任务,在锁快要过期时自动为锁续期,只要业务操作还未完成
4、监控和告警:在finally之前获取锁成功,释放锁失败时,告警异常情况。
4.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @GetMapping("/deduct_stock_4") public String deductStock4() throws InterruptedException { String lockKey = "product_001"; RLock redissonLock = redisson.getLock(lockKey); try { redissonLock.lock(); int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("stock", realStock + ""); System.out.println("扣减成功,剩余库存:" + realStock + ""); } else { System.out.println("扣减失败,库存不足"); } }finally { redissonLock.unlock(); } return "success"; }
|
存在问题:
1、其他线程获取不到锁超过一定时间,获取锁失败
解决办法:设置获取锁尝试时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @GetMapping("/deduct_stock_5") public String deductStock5() throws InterruptedException { String lockKey = "product_001"; RLock redissonLock = redisson.getLock(lockKey); try { redissonLock.tryLock(10,TimeUnit.SECONDS); int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("stock", realStock + ""); System.out.println("扣减成功,剩余库存:" + realStock + ""); } else { System.out.println("扣减失败,库存不足"); } }finally { redissonLock.unlock(); } return "success"; }
|
Redisson分布式锁实现原理

Redis实现分布式的问题
1、redis集群元数据的维护采用gossip协议通信
master消息没同步给slave前,master挂了,导致另一线程获取锁也成功

RedLock
所有节点获取锁成功才成功,优点类似zookeeper(过半节点成功才成功)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @RequestMapping("/redlock") public String redlock() throws InterruptedException { String lockKey = "product_001"; RLock lock1 = redisson.getLock(lockKey); RLock lock2 = redisson.getLock(lockKey); RLock lock3 = redisson.getLock(lockKey);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); try {
boolean res = redLock.tryLock(10, 30, TimeUnit.SECONDS); if (res) { } } catch (Exception e) { throw new RuntimeException("lock fail"); } finally { redLock.unlock(); }
return "end"; }
|
1、redis实现分布式锁(redisson)
2、Zookeeper实现分布式锁(集中式集群管理,性能较差)
3、基于数据库实现分布式锁
1、利用唯一索引(lock:借助唯一索引保证插入某个key的记录成功 unlock:删除记录)
2、行锁-利用乐观锁 update set xxx= xxx where xxx=xxx,根据返回的影响行数来判断是否获取锁成功 (https://blog.csdn.net/qq_19801061/article/details/119146521)
分布式锁总结
在目前分布式锁实现方案中,比较成熟、主流的方案有两种:
(1)基于Redis的分布式锁:适合并发量高,性能要求很高,对数据一致性要求较低,允许最终一致性的场景
(2)基于ZooKeeper的分布式锁:适合对数据一致性要求较高且并发量较低的场景
基于jedis实现分布式锁