Redisson的分布式锁是基于redis的,curator的分布式锁是基于zookeeper的。
curator的分布式锁在zookeeper之分布式锁已经讲解过了,这篇不讲源码的具体实现,讲讲他们实现分布式锁的流程。
Redisson的分布式锁
假设现在有某个服务的线程A,向Redis申请锁。他需要提供这个锁的名称(假设lock)、自己的信息(假设Thread-A)以及key失效的时间(假设30s,如果没有设置失效时间,当线程A异常退出,这个key会一直保存在Redis中,那其他线程申请锁的时候,会一直阻塞导致申请不到锁)。
向Redis申请锁的时候,线程A会发送Lua脚本给Redis。
由于考虑到锁的可重入,所以这个数据结构是哈希,key对应的值是锁的名称,哈希是锁的持有者和拿到锁的数值。
Redis收到请求后,就会判断是否有这个key,由于线程A是第一次申请这个key的,所以很明显没有,于是就创建新的哈希表,设置对应的数值和过期时间。
如果线程A再申请lock这个锁,Redis此时已经有了这个key,所以他就会看看这个key的持有者是不是当前申请者,此时当前持有者就是线程A,所以就会把数值加1,从1变成了2,然后再重置过期时间。
我们上面设置了30s的过期时间,如果程序执行了30s,还没执行完,就过期了,那这个锁就失效了,其他线程就会拿到锁,导致互斥性失效,所以Redisson有一个监听狗的机制。
我们的过期时间是30s,那监听狗(其实就是开启了一个线程)每30s/3=10s会给Redis发送请求,如果这个key的持有者是当前线程,那就会把失效时间重置为30s。
在线程A持有锁的时候,线程B也来申请锁。
由于这个key存在,且不是线程B持有的锁,所以他就获取锁失败了。
失败后他就会去订阅这个锁的解锁广播信息,并且创建信号量。
线程A释放锁的时候,会判断是否为当前锁的持有者,由于当前就是线程A,所以把数值从2减1,说明还是线程A持有锁。
当线程A再释放锁的时候,这个数值就减为0,说明线程A已经不需要这个锁了,此时Redis就会发布这个锁的解锁广播。
线程B可是一直订阅这个锁的解锁广播,当A解锁后,线程B就会接收到这个订阅,所以就释放信号量。
当时释放信号量的时候,线程B就开始重新申请,流程同上,如果还是没有获取到锁,那就会继续订阅这个锁的广播信息,当然不是无限等待,获取锁的过期时间到了,就说明没有获取锁。
curator的分布式锁
同样假设现在有某个服务的线程A,向Zookeeper申请锁。他需要提供这个锁的路径,并要求Zookeeper生成一个临时有序节点。由于这个lock目前还没有创建过临时节点,所以假设这个临时有序节点是/lock/0001。Zookeeper创建后就会把这个路径返回给线程A。
线程A拿到路径后,还会去拿/lock下的所有节点(此时就/lock0001),并进行排序。
排序完看看当前的节点是不是第一个,由于目前就是第一个,所以线程A就相当于拿到了锁,把线程的信息、节点的路径保存在内存中,并默认lockCount的值为1。
如果线程A再申请锁,他就会先看看内存是否有当前线程和这个路径的信息,如果有,就把lockCount加1,此时lockCount就是2。
在线程A持有锁的时候,线程B也来申请锁。
由于这个lock已经有了0001的临时有序节点,所以Zookeeper就会创建lock0002并返回给线程B。
线程B拿到路径后,同样会去拿/lock下的所有节点(此时就有/lock0001和/lock0002),并进行排序。
很明显他不是第一个节点,所以拿不到锁,于是就监听lock0001,并进入了wait。
线程A释放锁的时候,就会把内存中的lockCount减1,然后判断lockCount是否大于0,如果大于0,说明之前重入过,还需要持有锁,如果小于0,说明已经释放完了,如果等于0,说明不需要加锁了,那就移除监听,并删除临时有序节点。
Zookeeper删除lock0001时,Zookeeper的watch机制就会通知到线程B,此时就会唤醒线程B,线程B会重新去Zookeeper拉取子节点列表,并排序,此时lock0002是第一个,所以他就获取到了锁,然后把线程的信息、节点的路径保存在内存中,并默认lockCount的值为1。
Zookeeper分布式锁的流程大概如上。
那为什么是临时节点?当线程A异常退出后,这个临时节点也会删除,这样就可以通过watch机制通知监听这个节点的服务。
为什么是顺序节点?临时节点也可以保证互斥性,但是这样就会很多个服务一直争着去创建这个节点。
所以Zookeeper的临时节点,可以不用像Redis一样,需要过期时间防死锁,Watch机制不用像Redis一样,需要监听狗来保持锁。
重入也有不一样的地方,Zookeeper是保存客户端,Redis是保存在Redis中。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。