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

补充说明:

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

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

2. 基于缓存(Redis等)

3. 基于Zookeeper

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

1. 性能:redis最高

2. 可靠性:zookeeper最高

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

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

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

那共享锁怎么做呢?

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

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

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

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

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

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

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

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

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

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<Long> redisScript = new DefaultRedisScript<>();
// 设置lua脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
// 3.重试
Thread.sleep(500);
testLock();

 

 

 

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值