分布式锁

作者: 无聊之园 | 来源:发表于2019-06-09 18:26 被阅读0次

一、
分布式锁,的原理,都是,找一个各个服务都能访问的中间层,然后,通过获得这个中间层的占有权达到锁的效果。

1.1、redis分布式锁
redis锁有一个工具包 redisson
里面包装好了,redis锁相关的各种操作,包括可重入锁、公平锁、联锁、红锁、 读写锁等等,这个待会再说。

1.1.1 如果我们设计,会怎么做。

redis锁的大概原理分析如下:

while(true){
if(redis.setnx(key)){
        // 加锁成功
      ,,,处理逻辑
    // 释放锁
      redis.delete(key);
      break;
} else{
    Thread.sleep(10);

}
}

缺点:这种方式,一旦加锁的服务挂了,setnx成功,redis.delete(key)永远得不到执行,那么这个锁永远存在,其他服务获取不到锁了。

解决方式:setnx加超时机制,但是因为setnx本身没有超时机制,所以需要分步加。

while(true){
    if(redis.setnx(key,value)){
        // 30秒过期
        redis.expire(key, 30秒);
        // 加锁成功
        ,,,处理逻辑
        // 释放锁
         redis.delete(key);
        break;
} else{
    Thread.sleep(10);
}
}

可是这样又有一个问题,如果setnx成功,还没来的急expire,服务就挂了,依然会有锁永远无法释放的问题。

那么假设,加上multi,使得setnx和expire一起执行,那么就不会存在setnx执行后,没有执行expire的问题。但是,multi并非是完全事务的,也就是说,setnx失败,expire还是会执行。所以,可能会造成,key一直被expire刷新过期时间。

而在redis 2.6.12版本之后,set,具有了和setnx一样的功能:
set key value NX PX 30000 :nx是setnx的意思,px是过期时间的意思,30000毫秒过期。
这样,就能保证set和expire原子性了。

但是,这样又会有新的问题,因为有了过期时间,如果一个线程加锁后,执行业务逻辑时间太长,锁超过了30秒过期时间,锁已经过期了,并且已经被别的线程加锁了,然后这个旧线程delete了别人的锁。

解决方案:

1、可以在服务端,加一个延迟队列,比如ScheduledThreadPoolExecutor线程池,延迟10秒,expire刷新一下过期时间,这样,一旦服务挂了,这个延迟线程池也得不到执行了。

2、set key value的时候,value弄一个uuid,唯一标识符。然后delete的时候,进行对比,相同,则删除,不相同,则回滚,报错什么的。
但是,问题是,这种机制,get value和对比value,应该保证原子性,否则,get value之后,删除value之前,value过期了,并且,被别人加锁了,那么删除value还是会删除别人的锁。
解决方案:eval执行lua脚本,保证get和对比value的原子性。
redis提供了通过lua脚本自定义命令的功能,比如:

set foo bar
eval 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end' 1 foo bar

如此,基本上是能做到分布式的功能。但是,一旦redis挂了,或者如果是主从结构的redis,master节点挂了,锁还没有同步到从节点,然后从节点就哨兵选举成master节点,就会导致有之间锁的线程,错误。
我个人觉得:上面的redis 节点挂了,导致锁的失败影响是很小的。只会影响到一个锁线程,而且,如果value是uuid的话,那个锁线程如果执行完之后,delete节点的时候,发现uuid对不上,还会回滚。如果是延迟expire刷新过期时间的话,确实会造成,两个线程的delete别人的锁。

1.1.2 redis redlock
redis红锁,的大概思想是,在redis cluster环境下,轮流在多个master节点上建立锁,只要当建立的锁的数量大于master节点数量的一半以上,才算建立成功,这样,一旦一个节点挂了,其他线程,也不可能再在一半master节点以上建立锁成功。
建立的锁也是有失效期的,而且,建立锁时有超时机制,在对一个节点建立锁时间太长了,则超时失败,再另一个节点上建锁。
如果建立失败,则依此删除这个锁。

1.1.3 redission的lock
上面说的,redis锁,直接轮询争抢锁的话,会有一个什么问题呢?
如果很多线程并发轮询,而获取锁的线程,迟迟不释放,则会造成cpu负载过高。
redission的lock的实现机制是,eval lua脚本,然后通过redis的pub/sub发布订阅,加上jdk的locksupport.parkNanos()锁线程的方式,解决了轮旋的方式。

总结:redis 锁,很多人都不建议在生产环境使用,比如上面说的单点redis的问题,集群的red lock 也会有时钟漂移等各种问题。
但我个人觉得,使用redission基本上能够很好的搞定分布式锁。

二、zookeeper锁

zookeeper锁

zookeeper的监听回调结点的删除创建机制可以实现分布式锁的功能,大概原理就是加锁,则看是否锁节点存在,存在则说明别人已经获得锁了,则添加一个节点删除监听,然后,等锁释放后,回去回调这个监听,然后重新获得锁。
释放锁,则删除这个节点。

具体代码,这里写的很好:https://www.jianshu.com/p/5d12a01018e1

如果我们使用zookeeper锁,还是用工具类好,比如Curator,可以参考:
https://www.jianshu.com/p/31335efec309

三、zookeeper锁和redis锁对比
redis分布式锁,轮询获取锁,比较消耗性能,zk分布式锁,监听回调机制,性能开销较小

zookeeper不会存在,服务挂了,锁永远存在的线程,zookeeper可以创建临时节点,zookeeper感应到服务挂了,会自己删除锁节点。

三、数据库实现分布式锁
mysql的for update可以实现锁,但是吞吐量太低了,不考虑。

相关文章

  • 分布式锁

    为什么要用分布式锁 数据库乐观锁redis分布式锁zookeeper分布式锁 使用分布式锁的场景 实现分布式锁的方...

  • 什么是分布式锁?几种分布式锁分别是怎么实现的?

    一、什么是分布式锁: 1、什么是分布式锁: 分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资...

  • 4:Redis 分布式锁 (文末有项目连接)

    1:什么是缓存分布式锁 2:分布式锁的关键代码 3:业务代码使用分布式缓存锁 4:业务代码使用分布式缓存锁 5:测...

  • 锁(2)-- 分布式锁

    前言: 锁分3种:java锁、分布式锁、DB锁 分布式锁的几种实现方式 目前几乎很多大型网站及应用都是分布式部署...

  • java锁的概念

    参考文档探究分布式并发锁并发编程-锁的发展和主流分布式锁比较总结从构建分布式秒杀系统聊聊分布式锁探索并发编程(六)...

  • Redis实现分布式锁

    分布式下的分布式锁一般实现有三种: 基于数据库的乐观锁 基于redis的分布式锁 基于zookeeper的分布式锁...

  • 分布式锁

    为什么要用分布式锁? 分布式锁是悲观锁的实现; 如果采用乐观锁的方案就用不着分布式锁了。 能用乐观锁的地方尽量用乐...

  • 3.10:分布式锁

    本文将梳理微服务架构下,分布式锁的常用方案。整体包含以下三部分: 分布式锁的提出 分布式锁主流方案 分布式锁选择 ...

  • Redis实现分布式锁

    1. 分布式锁分类 数据库乐观锁 基于Redis的分布式锁 基于ZooKeeper的分布式锁 2. 组件依赖 po...

  • 大佬浅谈分布式锁

    redis 实现 redis 分布锁一、redis 实现分布式锁(可重入锁)redission 实现分布式锁1、对...

网友评论

    本文标题:分布式锁

    本文链接:https://www.haomeiwen.com/subject/puvgxctx.html