这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排( 二 )


文章插图
 
打开 Redis Desktop Manager 工具,看下到底它存的是什么 。原来在加锁的时候,写入了一个 HASH 类型的值,key 是锁名称 jackxu,field 是线程的名称,而 value 是 1(即表示锁的重入次数) 。

这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
小伙伴可能觉得我在一派胡言,没关系,我们点进去看下它的源码是具体实现的 。
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
点进 tryLock() 方法的 tryAcquire() 方法,再到->tryAcquireAsync() 再到->tryLockInnerAsync(),终于见到庐山真面目了,原来它最终也是通过 Lua 脚本来实现的 。
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
现在我把这段Lua脚本拿出来分析一下,很简单 。
// KEYS[1] 锁名称 updateAccount// ARGV[1] key 过期时间 10000ms// ARGV[2] 线程名称// 锁名称不存在if (redis.call('exists', KEYS[1]) == 0) then// 创建一个 hash,key=锁名称,field=线程名,value=https://www.isolves.com/it/cxkf/bk/2020-07-29/1redis.call('hset', KEYS[1], ARGV[2], 1);// 设置 hash 的过期时间redis.call('pexpire', KEYS[1], ARGV[1]);return nil;end;// 锁名称存在,判断是否当前线程持有的锁if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then// 如果是,value+1,代表重入次数+1redis.call('hincrby', KEYS[1], ARGV[2], 1);// 重新获得锁,需要重新设置 Key 的过期时间redis.call('pexpire', KEYS[1], ARGV[1]);return nil;end;// 锁存在,但是不是当前线程持有,返回过期时间(毫秒)return redis.call('pttl', KEYS[1]);unlock() 中的 unlockInnerAsync() 释放锁,同样也是通过 Lua 脚本实现 。
// KEYS[1] 锁的名称 updateAccount// KEYS[2] 频道名称 redisson_lock__channel:{updateAccount}// ARGV[1] 释放锁的消息 0// ARGV[2] 锁释放时间 10000// ARGV[3] 线程名称// 锁不存在(过期或者已经释放了)if (redis.call('exists', KEYS[1]) == 0) then// 发布锁已经释放的消息redis.call('publish', KEYS[2], ARGV[1]);return 1;end;// 锁存在,但是不是当前线程加的锁if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) thenreturn nil;end;// 锁存在,是当前线程加的锁// 重入次数-1local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);// -1 后大于 0,说明这个线程持有这把锁还有其他的任务需要执行if (counter > 0) then// 重新设置锁的过期时间redis.call('pexpire', KEYS[1], ARGV[2]);return 0;else// -1 之后等于 0,现在可以删除锁了redis.call('del', KEYS[1]);// 删除之后发布释放锁的消息redis.call('publish', KEYS[2], ARGV[1]);return 1;end;// 其他情况返回 nilreturn nil;看完它的使用后,我们发现真的使用起来像 JDK 中的 ReentrantLock 一样丝滑 。
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
RedLockRedLock 的中文是直译过来的,就叫红锁 。红锁并非是一个工具,而是 Redis 官方提出的一种分布式锁的算法 。我们知道如果采用单机部署模式,会存在单点问题,只要 redis 故障了,加锁就不行了 。如果采用 master-slave 模式,加锁的时候只对一个节点加锁,即便通过 sentinel 做了高可用,但是如果 master 节点故障了,发生主从切换,此时就会有可能出现锁丢失的问题 。基于以上的考虑,其实 redis 的作者 Antirez 也考虑到这个问题,他提出了一个 RedLock 的算法 。
我在这里画了一个图,图中这五个实例都是独自部署的,没有主从关系,它们就是5个 master 节点 。
这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排

文章插图
 
通过以下步骤获取一把锁:
  • 获取当前时间戳,单位是毫秒
  • 轮流尝试在每个 master 节点上创建锁,过期时间设置较短,一般就几十毫秒
  • 尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)
  • 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
  • 要是锁建立失败了,那么就依次删除这个锁
  • 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁
但是这样的这种算法还是颇具争议的,可能还会存在不少的问题,无法保证加锁的过程一定正确 。Martin Kleppmann 针对这个算法提出了质疑,接着 antirez 又回复了 Martin Kleppmann 的质疑 。一个是很有资历的分布式架构师,一个是 Redis 之父,这个就是著名的关于红锁的神仙打架事件 。
最后 Redisson 官网上也给出了如何使用红锁 redlock,几行代码搞定,依然很丝滑,感兴趣的小伙伴可以看下 。


推荐阅读