一、Redission在SpringBoot中的集成
1.导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
2.创建Redis客户端
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");//.setPassword("123456");
return Redisson.create(config);
}
}
3.实现分布式锁
@Autowired
private RedissonClient redissonClient;
@Test
public void testLock(){
RLock rLock = redissonClient.getLock("lock_stock");
//阻塞式等待,过期时间30s
rLock.lock();
try{
//执行业务
}finally {
//释放锁
rLock.unlock();
}
}
二、Redission实现分布式锁底层原理
如果负责存储分布式锁的Redission节点发生了宕机,并且锁没有被释放,就会出现死锁的现象。因此,为防止这种现象的产生,Redission有一个看门狗的机制,用来监控锁,若Redission实例未关闭,就会不断的延长锁的有效期,防止程序执行期间自动过期删除的问题。
Redisson加锁有以下两种情况:
(1)设置了过期时间
若设置了过期时间,会把设定的时间作为过期时间,然后使用Lua脚本获取到锁,未获取到锁的线程则会自旋重入,不停地尝试获取锁。
如果我们通过lease Time(rLock.lock(20, TimeUnit.SECONDS))的参数指定了加锁的时间,那么Redisson就不会再进行续期了,锁到达过期时间会自动释放锁,无需unlock手动解锁,如果出现锁的误删除情况时,Redisson会抛出异常。
(2)未设置过期时间
未设置过期时间的,Redisson加锁会有一个默认的过期时间为30s,线程获取到锁后,用了Lua来保证原子性,会开启一个定时任务,每隔10s看门狗会执行一次定时任务,若锁还在,重新将过期时间设置成30s。
三、Redisson锁的分类
1.可重入锁
@Autowired
private RedissonClient redissonClient;
@Test
public void testLock(){
//获取锁
RLock rLock = redissonClient.getLock("lock_stock");
//加锁,锁的过期时间是10s,10s后锁自动释放
rLock.lock(10, TimeUnit.SECONDS);
try{
//执行业务
}finally {
//释放锁,设置了锁的过期时间,不会手动调这个方法释放锁
rLock.unlock();
}
}
2.公平锁
公平锁保证了当多个Redisson客户端线程同时请求加锁时,会按照请求的顺序进行加锁,遵循先到先得的原则。
@Autowired
private RedissonClient redissonClient;
@Test
public void testLock5() {
RLock fairLock = redissonClient.getFairLock("anyLock");
try{
fairLock.lock();
}finally {
//释放锁
fairLock.unlock();
}
}
3.联锁
通过RedissonMultiLock对象可以将多个RLock锁关联为一个联锁,其中每个RLock对象实例都可以来自不同的Redisson实例。
RLock lockA = redissonInstanceA.getLock("lockA");
RLock lockB = redissonInstanceB.getLock("lockB");
RLock lockC = redissonInstanceC.getLock("lockC");
// 同时加锁:lockA, lockB, lockC
RedissonMultiLock lock = new RedissonMultiLock(lockA, lockB, lockC);
// 所有的都加锁成功才是成功
lock.lock();
// 释放锁
lock.unlock();
4.红锁
可以将多个RLock关联成一个红锁,每个RLock对象实例可以来自于不同的Redisson实例,RedLock可以保证以下特性:
(1)容错性:只要大多数节点的redis实例能正常运行就可以加锁和释放锁。
(2)互斥性:只有一个客户端可以获取到锁,即使发生宕机,也不会造成死锁。
(3)一致性:降低了数据不一致的可能,但不能完全保证数据的一致性。
RLock lockA = redissonInstanceA.getLock("lockA");
RLock lockB = redissonInstanceB.getLock("lockB");
RLock lockC = redissonInstanceC.getLock("lockC");
// 同时加锁:lockA, lockB, lockC
RedissonRedLock = new RedissonRedLock(lockA, lockB, lockC);
// 大部分节点加锁成功就算成功
lock.lock();
// 释放锁
lock.unlock();
5.闭锁
Redisson提供了RCountDownLatch
接口,它是一个计数降低到0时触发的锁。它可以实现在多个线程都执行完才结束的效果,否则就会闭锁来等待。
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
//设置数量为2
latch.trySetCount(2);
//await方法等待其他线程完成所有的trySetCount(2)就会结束闭锁
latch.await();
//在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
//完成第1个countDown
latch.countDown();
// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
//完成第2个countDown,闭锁完成
latch.countDown();
6.读写锁
Redisson的读写锁RReadWriteLock Java对象实现了ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。
分布式的读写锁允许同时有多个读锁和一个写锁处在加锁的状态,也就是使用同一个RReadWriteLock来加写锁和读锁,读锁需要等待写锁释放锁之后才能够加锁成功。
@Autowired
private RedissonClient redissonClient;
@Test
public void testWriteLock() {
//获取读写锁
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("ReadWriteLock");
//获取写锁
RLock rLock = readWriteLock.writeLock();
try{
//加写锁,读等待
rLock.lock();
Thread.sleep(200000);
//执行写业务
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rLock.unlock();
}
}
@Test
public void testReadLock() {
//获取读写锁
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("ReadWriteLock");
//获取读锁
RLock rLock = readWriteLock.readLock();
try{
//加上读锁,若写锁没释放,等待
rLock.lock();
//处理读业务
}finally {
rLock.unlock();
}
}
7.信号量
Redisson的信号量可以看做是在Redis中保存了一个数字,可以实现原子性的加或减,比如取10件商品做库存,就可以把这个库存做成信号量,然后实现原子性加减。防止了库存出现负数的情况。
@Autowired
private RedissonClient redissonClient;
@Test
public void testReadLock() throws InterruptedException {
//获得1个信号量
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
//设置信号量的值
boolean setPermits = semaphore.trySetPermits(1000);
}
@Test
public void testReadLock6() throws InterruptedException {
//获得到1个信号量
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
//获取2个信号量 , 值会减去2 , 若获取不到,方法会阻塞
semaphore.acquire(2);
//尝试获取2个信号量 , 值会减去2 , 若获取不到,方法不会阻塞
boolean tryAccquireSuccess = semaphore.tryAcquire(2);
}
@Test
public void testReadLock7() throws InterruptedException {
//获得到1个信号量
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
//释放2个值,数量会加回去
semaphore.release(2);
}
除了上述的信号量,还提供了一个可过期的信号量,在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可通过ID来进行区分,释放时也只能通过提交的ID才能释放。
RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String id = semaphore.acquire();
// 获取1个信号,有效期只有1秒钟。
String id = semaphore.acquire(1, TimeUnit.SECONDS);
semaphore.release(id);