1、三种分布式锁实现方式对比:
1、数据库分布式锁实现缺点:
1)db操作性能较差,且有锁表的风险;
2)非阻塞操作失败后,需要轮询,占用cpu资源;
3)长时间不commit或者长时间轮询,可能会占用较多连接资源。
2、ZK分布式锁实现缺点:
性能不如redis,因为其写操作(获取锁释放锁)都需要在Leader上执行,然后同步到follower。
3、Redis(缓存)分布式锁实现缺点:
1)过期时间不好控制;
2)非阻塞,操作失败后,需要轮询,占用cpu资源;
2、使用原生redis实现
使用原生Redis的SetNX+Expire实现的分布式锁。
//方案1 setnx String lockKey = "zhyRedis"; //通过val,给锁设置唯一id,防止其他线程删除锁 String clientId = UUID.randomUUID().toString(); //或者雪花生成位置ID boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS); // redisTemplate.expire(lockKey,10,TimeUnit.SECONDS) //旧版本redis(新版直接设置在后面,如上) //如果获取不到锁,则返回失败 if(!result){ result "failed"; } try { //如果能获取到锁,则返回成功 Integer count = Integer.parseInt(redisTemplate.opsForValue.get("count").toString()); if (count > 0) { Integer realCount = count - 1; System.out.Println("购买成功,剩余库存:" + realCount.toString()); redisTemplate.opsForValue().set("count",realCount.toString()); }else{ System.out.Println("购买失败,库存不足"); } } catch(Exception e){ e.printStackTrace(); return "failed"; } finally{ //解锁 //判断当前客户端id与redis分布式中持有的客户端id一致,才能删除锁 if(clientId.equals(redisTemplate.opsForValue().get(lockKey))){ redisTemplate.delete(lockKey) } }
3、使用redisson实现
相较于原生Redis的SetNX+Expire实现的分布式锁而言,Redisson的分布式锁组件可以解决原生Redis组合命令带来的一些缺陷,即redis的超时时间值不知道设置为多少才合适。如果此时Redis的服务器节点恰好出现宕机或者服务不能用的情况,那将会导致相应的Key永远存于缓存中,即处于所谓的“永久被锁死”的状态!
底层的实现机制在于:Redisson内部提供了一个监控锁的看门狗WatchDog,其作用在于Redis实例被关闭之前,不断延长锁的有效期。
除此之外,Redisson还通过加锁的方法提供了leaseTime等参数来指定加锁的有效时间,即超过这个时间后“锁”便自动解开了。
//方案2 redisson String lockKey = "zhyRedis"; RLock rLock = redisson.getLock(lockKey); try { rLock.lock(10, TimeUnit.SECONDS) //如果能获取到锁,则返回成功 Integer count = Integer.parseInt(redisTemplate.opsForValue.get("count").toString()); if (count > 0) { Integer realCount = count - 1; System.out.Println("购买成功,剩余库存:" + realCount.toString()); redisTemplate.opsForValue().set("count",realCount.toString()); }else{ System.out.Println("购买失败,库存不足"); } } catch(Exception e){ e.printStackTrace(); return "failed"; } finally{ //解锁 rLock.unlock(); }