本片文章为 分布式锁 相关的系列文章
本系列文章 将从 缓存的使用 本地锁 ** 一直写到 **分布式锁 以及相关的一些 知识点 问题
上一篇总结出Redis+LUA脚本只在单机版中 分布式锁有效 集群环境中可能出现问题 引出Redisson-redlock
上篇文章说到Redis+Lua脚本 做分布式锁的局限性 (在集群情况下失效)
解决方案
Redisson
- Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务
- Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
- github项目链接,https://github.com/redisson/redisson,~~我就不照本宣科了~~
内存数据网格 (百度百科):
- 内存网格(RAM(Random Access Memory)Grid)是一种面向广域网上内存资源共享的新型网格系统。它的主要目标是在物理内存不足的情况下,提高内存密集型应用或IO密集型应用的系统性能。内存网格的应用效果取决于网络通信开销。在减少或隐藏网络通信开销的情况下,其性能可以进一步提高。
分布式锁和同步器
- 可重入锁(Reentrant Lock)
基于Redis的Redisson分布式可重入锁
RLock
Java对象实现了java.util.concurrent.locks.Lock
接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
1
2
3
4
5 // Renntrant Lock 也称为 RLock
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();
lock.unlock(); 使用Reentrant Lock这种分布式锁的 Redisson 节点 宕机后 如果这个锁正好处在于 锁定 的状态 那么这个锁 就会变成 死锁
还有个问题 如果 给锁 增加了 过期时间 但是 过期时间 小于 业务执行时间 也就是说 业务还没有执行完毕 但是 设定的过期时间到了 此情况下 也会导致 锁资源被释放 并且在后面进入的线程 还会出现 误删除 其他锁的情况
Redisson 内部 提供了一个 监控锁的 看门狗 它的作用 是在 redisson 实例被关闭前 不断的延长锁的有效期 默认情况下 看门狗的检查锁的超时时间为30秒 也可以通过修改Config.lockWatchdogTimeout来另行制定
此外 Redisson 还通过 加锁的方式 提供了 leaseTime 的参数 来指定 加锁时间 超过这个时间后 锁 便自动解开
1
2
3
4
5
6
7
8
9
10
11
12
13 // 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}Redisson同时还为分布式锁提供了异步执行的相关方法
1
2
3
4 RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);RLock 对象 完全符合 Java的Lock规范 也就是说 只有拥有锁的 进程 才能解锁 其他进程解锁 会 抛出异常(IllegalMonitorStateException)如果遇到其他进程也能解锁的情况 请使用 分布式信号量Semaphore 对象
- 公平锁 (Fair Lock)
基于Redis的Redisson分布式可重入公平锁也是实现了
java.util.concurrent.locks.Lock
接口的一种RLock
对象。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。
1
2
3 RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock(); 大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了
leaseTime
的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
1
2
3
4
5
6
7
8 // 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:
1
2
3
4 RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
- 联锁 (MultiLock)
基于Redis的Redisson分布式联锁
RedissonMultiLock
对象可以将多个RLock
对象关联为一个联锁,每个RLock
对象实例可以来自于不同的Redisson实例。
1
2
3
4
5
6
7
8
9
10 RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 所有的锁都上锁成功才算成功。
lock.lock();
...
lock.unlock(); 同样 联锁 也有其他 Redisson的特性 (看门狗 以及 指定锁时间)
1
2
3
4
5
6
7
8 RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
- 红锁 (RedLock)
基于Redis的Redisson红锁
RedissonRedLock
对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock
对象关联为一个红锁,每个RLock
对象实例可以来自于不同的Redisson实例。
1
2
3
4
5
6
7
8
9
10 RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。(超过百分之五十)
lock.lock();
...
lock.unlock(); 同样 联锁 也有其他 Redisson的特性 (看门狗 以及 指定锁时间)
1
2
3
4
5
6
7
8 RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();大部分节点 上锁成功 那么就意味着 将会消耗大量的 redis 连接(redis connection)从而 增加了性能的损耗 换句话说 会损失性能
- 读写锁 (ReadWriteLock)
基于Redis的Redisson分布式可重入读写锁
RReadWriteLock
Java对象实现了java.util.concurrent.locks.ReadWriteLock (JUC) 接口。其中读锁和写锁都继承了RLock接口。 分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
1
2
3
4
5 RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock(); 同样 联锁 也有其他 Redisson的特性 (看门狗 以及 指定锁时间)
1
2
3
4
5
6
7
8
9
10
11
12 // 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
- 信号量 (Semaphore)
基于Redis的Redisson的分布式信号量(Semaphore)Java对象
RSemaphore
采用了与java.util.concurrent.Semaphore
相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();
- 可过期性信号量 (PermitExpirableSemaphore)
基于Redis的Redisson可过期性信号量(PermitExpirableSemaphore)是在 RSemaphore 对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放。它提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
1
2
3
4
5
6 RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// 获取一个信号,有效期只有2秒钟。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
- 闭锁 (CountDownLatch)
基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象 RCountDownLatch 采用了与 java.util.concurrent.CountDownLatch 相似的接口和用法。
1
2
3
4
5
6
7 RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
看到上述的分布式锁和同步器 让我想到了JUC相关的知识点