redis实现分布式锁及其问题解决方式

补充说明:

我们现在分布式锁主流的实现方案有:

1. 基于数据库实现分布式锁

2. 基于缓存(Redis等)

3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点

1. 性能:redis最高

2. 可靠性:zookeeper最高

这里,我们就基于redis实现分布式锁。

分布式锁用来解决什么问题呢?

   通俗的说:就是你在分布式集群中,如果你上锁的话,在当单机版中咱们可以实现,但是我们集群分布式中为其中一台机器上锁,另外的机器他应该找不到,或者不知道这里面有上锁,所以咱们用一种锁让所有机器都能认识,就是咱通俗说那个叫共享锁;

那共享锁怎么做呢?

  有各种方案,咱们用的是Redis进行实现,做法就是通过setnx进行上锁,上锁之后用del(delete)来释放锁,但是这个过程呢如果说你上锁之后你忘记释放了,那锁他就一直没有释放,别的操作只能等待,所以咱们可以设置key的这个过期时间,你可以手动释放,如果你没有手动释放,它会自动完成,而这个过程中因为它不是原子操作的;

想让它变成原子性的操作,怎么做?

  我在上锁的同时设置过期时间,让上锁过期时间一起进行,用set nx ex同时操作,这样的话把这个问题都做成了原子性操作,这就是我们关于分布式锁的基本演示。

redis实现分布式锁及其问题解决方式_第1张图片

问题:setnx刚好获取到锁,业务逻辑出现异常,导致锁无法释放。

解决:设置过期时间,自动释放锁。

通过expire设置过期时间(缺乏原子性:如果在setnx和expire之间出现异常,锁也无法释放)。

解决:在set时指定过期时间也就是同时设置Key和过期时间(推荐)。

这时会出现其他问题:可能会释放其他服务器的锁

过程是:a先进行上锁的时候,发现服务器卡顿了,导致a的过期时间已经到了,当a反应过来的时候,b得到了锁,然后a又发现过期时间到了,又释放锁,这是释放的是b的锁,导致了误删锁的操作,使用uuid解决防误删。

redis实现分布式锁及其问题解决方式_第2张图片

Uuid防止误删:

    我们要删除锁的时候,首先判断当前uuid是否和要删的uuid是否相同,相同则可以删除,否则,则不行。

这时又出现了一个问题:

    因为在删除操作中没有原子性的原因,如果遇到了在uuid比较过后,a正准备删除锁的时候,刚好到了过期时间,锁立马被b引用了,此时a还不知道,又会误删锁,这是uuid已经判断过了是一样的锁,所以又会引起问题。

解决:引用Lua脚本进行优化。

// 1. 从redis中获取锁,set k1 v1 px 20000 nx
String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue()
      .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);
// 2. 释放锁 del
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 设置lua脚本返回的数据类型
DefaultRedisScript redisScript = new DefaultRedisScript<>();
// 设置lua脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
// 3.重试
Thread.sleep(500);
testLock();

 

 

 

 

 

 

 

你可能感兴趣的:(redis,分布式,java)