一、分布式锁
在单机场景下,可以使用Java里的内置所来实现进行同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
阻塞所通常使用互斥量来实现:
- 互斥量mutex为0表示有其他进程在使用锁,此时处于锁定状态。
- 互斥量mutex为1表示未锁定状态。
1和0可以用一个整形值表示,也可以用某个数据是否存在表示。
1、数据库的唯一索引
获取锁时向表中插入一条记录,释放锁时删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否在锁定状态。
存在以下几个问题:
- 锁没有失效时间,解锁失败的话,其他进程无法再获得该锁。
- 只能是非阻塞锁,插入失败直接就报错,无法重试。
- 不可重入,已经获得锁的进程也必须重新获取锁。
2、Redis的SETNX指令
使用SETNX(set if not exist)指令插入一个键值对,如果key已经存在,那么会返回false,否则插入成功并返回true。
SETNX指令和数据库的唯一索引类似,保证了只存在一个key的键值对,那么可以用一个key的键值对是否存在来判断是否存于锁定状态。
EXPIRE指令可以为一个键值对设置一个过期时间,从而避免了数据库唯一索引方式中释放锁失败的问题。也就是数据库的唯一索引解决方案的升级而已,只是可以设置过期时间。
3、Redis的RedLock算法
使用了多个Redis实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。多个Redis实例实现,也就是即使一台Redis挂了,还可以有其他的服务可用。
- 尝试从N个互相独立的Redis实例中获取锁。
- 计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数(N/2+1),半数以上的实例上获取锁,那么就认为获取锁成功了。
- 如果锁获取失败,就到每个实例上释放锁。
4、Zookeeper的有序节点
1、Zookeeper抽象模型
Zookeeper提供了一种树型结构的命名空间,/app1/p_1节点的父节点为app1。有点类似linux系统文件系统,文件目录树
2、节点类型
- 永久节点:不会因为会话结束或者超时而消失。
- 临时节点:如果会话结束或者超时就会消失。
- 有序节点:会在节点名的后面加上一个数字后缀,并且是有序的。例如生成的有序节点为/lock/node-0000000000,它的下一个有序节点就是/lock/node-0000000001,以此类推。
3、监听器
为一个节点注册监听器,在节点状态发生改变的时候,会给客户端发送消息 。
4、分布式锁实现
- 创建一个目录/lock。
- 当一个客户端需要获取锁时,在/lock下创建临时的且有序的子节点。
- 客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁。否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直到获取锁为止。
- 执行业务代码,完成后,删除对应的子节点。
5、会话超时
如果一个已经获的锁的会话超时了,因为创建的是临时节点,所以该会话对应的临时节点会被删除,其他会话就可以获取锁。可以看到zookeeper分布式锁不会出现数据库的唯一索引的分布式锁释放锁失败的问题。
6、羊群效应
一个节点未获得锁,只需要监听自己前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其他所有子节点都会收到通知(羊群效应),而我们秩序网它的后一个子节点收到通知。
- 本文作者: Victor Dan
- 本文链接: https://anonymousdq.github.io/victor.github.io/2019/11/01/一、分布式锁/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!