5种分布式锁实现的对比?

点击蓝色“乔志勇笔记”关注我哟

加个“星标”,第一时间获取推送的文章哦!

 

 

一、分布式锁的适用场景

在分布式环境下,为保证数据的一致性,需要保证同一时刻同一方法,只有一个线程在运行,即互斥

二、分布式锁的设计因素

1、互斥性

同一时刻只能有一个服务(或应用)访问资源,特殊情况下有读写锁

2、原子性

一致性要求保证加锁和解锁的行为是原子性的

3、安全性

锁只能被持有该锁的服务(或应用)释放

4、容错性

在持有锁的服务崩溃时,锁仍能得到释放避免死锁

5、可重用性

同一个客户端获得锁后可递归调用

6、公平性

看业务是否需要公平,避免饿死

7、支持阻塞和非阻塞

和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)

8、高可用

获取锁和释放锁 要高可用

9、高性能

获取锁和释放锁的性能要好

10、持久性

锁按业务需要自动续约/自动延期

三、分布式锁的实现方案

1、基于数据库的实现

方案一:基于表的唯一索引

(1)创建锁记录table,用锁定的资源作为唯一索引

(2)加锁时执行一条insert插入

(3)完毕后删除 该记录

问题:

非阻塞,无容错(客户端崩溃后锁不能释放),不可重入等等

方案二:基于数据库排他锁

使用select for update 加锁 保持回话session连接 ,解锁时释放连接

问题:

回话连接性能差,还是不可重用

2、基于redis的实现

方案一:新的set命令 和 lua 脚本

 
  1. - 获取锁(unique_value可以是UUID等)

  2. SET resource_name unique_value NX PX 30000

  3.  

  4. - 释放锁(lua脚本中,一定要比较value,防止误解锁)

  5. if redis.call("get",KEYS[1]) == ARGV[1] then

  6. return redis.call("del",KEYS[1])

  7. else

  8. return 0

  9. end

问题:(1)非阻塞,只能通过死循环实现,通过 Redis 的订阅发布模式来实现通知的成本太高

(2)redis单点,主从切换可能出现锁丢失

方案二:实现redlock算法的redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上

基于redlock算法,根据lua 脚本和watch dog 机制实现了自动延期,可重入 ,还可实现读写锁、公平锁,联锁等等

问题:AP模型,无一致性算法,存在一致性问题

参考文章:

https://juejin.im/post/5bf3f15851882526a643e207

https://blog.csdn.net/turbo_zone/article/details/83422215

https://mp.weixin.qq.com/s?__biz=MzU5ODUwNzY1Nw==&mid=2247484164&idx=1&sn=210397905ef284c1d2756d1cdf73880f

https://mp.weixin.qq.com/s?__biz=MzU5ODUwNzY1Nw==&mid=2247484155&idx=1&sn=0c73f45f2f641ba0bf4399f57170ac9b&scene=21#wechat_redirect

3、 基于zookeeper的实现

基于Curator客户端实现分布式锁

  • Shared Reentrant Lock 可重入锁

  • Shared Lock 共享不可重入锁

  • Shared Reentrant Read Write Lock 可重入读写锁

  • Shared Semaphore 信号量

  • Multi Shared Lock 多锁

方案一:排他锁

创建临时有序节点排序后,watch比自己小1的节点等待获取锁

方案二:读写锁

  •  

    读锁:又称共享锁,如果前面没有写节点,可以直接上锁;当前面有写节点时,则等待距离自己最近的写节点释放( 删除 )。

     

  •  

    写锁:如果前面没有节点,可以直接上锁;如果前面有节点,则等待前一个节点释放( 删除 )

     

存在问题:

(1)性能上不如使用缓存实现的分布式锁,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能

参考文章:

利用Zookeeper实现 - 分布式锁

4、基于etcd的实现

etcd像是专门为集群环境的服务发现和注册而设计,它提供了数据TTL失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便的跟踪并管理集群节点的状态

因为 etcd 使用 Raft 算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序

保持独占,即所有试图获取锁的用户最终只有一个可以得到。etcd为此提供了一套实现分布式锁原子操作CAS(CompareAndSwap)的API。通过设置prevExist值,可以保证在多个节点同时创建某个目录时,只有一个成功,而该用户即可认为是获得了锁。

·控制时序,即所有试图获取锁的用户都会进入等待队列,获得锁的顺序是全局唯一的,同时决定了队列执行顺序。etcd为此也提供了一套API(自动创建有序键),对一个目录建值时指定为POST动作,这样etcd会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用API按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号

方案:基于etcd API

尝试拿锁 + 自动续租 + 关闭清理 三个api

拿锁失败 进入等待队列

参考文章:

https://segmentfault.com/a/1190000014297365

问题:没有现成的框架,需要一定自研

5、基于consul的实现

方案:consul中锁的主要是依赖KV Store和Session相关API

(1)acquire操作只有当锁不存在持有者时才会返回true,并且set设置的Value值,同时执行操作的session会持有对该Key的锁,否则就返回false

(2)release操作则是使用指定的session来释放某个Key的锁,如果指定的session无效,那么会返回false,否则就会set设置Value值,并返回true

参考文章:http://vearne.cc/archives/39126?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

问题: 没有现成的框架,需要一定自研

为保持一致性 ,1个client释放锁之后,其它client无法立刻获得锁

四、分布式锁的总结

综合所有设计要素,发表下自我的看法:

1、如果缺乏自研能力,用redis的redisson框架最好 ,相比zk 性能比较好

2、如果拥有自研能力,基于etcd实现分布式锁是做好的,consul分布式应用不广泛

3、不建议线上业务基于数据库实现,因为基本很难用


 

近期文章:

Java并发编程学习体系

java8 Stream 史上最全总结

Java 网络编程"初探"

redis 知识点总结

java 核心技术学习总结 (一)

spring中"投机取巧"地限制 用户同时登陆

 


 

如果你喜欢本文

请长按二维码,关注乔志勇笔记

 

  • 0
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值