redis实现分布式锁
redis实现分布式锁
分布式锁一般有三种实现方式:1. 基于数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本文档主要介绍基于redis实现分布式锁的方法。
1.加锁
//redis加锁
public boolean getLock(Jedis jedis,String key,int expire){
String uuid = UUID.randomUUID().toString();
String result = jedis.set(key, uuid, "NX", "PX", expire);
if(Objects.equals("OK",result)){
logger.info("redis 加锁成功");
return true;
}else{
logger.info("redis锁已存在,加锁失败");
return false;
}
}
- 第一个为key,我们使用key来当锁,因为key是唯一的。
- 第二个为value,使用uuid便于解锁的时候确保解的是同一把锁。
- 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
- 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
- 第五个为time,与第四个参数相呼应,代表key的过期时间。
2.执行业务流程
public void doTask(){
//处理业务逻辑
}
3.解锁
//redis解锁
public boolean unLock(Jedis jedis,String key,String uuid){
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
long res = redisClient.executeCmd(jedis -> jedis.eval(script, Collections.singletonList(RedisConstants.CLOSE_LEAVE_USER), Collections.singletonList(uuid)));
if (Objects.equals(1L,res)){
logger.info("redis解锁成功");
return true;
}else{
logger.info("redis解锁失败");
return false;
}
}
- 使用lua脚本解锁,把它变成原子操作,保证锁的释放正确。
但是以上代码还是存在问题的,会产生续约和集群同步延迟问题。
续约问题
假想这样一个场景,如果过期时间为30S,A线程超过30S还没执行完,但是自动过期了。这时候B线程就会再拿到锁,造成了同时有两个线程持有锁
这时候就要考虑延长锁的过期时间了。可以设置一个合理的过期时间,保证业务能处理完。或者使用Redisson
集群同步延迟问题
用于redis的服务肯定不能是单机,因为单机就不是高可用了,一量挂掉整个分布式锁就没用了。
在集群场景下,如果A在master拿到了锁,在没有把数据同步到slave时,master挂掉了。B再拿锁就会从slave拿锁,而且会拿到。又出现了两个线程同时拿到锁
Redisson
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。
Redisson通过lua脚本解决了上面的原子性问题,通过“看门狗”解决了续约问题,但是它应该解决不了集群中的同步延迟问题。
总结
redis分布式锁的方案,无论用何种方式实现都会有续约问题与集群同步延迟问题。总的来说,是一个不太靠谱的方案。如果追求高正确率,不能采用这种方案。
但是它也有优点,就是比较简单,在某些非严格要求的场景是可以使用的,比如社交系统一类,交易系统一类不能出现重复交易则不建议用。
参考文章:https://www.jb51.net/article/194793.htm