分布式锁
简介
分布式锁是分布式系统中必要且重要的控制资源并发访问的手段
要求
- 互斥性
- 不会发生死锁
- 可重入性
- 高效、高可用
固有缺陷
- 加锁不设置超时时间:客户端阻塞或宕机锁无法释放
- 加锁不设置线程标记:解锁时会发送误操作
- 超时时间设置:过长阻塞、过短锁泄漏
- 主从集群:主节点在确认加锁后宕机,从节点未确认加锁后升级成主节点发生锁泄漏
- 客户端发生阻塞:客户端发生GC或者网络等阻塞时,若此期间锁超时释放被其他客户端获取则发送锁泄漏
实现方式
- 完整实现:在获取锁的同时生成一个递增的序列号,每次资源发生修改的时候比较客户端所携带的序列号,早于当前资源版本的修改不被允许。
- 简易实现:在锁超时后还有一个lock-delay的时长,在lock-delay时间内其获取同一把锁会被拒绝,正常的释放锁则可以立刻被再次获取。
实现方案
- 数据库
- Redis
- ZooKeeper
- Google Chubby
数据库
使用数据库实现作为分布式锁大家都比较熟悉了,我们可以建立一块表来记录锁的资源、线程标记、重入次数、超时时间。
使用事务来保证客户端加解锁过程的原子性,使用定时任务来删除超时的锁。
- 优点
- 简单
- 无需额外的第三方组件
- 缺点
- 性能限制于数据库
- 强依赖单点服务
- 具有分布式锁固有缺陷3、4、5
Redis
使用Redis实现分布式锁大家也比较熟悉了,我们可以用常见的基于单机的setNxEx,也可以用基于集群的RedLock。下面简单介绍一下两种方式中比较重要的操作。
setNxEx
- 加锁时set if not exist 和set expire是原子操作
- 解锁时使用以下lua脚本保证原子操作
1
2
3
4
5if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
此种的优缺点与数据库实现基本相同,但是性能更好。
RedLock
- 获取当前时间
- 轮流在每台master上获取锁,获取锁超时时间应远小于锁超时时间M
- 计算获取锁成功的机器以及总的获取锁时间N,获取锁成功机器大于设置个数才成功
- 如果锁获取成功了,锁自动释放时间为M-N
- 获取锁失败了应在每台master上执行释放锁操作
- 锁使用完成释放也应该在每台master上执行释放操作,即使获取锁时失败的机器
- 优点
- 简单
- 使用集群可靠性更高
- 缺点
- 当获取锁成功的某台机器宕机后重启,会发送锁泄漏
- 当发生时间跳跃时,会发生锁泄漏
关于RedLock的有名的讨论,详见参考文档1、2中的说明。
ZooKeeper
使用ZooKeeper实现分布式锁也是常见的手段,这里同样只介绍比较重要的操作。
- 客户端尝试创建一个临时且有序的子节点
- 判断当前子节点是否为序号最小的子节点,是则获取锁成功,否则监听节点变更直到获取锁成功
- 当客户端失去心跳则临时子节点会被删除
- 监听节点变化应当只监听有序子节点列表的前一个,如果监听子节点则会发送羊群效应
- 优点
- 使用集群可靠性更高
- 缺点
- 当发生时间跳跃时,会发生锁泄漏
- 具有分布式锁固有缺陷5
Google Chubby
Chubby是Google内部使用的分布式锁系统,也是较全面的实现了两种方式的系统,实现方式章节的内容就是取自Chubby系统的思路。
具体的实现操作这里不做介绍,详见参考文档6中的说明。
参考文档
1 基于Redis的分布式锁到底安全吗(上)
2 基于Redis的分布式锁到底安全吗(下)
3 分布式之抉择分布式锁
4 Redis 分布式锁的正确实现方式( Java 版 )
5 基于Zookeeper的分布式锁
6 Chubby的锁服务
7 再有人问你分布式锁,这篇文章扔给他