分布式锁工具
一、背景
当前问题:项目中会使用到分布式锁用于定时任务、接口幂等性处理,但是分布式锁的实现较简单,会出现执行超时、加解锁失败等场景。分布式锁都有哪些实现,他们的优劣势是什么呢?
二、现有技术
分布式锁实现目的如下图:
1.reids分布式锁实现
- 第一种直接通过redis提供的setNx命令执行加锁,可以设置value值。不能设置锁的有效期,如果不调用del命令,会一直存在。
- 第二种通过lua脚本执行加锁。可以直接使用set命令进行加锁,然后设置锁的有效期,也可以使用hashSet方式,将所有的锁都保存在同一个hash数组中,通过hash中的key值进行区分,缺点是在redis中hash类型key值有效期是针对整个hashKey的。
以上两种方式都能完成加锁操作,第二种能够自由设置锁的有效期。
基于jimdb的加锁实现方式比较如下
- jimdb事务方式:批量传入多个命令后,不间断的执行传入的命令,但不保证原子性
- pipeline方式:批量传入多个命令后,与其他请求竞争后执行命令,不保证原子性
- lua脚本方式:批量传入多个命令后,原子性的执行
- set命令,jimdb直接支持setEx命令,可以在设置锁的同时设置锁的有效期,不支持重入
为什么不能先执行setNx,然后在执行expire命令,这样第一种也有有效期了呢?两个命令分开执行不具有原子性,比如加锁成功了,但在执行expire命令时失败了,还是不具有有效期。
优缺点:
- 缺点:即使设置有效期成功了,如果操作共享资源的时间大于过期时间,就会出现锁提前过期的问题,进而导致分布式锁直接失效。如果锁的超时时间设置过长,加过期时间与不加没有了区别。
- 优点:通过lua脚本可以实现原子性
2.redisson实现方式
需要手动初始化RedissonLock,然后进行lock操作,内部通过lua脚本的方式进行加锁,方法结束后再finally中进行unlock解锁。如果加锁时不指定过期时间,会自动续期。加锁失败会自动重试
缺点:1.目前redisson未解决redLock问题 2.redisson未解决执行超时情况 3.仅支持redis
3.zookeeper分布式锁实现
ZooKeeper 分布式锁是基于临时顺序节点和Watcher(事件监听器)实现的。
- 临时节点:临时节点的生命周期是与客户端会话(session)绑定的,会话消失则节点消失。并且,临时节点只能做叶子节点,不能创建子节点。
- Watcher事件监听:是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
获取锁:
- 首先要有一个持久节点
/locks
,客户端获取锁就是在locks
下创建临时顺序节点。 - 假设客户端 1 创建了
/locks/lock1
节点,创建成功之后,会判断lock1
是否是/locks
下最小的子节点。 - 如果
lock1
是最小的子节点,则获取锁成功。否则,获取锁失败。 - 如果获取锁失败,则说明有其他的客户端已经成功获取锁。客户端 1 并不会不停地循环去尝试加锁,而是在前一个节点比如
/locks/lock0
上注册一个事件监听器。这个监听器的作用是当前一个节点释放锁之后通知客户端 1(避免无效自旋),这样客户端 1 就加锁成功了。
释放锁:
- 成功获取锁的客户端在执行完业务流程之后,会将对应的子节点删除。
- 成功获取锁的客户端在出现故障之后,对应的子节点由于是临时顺序节点,也会被自动删除,避免了锁无法被释放。
- 事件监听器监听的就是这个子节点删除事件,子节点删除就意味着锁被释放。
优缺点:性能是唯一缺点,另外仅需要分布式锁功能而搭建zooker集群是不划算的。
4.数据库分布式锁
在数据库中增加一张锁表。
- 简单:每次加锁时,插入一条数据,解锁时删除数据。
- 使用悲观锁。通过for update增加悲观锁。
- 使用乐观锁,表中增加版本号的概念。
优点:不需要额外部署外部服务
缺点:数据库的各种实现方式均有一些问题,通用的缺点就是性能不高,同时为数据库带来很大压力。在实现可重入、自动解锁、自动重试加锁等功能上均比较复杂。写主读从架构上,悲观锁还要考虑主从同步问题。
5.ETCD分布式锁
1.什么是etcd?
etcd 是一个分布式可靠的键值存储,用于存储分布式系统中最重要的数据,重点在于:
- 简单:定义明确、面向用户的 API(gRPC)
- 安全:自动 TLS,带有可选的客户端证书身份验证
- 快速:基准速度为 10,000 次写入/秒
- 可靠:使用 Raft 分布式协议
etcd 用 Go 编写,使用Raft共识算法来管理高可用性复制日志。
etcd 特性:
- 租约机制:用于支撑异常情况下的锁自动释放能力。可以自由设置过期时间
- 前缀和 Revision 机制:用于支撑公平获取锁和排队等待的能力,通过前缀和获取,通过revision进行顺序执行
- 监听机制:用于支撑抢锁能力
- 集群模式:用于支撑锁服务的高可用
2.如何用etcd实现分布式锁?
优点:通过raft协议保证一致性,防止脑裂,惊群效应,自带续期功能,性能高,高可用。
缺点: 吞吐量低(相比redis)。公司提供etcd集群使用,但是之前出现过购物车开天窗问题,集团不允许在高负载情况下请求直接打到etcd,所以这是唯一缺点。
6.分布式锁横向对比
三、分布式锁工具结论
方案:以上对比可知,在较低低吞度量场景下,etcd的实现方式是最好的
四、参考资料
- redis官方文档:Distributed Locks with Redis | Docs
- 分布式锁中的王者方案 - Redisson_redis_悟空聊架构_InfoQ写作社区
- Redis 分布式锁|从青铜到钻石的五种演进方案 - 悟空聊架构的个人空间 - OSCHINA - 中文开源技术交流社区
- jimdb支持命令:https://cf.jd.com/pages/viewpage.action?pageId=584518003
- jimdb帮助文档:登录JoySpace
- jimdb事务:事务 — Redis 设计与实现
- redisson:https://github.com/redisson/redisson/wiki
- etcd:GitHub - etcd-io/etcd: Distributed reliable key-value store for the most critical data of a distributed system
- etcd实现分布式锁:https://www.51cto.com/article/722138.html
关于redLock:
- How to do distributed locking — Martin Kleppmann’s blog
- Is Redlock safe? - <antirez>
- http://zhangtielei.com/posts/blog-redlock-reasoning.html、基于Redis的分布式锁到底安全吗(下)? - 铁蕾的个人博客 对于第一个和第二个的中文翻译和总结。。