前言:
如果你的简历中写了做过电商项目,那么面试官基本都会从SpringBoot、SpringCloud以及Dubbo这些微服务架构涉及的知识问起,然后深入到问什么是分布事务、分布式锁以及分布式缓存等内容。
这篇文章主要聊聊什么是Redisson分布式锁,Redisson分布式锁的实现原理以及运用示例。
一、概念
Redisson是一个基于Java的分布式服务框架,提供了丰富的分布式锁实现。其底层使用了Redis作为存储介质,通过Redis的原子性操作实现了分布式锁的加锁和释放。
具体来说,Redisson的分布式锁实现分为以下几个步骤:
-
获取分布式锁对象:使用Redisson的分布式锁API获取一个分布式锁对象。
-
尝试加锁:调用分布式锁对象的tryLock()方法进行加锁操作。如果加锁成功,则返回true;否则返回false。
-
执行业务逻辑:如果加锁成功,则执行相应的业务逻辑。
-
释放锁:在业务逻辑执行完成后,调用分布式锁对象的unlock()方法释放锁
需要注意的是,为了避免死锁问题,Redisson的分布式锁还支持可重入锁和公平锁两种模式。可重入锁允许同一个线程多次获得同一个分布式锁;公平锁则按照线程请求锁的顺序来分配锁资源,避免出现饥饿现象。此外,Redisson还提供了超时机制,可以在一定时间内无响应时自动释放锁资源。
二、Redisson类型
Redisson分布式锁主要包括以下几种类型:
-
可重入锁(Reentrant Lock):基于CAS原子操作实现的互斥锁,线程执行时存在可重入锁的访问临界区。可重入锁可以保证同一时间只有一个线程可以访问临界区,从而保证数据的一致性。
-
读写锁(ReadWrite Lock):提供读写操作的互斥锁机制,允许多个线程同时读取数据,但只允许一个线程写入数据。
-
乐观锁(Optimistic Locking):在数据未被修改的情况下,提前返回结果;在数据发生变化的情况下,回滚事务,保证数据的一致性。乐观锁不保证锁的顺序,但是可以减少锁的释放次数,提高并发性能。
-
悲观锁(Pessimistic Locking):在数据未被修改的情况下,阻塞等待直到事务提交;在数据发生变化的情况下,回滚事务,保证数据的一致性。悲观锁保证了数据的正确性,但是会影响并发性能。
不同的分布式锁在不同的场景中有不同的优缺点,开发者需要根据实际需求选择合适的锁类型。 -
公平锁(Fair Lock):
-
联锁(MultiLock):
-
红锁(RedLock):
-
信号量(Semaphore):
三、Redisson原理分析
为了更好的理解分布式锁的原理,我这边自己画张图通过这张图来分析。
1、加锁机制
线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。
线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。
2、watch dog自动延期机制
最常见的使用方法
RLock lock = redisson.getLock("my-lock");
// 最常见的使用方法
lock.lock();
四、Redisson示例
1、阻塞锁
使用基本锁以后,redisson使用了自动续期,如果业务超长,运行期间自动续上30s,不用担心业务时间长,锁自动过期被删掉。
public String rlock() {
RLock rlock = redissonClient.getLock("rlock");
rlock.lock();//阻塞锁
//从数据库获取
try {
if (rlock.isLocked()) {
log.info("加锁成功......." + Thread.currentThread().getId());
SpuInfoEntity spuInfo = spuInfoService.getById(11);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return spuInfo.getSpuName();
}
} finally {
rlock.unlock();
log.info("解锁成功......."+ Thread.currentThread().getId());
}
return null;
}
2、可重入锁
仅在调用时锁是空闲的情况下才获取锁。
- 如果锁可用,则获取锁,并立即返回值为true。
- 如果锁不可用,那么这个方法将立即返回值为false
public String trylock() {
RLock rlock = redissonClient.getLock("rlock");
//从数据库获取
try {
boolean lock = rlock.tryLock(10, TimeUnit.SECONDS);/
if (lock) {
log.info("加锁成功......." + Thread.currentThread().getId());
SpuInfoEntity spuInfo = spuInfoService.getById(11);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return spuInfo.getSpuName();
}
} catch (Exception e){
}finally {
rlock.unlock();
log.info("解锁成功......."+ Thread.currentThread().getId());
}
return null;
}
3、读写锁
- 读读模式,相当于无锁
- 写读模式:等待写锁释放
- 写写模式:阻塞方式
- 读写模式:有读锁,写也必须等待
@Override
public String readlock() {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.readLock();
rLock.lock();
System.out.println("读锁错过了.....");
String writeValue;
try {
writeValue = redisTemplate.opsForValue().get("writeValue");
} finally {
rLock.unlock();
System.out.println("读锁释放了.....");
}
return writeValue;
}
/**
* 改数据加写锁,读数据加读锁
* @return
*/
@Override
public String writelock() {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.writeLock();
rLock.lock();
String res = "";
try {
System.out.println("写锁错过了.....");
res = UUID.randomUUID().toString();
Thread.sleep(1000);
redisTemplate.opsForValue().set("writeValue", res);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
rLock.unlock();
System.out.println("写锁释放了.....");
}
return res;
}
4、闭锁
只有当countDown计算器次数大于5时, latch.await()才释放
public String lockDoor() {
try {
RCountDownLatch latch = redissonClient.getCountDownLatch("door");
latch.trySetCount(5);
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "放假了.....";
}
@Override
public String gogogo(Long id) {
RCountDownLatch latch = redissonClient.getCountDownLatch("door");
latch.countDown(); //计数减一
return id + "班的人走了...";
}
5、信号量
只有park.release释放之后,park.acquire()才能获取
@Override
public String park() {
try {
redisTemplate.opsForValue().set("park", "3");
RSemaphore park = redissonClient.getSemaphore("park");
boolean b = park.tryAcquire();//获取一个信号量,占一个车位
if(b){
return "停成功了。。。";
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return "停车失败了》。。。";
}
@Override
public String go(Long id) {
try {
RSemaphore park = redissonClient.getSemaphore("park");
park.release(); //释放一个信号量,释放一个车位
} catch (Exception e) {
throw new RuntimeException(e);
}
return id + "车走了...";
}
五、项目Redisson配置
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--lettuce,redis客户端,使用netty作网络通信-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--jedis,redis客户端,解决压测堆外内存溢出,springboot2.3.2已解决-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--redisson,redis客户端,封装了分布式锁实现,也可以使用springboot的方式,不需要自己配置-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.3</version>
</dependency>
2、配置RedissonClient
@Configuration
public class MyRedissonConfig {
/**
* 注入客户端实例对象
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson(@Value("${spring.redis.host}") String host, @Value("${spring.redis.port}")String port) throws IOException {
// 1.创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port);// 单节点模式
// config.useSingleServer().setAddress("rediss://" + host + ":" + port);// 使用安全连接
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");// 集群模式
// 2.创建redisson客户端实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
3、application.yml配置
spring:
redis:
host: 127.0.0.1
database: 2
port: 6379
timeout: 10000ms
password:
lettuce:
pool:
max-active: 5
min-idle: 1
max-idle: 3
max-wait: 30000ms
六、Redisson分布式锁的缺点
Redis分布式锁会有个缺陷,就是在Redis哨兵模式下:
-
客户端1 对某个 master节点 写入了redisson锁,此时会异步复制给对应的 slave节点。但是这个过程中一旦发生master节点宕机,主备切换,slave节点从变为了 master节点。
-
这时客户端2来尝试加锁的时候,在新的master节点上也能加锁,此时就会导致多个客户端对同一个分布式锁完成了加锁。
-
这时系统在业务语义上一定会出现问题, 导致各种脏数据的产生 。
-
缺陷 在哨兵模式或者主从模式下,如果 master实例宕机的时候,可能导致多个客户端同时完成加锁。
源码下载:
https://gitee.com/charlinchenlin/koo-erp