分布式锁
1、靠谱分布式锁具有的特征
「互斥性」: 任意时刻,只有一个客户端能持有锁。
「锁超时释放」:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
「可重入性」:一个线程如果获取了锁之后,可以再次对其请求加锁。
「高性能和高可用」:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
「安全性」:锁只能被持有的客户端删除,不能被其他客户端删除
除了以上特性外,这把锁最好是阻塞的和公平的。
常见分布式锁方案对比
分类 | 方案 | 实现原理 | 优点 | 缺点 |
---|---|---|---|---|
基于数据库 | 基于mysql 表唯一索引 | 1.表增加唯一索引 2.加锁:执行insert语句,若报错,则表明加锁失败 3.解锁:执行delete语句 | 完全利用DB现有能力,实现简单 | 1.锁无超时自动失效机制,有死锁风险 2.不支持锁重入,不支持阻塞等待 3.操作数据库开销大,性能不高 |
基于MongoDB findAndModify原子操作 | 1.加锁:执行findAndModify原子命令查找document,若不存在则新增 2.解锁:删除document | 实现也很容易,较基于MySQL唯一索引的方案,性能要好很多 | 1.大部分公司数据库用MySQL,可能缺乏相应的MongoDB运维、开发人员 2.锁无超时自动失效机制 | |
基于分布式协调系统 | 基于ZooKeeper | 1.加锁:在/lock目录下创建临时有序节点,判断创建的节点序号是否最小。若是,则表示获取到锁;否,则则watch /lock目录下序号比自身小的前一个节点 2.解锁:删除节点 | 1.由zk保障系统高可用 2.Curator框架已原生支持系列分布式锁命令,使用简单 | 需单独维护一套zk集群,维保成本高 |
基于缓存 | 基于redis命令 | 1. 加锁:执行setnx,若成功再执行expire添加过期时间 2. 解锁:执行delete命令 | 实现简单,相比数据库和分布式系统的实现,该方案最轻,性能最好 | 1.setnx和expire分2步执行,非原子操作;若setnx执行成功,但expire执行失败,就可能出现死锁 2.delete命令存在误删除非当前线程持有的锁的可能 3.不支持阻塞等待、不可重入 |
基于redis Lua脚本能力 | 1. 加锁:执行SET lock_name random_value EX seconds NX 命令 2.解锁:执行Lua脚本,释放锁时验证random_value -- ARGV[1]为random_value, KEYS[1]为lock_name if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end | 同上;实现逻辑上也更严谨,除了单点问题,生产环境采用用这种方案,问题也不大。 | 1.单点故障: 加锁成功后,锁从master复制到slave的时候挂了,会出现同一资源被多个client加锁。 2.锁过期问题:执行时间超过了锁的过期时间。为了不出现一直上锁的情况,加了一个兜底的过期时间,时间到了锁自动释放,但是,如果在这期间任务并没有做完怎么办?由于GC或者网络延迟导致的任务时间变长,很难保证任务一定能在锁的过期时间内完成。 |
如何解决Rdis Lua脚本实现分布式锁的两个问题呢?官方推荐使用Redlock算法。
对于第一个单点问题,顺着redis的思路,接下来想到的肯定是Redlock了。Redlock为了解决单机的问题,需要多个(大于2)redis的master节点,多个master节点互相独立,没有数据同步。
Redlock的实现如下:
1)获取当前时间。
2)依次获取N个节点的锁。每个节点加锁的实现方式同上。这里有个细节,就是每次获取锁的时候的过期时间都不同,需要减去之前获取锁的操作的耗时:
比如传入的锁的过期时间为500ms;
获取第一个节点的锁花了1ms,那么第一个节点的锁的过期时间就是499ms;
获取第二个节点的锁花了2ms,那么第二个节点的锁的过期时间就是497ms;
如果锁的过期时间小于等于0了,说明整个获取锁的操作超时了,整个操作失败。
3)判断是否获取锁成功。如果client在上述步骤中获取到了(N/2 + 1)个节点锁,并且每个锁的过期时间都是大于0的,则获取锁成功,否则失败。失败时释放锁。
4)释放锁。对所有节点发送释放锁的指令,每个节点的实现逻辑和上面的简单实现一样。为什么要对所有节点操作?因为分布式场景下从一个节点获取锁失败不代表在那个节点上加速失败,可能实际上加锁已经成功了,但是返回时因为网络抖动超时了。
以上就是大家常见的redlock实现的描述了,一眼看上去就是简单版本的多master版本,如果真是这样就太简单了,接下来分析下这个算法在各个场景下是怎样被玩坏的。