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

posted on 2021-11-24 11:21  有梦可有为  阅读(2055)  评论(0编辑  收藏  举报