Redis 锁
1.最普通的锁(单机) set nx 互斥锁 设置过期时间,释放锁 直接删除key 即可,可以使用lua脚本实现
set nx中的随机值的作用: 用来判断 删除锁时 当前的锁是不是 我获取的那个锁
问题:Redis 宕机了 锁就没了
2.RedLock
这个场景是假设有一个redis cluster,有5个redis master实例。然后执行如下步骤获取一把锁:
1)获取当前时间戳,单位是毫秒
2)跟上面类似,轮流尝试在每个master节点上创建锁,过期时间较短,一般就几十毫秒
3)尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)
4)客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
5)要是锁建立失败了,那么就依次删除这个锁
6)只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁
RedLock算法作用: 防止redis单例挂掉,获取不了锁和不能释放锁
redis分布式锁的方案:
1.单例
2.主从架构 +哨兵 高可用,依然存在master宕机,锁数据还没复制到slave节点, 还是会有漏洞
3.多master集群 cluster 或者 使用twitter开源的twemproxy做客户端集群分片 :
redlock ,每个master 都要加锁(网络等情况)可能导致每个master上的锁过期时间不一样,到时候会被其他线程获取到锁。(不够健壮并且比较复杂)
4.redisson 也是比较知名的redis客户端类库,可重入锁、读写锁、公平锁、信号量、CountDownLatch,很多种复杂的锁的语义,可以支持我们将分布式锁玩儿的非常的好,
可以用 redis的Jedis + Redisson结合起来,Jedis封装redis的一些基础的语义,一些操作,都是不错的
ZK锁
zk分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新枷锁。
有两种实现方式:
1.全部监听锁节点,以前比较早期都是这么实现,监听的请求太多 会引起惊群效应,会并发争抢,性能比较低
2.按节点大小顺序监听排在自己前面的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 ZooKeeper 给通知
但是,使用 zk 临时节点会存在另一个问题:由于 zk 依靠 session 定期的心跳来维持客户端,如果客户端进入长时间的 GC,可能会导致 zk 认为客户端宕机而释放锁,让其他的客户端获取锁,但是客户端在 GC 恢复后,会认为自己还持有锁,从而可能出现多个客户端同时获取到锁的情形。
1.zk其他没有获取到锁的线程全部监听同一个node,引起惊群效应
优化 顺序节点实现监听
(3)redis分布式锁和zk分布式锁的对比
1. 性能对比:
redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能
zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小
2.释放时间对比:
另外一点就是,如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁
3.实现机制对比:
redis分布式锁大家每发现好麻烦吗?遍历上锁,计算时间等等。。。zk的分布式锁语义清晰实现简单
所以先不分析太多的东西,就说这两点,我个人实践认为zk的分布式锁比redis的分布式锁牢靠、而且模型简单易用
redis 分布式锁
重入锁源码总结
做一个总结,从实现原理以及源码的层面,真正剖析和了解到了redis分布式锁的企业级的实现,这个分布式锁实现的还是非常漂亮的,麻雀虽小,五脏俱全,分布式的可重入锁,总结一下redis
(1)加锁:在redis里设置hash数据结构,生存周期是30000毫秒
(2)维持加锁:代码里一直加锁,redis里的key会一直保持存活,后台每隔10秒的定时任务(watchdog)不断的检查,只要客户端还在加锁,就刷新key的生存周期为30000毫秒
(3)可重入锁:同一个线程可以多次加锁,就是在hash数据结构中将加锁次数累加1
(4)锁互斥:不同客户端,或者不同线程,尝试加锁陷入死循环等待
(5)手动释放锁:可重入锁自动递减加锁次数,全部释放锁之后删除锁key
(6)宕机自动释放锁:如果持有锁的客户端宕机了,那么此时后台的watchdog定时调度任务也没了,不会刷新锁key的生存周期,此时redis里的锁key会自动释放
(7)尝试加锁超时:在指定时间内没有成功加锁就自动退出死循环,标识本次尝试加锁失败
(8)超时锁自动释放:获取锁之后,在一定时间内没有手动释放锁,则redis里的key自动过期,自动释放锁
这8大机制,组合在一起,才是构成了一个企业级的基于redis的分布式锁的方案
redisson基于redis实现的分布式锁的核心原理给搞通透了,后续我们再看其他的锁,包括公平锁、读写锁、MultiLock、RedLock这一系列的源码的时候,就比较得心应手了,分析源码会稍微快一些,主要分析各种不同的锁之间的实现原理的区别
redis加锁,本质,还是在redis集群中挑选一个master实例来加锁,master -> slave,实现了高可用的机制,如果master宕机,slave会自动切换为master
假设客户端刚刚在master写入一个锁,此时发生了master的宕机,但是master还没来得及将那个锁key异步同步到slave,slave就切换成了新的master。此时别的客户端在新的master上也尝试获取同一个锁,会成功获取锁
此时两个客户端,都会获取同一把分布式锁,可能有的时候就会导致一些数据的问题
redisson的分布式锁,隐患主要就是在这里
FairLock 公平锁
其实就是基于重入锁的 获取锁失败的线程加入队列,排队获取锁
MultiLock 联锁
基于重入锁的基础之上 去连续获取多个锁,释放锁也是 遍历释放锁
RedLock 红锁 原理
这个场景是假设有一个redis cluster,有3个redis master实例
然后执行如下步骤获取一把分布式锁:
1)获取当前时间戳,单位是毫秒
2)跟上面类似,轮流尝试在每个master节点上创建锁,过期时间较短,一般就几十毫秒,在每个节点上创建锁的过程中,需要加一个超时时间,一般来说比如几十毫秒如果没有获取到锁就超时了,标识为获取锁失败
3)尝试在大多数节点上建立一个锁,比如3个节点就要求是2个节点(n / 2 +1)
4)客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
5)要是锁建立失败了,那么就依次删除已经创建的锁
6)只要别人创建了一把分布式锁,你就得不断轮询去尝试获取锁
他这里最最核心的一个点,普通的redis分布式锁,其实是在redis集群中根据hash算法选择一台redis实例创建一个锁就可以了
RedLock算法思想,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁
RedissonRedLock锁的实现,非常的简单,他是RedissonMultiLock的一个子类,RedLock算法的实现,是依赖于MultiLock的一个机制来实现的
重载了failedLocksLimit calcLockWaitTime 方法
failedLocksLimit ( n - (n / 2 + 1) )允许加锁失败的数量 MultiLock是不允许失败的
calcLockWaitTime ( waitTime / n ) 计算每个锁加锁的超时时间, MultiLock 是 只算总的加锁 超时时间
判断逻辑就会根据这个超时时间 和 锁失败数量去判断
这个就是MultiLock 和 RedLock的区别
zookeeper分布式锁(一):可重入锁源码剖析
总结:
0.节点存在就重复加锁
1.创建节点
2.获取临时顺序节点列表
3.获取创建的节点 在列表的位置 ourIndex
4. 是否可以获取锁判断 maxLeases(最大可以获取锁的实例)
boolean getsTheLock = ourIndex < maxLeases;
5.获取锁成功直接返回
6.获取锁失败 获取上一个节点并且监听上一个节点删除事件
7.wait 阻塞(被唤醒后 继续while循环 尝试获取锁)
8.释放锁 锁的重入次数还大于0 就直接返回,释放锁 删除节点,删除threadData(ConcurrentMap<Thread, Node>)
9.获取锁失败的线程 被监听器 唤醒 notifyAll
10.重新尝试获取锁
redis锁:
1.实现方案
2.架构部署 和 锁的关系
3.获取锁 释放锁 原理
4.原理图
zookeeper:
1.实现方案
2.获取锁释放锁原理
3.原理图
redis 和zookeeper 对比