3.3 KiB
3.3 KiB
基于数据库的分布式锁实现方案
获得锁:表中插入一条记录 释放锁:删除该记录
问题:
- 为了预防单点故障,数据库可以配置主从
- 为了预防超时问题,可以设置时间戳等方式删除超时数据
- 不可重入:同一个线程在没有释放锁之前不能再次获得该锁。实现可重入需要改造加锁方法,增加存储和判断线程信息
- 阻塞等待问题:使用数据库获取锁的时候,如果获取失败会直接返回,因此需要轮询获取锁
- 主从问题,在高并发的场景下,数据库主从延时增大,线程读取的数据非最新版,导致锁重复。
基于Zookeeper的分布式锁实现方案
独占锁-使用临时节点实现
获得锁: 当要对某个资源加锁时,Zookeeper在该资源对应的指定的节点目录下,生成一个唯一的临时节点。其他客户端对该节点设置一个Watcher通知。 释放锁:Zookeeper删除对应的临时节点,其他客户端可以监听到节点被删除的通知,并重新竞争锁。
读写锁-使用临时有序节点实现
获得读锁:
- 获得临时有序节点,并标记为读锁
- 获取资源路径下所有的子节点,从小到大排序。
- 获取当前临近节点前的临近写锁节点。
- 如果不存在临近写锁节点,则成功获得读锁
- 如果存在临近写锁节点,则设置Water监听该节点的删除事件。
- 一旦监听到删除事件,重复2,3,4,5的步骤。
获得写锁:
- 创建临时有序节点,并标记为写锁。
- 获取路径下的所有子节点,并进行从小到大排序。
- 获取当前节点的临近的写锁节点和读锁节点。
- 如果不存在临近节点,则成功获取锁。
- 如果存在临近节点,对其进行监听删除事件。
- 一旦监听到删除事件,重复2,3,4,5的步骤(递归)。
释放锁:
- 删除对应的临时节点。
基于Redis的分布式锁实现方案
原理:在获取锁之前,先查询一下以该锁为key对应的value是否存在,若存在,说明该锁被其他客户端获取了。
改进1:为了防止获取锁的客户端突然宕机,需要在设置key的时候,指定一个过期时间,以确保即使宕机了,锁也能最后释放。通过SETNX命令设置key的值,通过EXPIRE命令设置过期时间。
改进2:由于SETNX和EXPIRE命令的执行不是原子性的,多个客户端在检验锁是否存在时会导致多个客户端都认为自己能获取到锁。Redis提供了Set原子性命令,在设置值的同时指定过期时间。
改进3:客户端获取锁以后任务未执行完,但锁已经过期,被别的客户端获取了,这时客户端扔会释放锁,导致锁失效。可以在设置key的时候,设置value为一个随机值r,删除的时候先比较一下redis里的value是否为r再决定删除。
改进4:客户端先比较一下redis的value是否为r再决定删除,但由于比较后再删除锁不是原子的。在比较过程中,key有可能因过期而被清除了导致一个客户端就可以获取到key。Redis并没有提供相关的比较后删除的原子操作。此时释放锁的过程可以使用lua脚本,redis将lua脚本的命令视为一个原子操作。 Redis可以原子执行lua脚本