Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。
Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
为什么要使用分布式锁,解决了什么问题?
普通锁:
我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug!
注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间!
分布式锁:
普通锁的使用堆内存中的变量的方式肯定不适用了。某台计算机上的堆内存中的变量对于其他计算机上的线程肯定是不可见的。那么,根据锁的本质和原理,我们就要找到另外的对于多机上的线程都可见的标志,以它来作为锁,就可以了。这样的锁,就是分布式锁。
分布式锁应该具备哪些条件?
1.在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2.高可用的获取锁与释放锁;
3.高性能的获取锁与释放锁;
4.具备可重入特性;
5.具备锁失效机制,防止死锁;
6.具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
Redisson原理分析:
1.加锁机制
2.互斥机制
3.watch dog自动延期机制
4.可重入加锁机制
5.锁释放机制
加锁机制:
线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库;
线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库;
如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器,发送一段lua脚本到redis上;
![](https://i-blog.csdnimg.cn/blog_migrate/0570c81f32f7c141134bafee013eab89.png)
互斥机制:
客户端A已加锁成功情况下,如果客户端B来尝试加锁,执行以下同样一段lua脚本,会怎么样呢?第一个if判断会执行“exists myLock”,发现myLock这个锁key已经存在了。接着第二个if判断,判断一下,myLock锁key的hash数据结构中,是否包含客户端B的ID,但是明显不是的,因为那里包含的是客户端A的ID。
![](https://i-blog.csdnimg.cn/blog_migrate/11b6c7e22c28dd0ade2cf8cf0f9b747b.png)
所以,客户端B会获取到pttl myLock返回的一个数字,这个数字代表了myLock这个锁key的剩余时间。比如还剩15秒的生存时间。如客户端B未设置leaseTime并传入了waitTime,此时客户端B会进入一个while循环,不停的尝试加锁。
watch dog自动延期机制:
在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生。
如果客户端A一旦加锁成功(leaseTime等于-1),就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间。
![](https://i-blog.csdnimg.cn/blog_migrate/d698c9d986c6b00a74f8a6aee6c973ea.png)
可重入加锁机制:
能重入加锁的设计妙处
1.Redis存储锁的数据类型是 Hash类型
2.Hash数据类型的key值包含了当前线程信息
![](https://i-blog.csdnimg.cn/blog_migrate/7322f7bc5c862fea0c654dba23dfbd15.png)
key: try-lock-1
haskKey: fff2f4af-ac49-48b9-868b-7bf07134d310:91 客户端UUID:当前线程ID
value: +1 or -1 重入锁关键
![](https://i-blog.csdnimg.cn/blog_migrate/cf05bda0fbd2ed46042eff2d435d7800.png)
锁释放机制:
1.判断key是否存在,不存执行publish命令发布释放锁消息并返回1;
2. key存在,但field在hash中不存在,说明自己不是锁持有者,返回null;
3.因为支持锁重入,每次释放一把锁并对锁的值减1;
4.如果还有剩余的锁,则重置锁的失效时间并返回0 else 通过del命名删除key,发布释放锁消息并返回1;
![](https://i-blog.csdnimg.cn/blog_migrate/a4f54a6a09c428ea0b4b97f43ec416cc.png)
Redisson分布式锁的问题?
由于Redisson的加锁方式,本质上时是判断某个Redis节点(主节点)是否具有某个key,且Redis集群间会发生异步的主从复制行为,可能会出现重复加锁的问题。
在极端情况下,客户端A加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点。
客户端B再次加锁,在新的master节点上加锁也也会成功,这个时候客户端B也会认为加锁成功,出现两个节点同时持有一把锁的问题,此时就会产生脏数据的问题。
如何解决?
作者提出Redisson RedLock解决,存在的问题?
1.需要提供多套Reids主从部署架构,这多套Redis主从架构中的Master节点必须都是独立的,相互之间没有任何数据交互。
2.使用红锁后,当加锁成功的RLock个数不超过总数的一半时,会返回加锁失败,即使在业务层面任务加锁成功了,但是红锁也会返回加锁失败的结果。
3.高并发场景下会影响性能。
4.需要开发者来保证key分散不在不同的master节点上。
Redisson RedLock 已经被弃用,直接使用普通的加锁即可,会基于 wait 机制将锁同步到从节点,但是也并不能保证一致性。仅仅是最大限度的保证一致性。
具体可以查看redisson的github:https://github.com/redisson/redisson/issues/2669
如果业务对数据一致性要求很高,则可以在业务层面做幂等,redisson基于AP无法保证一致性, 对并发要求不高的情况下也可以考虑基于CP的zookeeper作为分布式锁