【高并发】高并发分布式锁架构解密,不是所有的锁都是分布式锁!!

读者福利

由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

更多笔记分享

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

stringRedisTemplate.delete(PRODUCT_ID);

}

return “success”;

}

在上述代码中,我们在向Redis中设置锁标志位的时候就设置了超时时间。此时,只要向Redis中成功设置了数据,则即使我们的业务系统宕机,Redis中的数据过期后,也会自动删除。后续的线程进入提交订单的方法后,就会成功的设置锁标志位,并向下执行正常的下单流程。

到此,上述的代码基本上在功能角度解决了程序的死锁问题,那么,上述程序真的就完美了吗?哈哈,很多小伙伴肯定会说不完美!确实,上面的代码还不是完美的,那大家知道哪里不完美吗?接下来,我们继续分析。

在开发集成角度分析代码

在我们开发公共的系统组件时,比如我们这里说的分布式锁,我们肯定会抽取一些公共的类来完成相应的功能来供系统使用。

这里,假设我们定义了一个RedisLock接口,如下所示。

public interface RedisLock{

//加锁操作

boolean tryLock(String key, long timeout, TimeUnit unit);

//解锁操作

void releaseLock(String key);

}

接下来,使用RedisLockImpl类实现RedisLock接口,提供具体的加锁和解锁实现,如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

return stringRedisTemplate.opsForValue().setIfAbsent(key, “binghe”, timeout, unit);

}

@Override

public void releaseLock(String key){

stringRedisTemplate.delete(key);

}

}

在开发集成的角度来说,当一个线程从上到下执行时,首先对程序进行加锁操作,然后执行业务代码,执行完成后,再进行释放锁的操作。理论上,加锁和释放锁时,操作的Redis Key都是一样的。但是,如果其他开发人员在编写代码时,并没有调用tryLock()方法,而是直接调用了releaseLock()方法,并且他调用releaseLock()方法传递的key与你调用tryLock()方法传递的key是一样的。那此时就会出现问题了,他在编写代码时,硬生生的将你加的锁释放了!!!

所以,上述代码是不安全的,别人能够随随便便的将你加的锁删除,这就是锁的误删操作,这是非常危险的,所以,上述的程序存在很严重的问题!!

那如何实现只有加锁的线程才能进行相应的解锁操作呢? 继续向下看。

如何实现加锁和解锁的归一化?

什么是加锁和解锁的归一化呢?简单点来说,就是一个线程执行了加锁操作后,后续必须由这个线程执行解锁操作,加锁和解锁操作由同一个线程来完成。

为了解决只有加锁的线程才能进行相应的解锁操作的问题,那么,我们就需要将加锁和解锁操作绑定到同一个线程中,那么,如何将加锁操作和解锁操作绑定到同一个线程呢?其实很简单,相信很多小伙伴都想到了—— 使用ThreadLocal实现 。没错,使用ThreadLocal类确实能够解决这个问题。

此时,我们将RedisLockImpl类的代码修改成如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

return stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){

stringRedisTemplate.delete(key);

}

}

}

上述代码的主要逻辑为:在对程序执行尝试加锁操作时,首先生成一个uuid,将生成的uuid绑定到当前线程,并将传递的key参数操作Redis中的key,生成的uuid作为Redis中的Value,保存到Redis中,同时设置超时时间。当执行解锁操作时,首先,判断当前线程中绑定的uuid是否和Redis中存储的uuid相等,只有二者相等时,才会执行删除锁标志位的操作。这就避免了一个线程对程序进行了加锁操作后,其他线程对这个锁进行了解锁操作的问题。

继续分析

我们将加锁和解锁的方法改成如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

private String lockUUID;

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

lockUUID = uuid;

return stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

if(lockUUID.equals(stringRedisTemplate.opsForValue().get(key))){

stringRedisTemplate.delete(key);

}

}

}

相信很多小伙伴都会看出上述代码存在什么问题了!! 没错,那就是 线程安全的问题。

所以,这里,我们需要使用ThreadLocal来解决线程安全问题。

可重入性分析

在上面的代码中,当一个线程成功设置了锁标志位后,其他的线程再设置锁标志位时,就会返回失败。还有一种场景就是在提交订单的接口方法中,调用了服务A,服务A调用了服务B,而服务B的方法中存在对同一个商品的加锁和解锁操作。

所以,服务B成功设置锁标志位后,提交订单的接口方法继续执行时,也不能成功设置锁标志位了。也就是说,目前实现的分布式锁没有可重入性。

这里,就存在可重入性的问题了。我们希望设计的分布式锁 具有可重入性 ,那什么是可重入性呢?简单点来说,就是同一个线程,能够多次获取同一把锁,并且能够按照顺序进行解决操作。

其实,在JDK 1.5之后提供的锁很多都支持可重入性,比如synchronized和Lock。

如何实现可重入性呢?

映射到我们加锁和解锁方法时,我们如何支持同一个线程能够多次获取到锁(设置锁标志位)呢?可以这样简单的设计:如果当前线程没有绑定uuid,则生成uuid绑定到当前线程,并且在Redis中设置锁标志位。如果当前线程已经绑定了uuid,则直接返回true,证明当前线程之前已经设置了锁标志位,也就是说已经获取到了锁,直接返回true。

结合以上分析,我们将提交订单的接口方法代码改造成如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

Boolean isLocked = false;

if(threadLocal.get() == null){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

}else{

isLocked = true;

}

return isLocked;

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){

stringRedisTemplate.delete(key);

}

}

}

这样写看似没有啥问题,但是大家细想一下,这样写就真的OK了吗?

可重入性的问题分析

既然上面分布式锁的可重入性是存在问题的,那我们就来分析下问题的根源在哪里!

假设我们提交订单的方法中,首先使用RedisLock接口对代码块添加了分布式锁,在加锁后的代码中调用了服务A,而服务A中也存在调用RedisLock接口的加锁和解锁操作。而多次调用RedisLock接口的加锁操作时,只要之前的锁没有失效,则会直接返回true,表示成功获取锁。也就是说,无论调用加锁操作多少次,最终只会成功加锁一次。而执行完服务A中的逻辑后,在服务A中调用RedisLock接口的解锁方法,此时,会将当前线程所有的加锁操作获得的锁全部释放掉。

我们可以使用下图来简单的表示这个过程。

在这里插入图片描述

那么问题来了,如何解决可重入性的问题呢?

解决可重入性问题

相信很多小伙伴都能够想出使用计数器的方式来解决上面可重入性的问题,没错,就是使用计数器来解决。 整体流程如下所示。

在这里插入图片描述

那么,体现在程序代码上是什么样子呢?我们来修改RedisLockImpl类的代码,如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

private ThreadLocal threadLocalInteger = new ThreadLocal();

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

Boolean isLocked = false;

if(threadLocal.get() == null){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

}else{

isLocked = true;

}

//加锁成功后将计数器加1

if(isLocked){

Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();

threadLocalInteger.set(count++);

}

return isLocked;

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){

Integer count = threadLocalInteger.get();

//计数器减为0时释放锁

if(count == null || --count <= 0){

stringRedisTemplate.delete(key);

}

}

}

}

至此,我们基本上解决了分布式锁的可重入性问题。

说到这里,我还要问大家一句,上面的解决问题的方案真的没问题了吗?

阻塞与非阻塞锁

在提交订单的方法中,当获取Redis分布式锁失败时,我们直接返回了failure来表示当前请求下单的操作失败了。试想,在高并发环境下,一旦某个请求获得了分布式锁,那么,在这个请求释放锁之前,其他的请求调用下单方法时,都会返回下单失败的信息。在真实场景中,这是非常不友好的。我们可以将后续的请求进行阻塞,直到当前请求释放锁后,再唤醒阻塞的请求获得分布式锁来执行方法。

所以,我们设计的分布式锁需要支持 阻塞和非阻塞 的特性。

那么,如何实现阻塞呢?我们可以使用自旋来实现,继续修改RedisLockImpl的代码如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

private ThreadLocal threadLocalInteger = new ThreadLocal();

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

Boolean isLocked = false;

if(threadLocal.get() == null){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

//如果获取锁失败,则自旋获取锁,直到成功

if(!isLocked){

for(;😉{

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

if(isLocked){

break;

}

}

}

}else{

isLocked = true;

}

//加锁成功后将计数器加1

if(isLocked){

Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();

threadLocalInteger.set(count++);

}

return isLocked;

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){

Integer count = threadLocalInteger.get();

//计数器减为0时释放锁

if(count == null || --count <= 0){

stringRedisTemplate.delete(key);

}

}

}

}

在分布式锁的设计中,阻塞锁和非阻塞锁 是非常重要的概念,大家一定要记住这个知识点。

锁失效问题

尽管我们实现了分布式锁的阻塞特性,但是还有一个问题是我们不得不考虑的。那就是 锁失效 的问题。

当程序执行业务的时间超过了锁的过期时间会发生什么呢? 想必很多小伙伴都能够想到,那就是前面的请求没执行完,锁过期失效了,后面的请求获取到分布式锁,继续向下执行了,程序无法做到真正的互斥,无法保证业务的原子性了。

那如何解决这个问题呢?答案就是:我们必须保证在业务代码执行完毕后,才能释放分布式锁。 方案是有了,那如何实现呢?

说白了,我们需要在业务代码中,时不时的执行下面的代码来保证在业务代码没执行完时,分布式锁不会因超时而被释放。

springRedisTemplate.expire(PRODUCT_ID, 30, TimeUnit.SECONDS);

这里,我们需要定义一个定时策略来执行上面的代码,需要注意的是:我们不能等到30秒后再执行上述代码,因为30秒时,锁已经失效了。例如,我们可以每10秒执行一次上面的代码。

有些小伙伴说,直接在RedisLockImpl类中添加一个while(true)循环来解决这个问题,那我们就这样修改下RedisLockImpl类的代码,看看有没有啥问题。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

private ThreadLocal threadLocalInteger = new ThreadLocal();

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

Boolean isLocked = false;

if(threadLocal.get() == null){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

//如果获取锁失败,则自旋获取锁,直到成功

if(!isLocked){

for(;😉{

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

if(isLocked){

break;

}

}

}

//定义更新锁的过期时间

while(true){

Integer count = threadLocalInteger.get();

//当前锁已经被释放,则退出循环

if(count == 0 || count <= 0){

break;

}

springRedisTemplate.expire(key, 30, TimeUnit.SECONDS);

try{

//每隔10秒执行一次

Thread.sleep(10000);

}catch (InterruptedException e){

e.printStackTrace();

}

}

}else{

isLocked = true;

}

//加锁成功后将计数器加1

if(isLocked){

Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();

threadLocalInteger.set(count++);

}

return isLocked;

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){

Integer count = threadLocalInteger.get();

//计数器减为0时释放锁

if(count == null || --count <= 0){

stringRedisTemplate.delete(key);

}

}

}

}

相信小伙伴们看了代码就会发现哪里有问题了:更新锁过期时间的代码肯定不能这么去写。因为这么写会 导致当前线程在更新锁超时时间的while(true)循环中一直阻塞而无法返回结果。 所以,我们不能将当前线程阻塞,需要异步执行定时任务来更新锁的过期时间。

此时,我们继续修改RedisLockImpl类的代码,将定时更新锁超时的代码放到一个单独的线程中执行,如下所示。

public class RedisLockImpl implements RedisLock{

@Autowired

private StringRedisTemplate stringRedisTemplate;

private ThreadLocal threadLocal = new ThreadLocal();

private ThreadLocal threadLocalInteger = new ThreadLocal();

@Override

public boolean tryLock(String key, long timeout, TimeUnit unit){

Boolean isLocked = false;

if(threadLocal.get() == null){

String uuid = UUID.randomUUID().toString();

threadLocal.set(uuid);

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

//如果获取锁失败,则自旋获取锁,直到成功

if(!isLocked){

for(;😉{

isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);

if(isLocked){

break;

}

}

}

//启动新线程来执行定时任务,更新锁过期时间

new Thread(new UpdateLockTimeoutTask(uuid, stringRedisTemplate, key)).start();

}else{

isLocked = true;

}

//加锁成功后将计数器加1

if(isLocked){

Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();

threadLocalInteger.set(count++);

}

return isLocked;

}

@Override

public void releaseLock(String key){

//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作

String uuid = stringRedisTemplate.opsForValue().get(key);

if(threadLocal.get().equals(uuid)){

Integer count = threadLocalInteger.get();

//计数器减为0时释放锁

if(count == null || --count <= 0){

stringRedisTemplate.delete(key);

//获取更新锁超时时间的线程并中断

long threadId = stringRedisTemplate.opsForValue().get(uuid);

Thread updateLockTimeoutThread = ThreadUtils.getThreadByThreadId(threadId);

if(updateLockTimeoutThread != null){

//中断更新锁超时时间的线程

updateLockTimeoutThread.interrupt();

stringRedisTemplate.delete(uuid);

}

}

}

}

}

创建UpdateLockTimeoutTask类来执行更新锁超时的时间。

public class UpdateLockTimeoutTask implements Runnable{

//uuid

private long uuid;

private StringRedisTemplate stringRedisTemplate;

private String key;

public UpdateLockTimeoutTask(long uuid, StringRedisTemplate stringRedisTemplate, String key){

this.uuid = uuid;

this.stringRedisTemplate = stringRedisTemplate;

this.key = key;

}

@Override

public void run(){

//以uuid为key,当前线程id为value保存到Redis中

stringRedisTemplate.opsForValue().set(uuid, Thread.currentThread().getId());

//定义更新锁的过期时间

while(true){

springRedisTemplate.expire(key, 30, TimeUnit.SECONDS);

try{

//每隔10秒执行一次

Thread.sleep(10000);

}catch (InterruptedException e){

e.printStackTrace();

}

}

}

}

接下来,我们定义一个ThreadUtils工具类,这个工具类中有一个根据线程id获取线程的方法getThreadByThreadId(long threadId)。

public class ThreadUtils{

//根据线程id获取线程句柄

public static Thread getThreadByThreadId(long threadId){

ThreadGroup group = Thread.currentThread().getThreadGroup();

while(group != null){

Thread[] threads = new Thread[(int)(group.activeCount() * 1.2)];

int count = group.enumerate(threads, true);

for(int i = 0; i < count; i++){

if(threadId == threads[i].getId()){

return threads[i];

}

}

}

}

}

上述解决分布式锁失效的问题在分布式锁领域有一个专业的术语叫做 “异步续命” 。需要注意的是:当业务代码执行完毕后,我们需要停止更新锁超时时间的线程。所以,这里,我对程序的改动是比较大的,首先,将更新锁超时的时间任务重新定义为一个UpdateLockTimeoutTask类,并将uuid和StringRedisTemplate注入到任务类中,在执行定时更新锁超时时间时,首先将当前线程保存到Redis中,其中Key为传递进来的uuid。

在首先获取分布式锁后,重新启动线程,并将uuid和StringRedisTemplate传递到任务类中执行任务。当业务代码执行完毕后,调用releaseLock()方法释放锁时,我们会通过uuid从Redis中获取更新锁超时时间的线程id,并通过线程id获取到更新锁超时时间的线程,调用线程的interrupt()方法来中断线程。

此时,当分布式锁释放后,更新锁超时的线程就会由于线程中断而退出了。

实现分布式锁的基本要求


结合上述的案例,我们可以得出实现分布式锁的基本要求:

  • 支持互斥性

  • 支持锁超时

  • 支持阻塞和非阻塞特性

  • 支持可重入性

  • 支持高可用

通用分布式解决方案


在互联网行业,分布式锁是一个绕不开的话题,同时,也有很多通用的分布式锁解决方案,其中,用的比较多的一种方案就是使用开源的Redisson框架来解决分布式锁问题。

有关Redisson分布式锁的使用方案大家可以参考《【高并发】你知道吗?大家都在使用Redisson实现分布式锁了!!

既然Redisson框架已经很牛逼了,我们直接使用Redisson框架是否能够100%的保证分布式锁不出问题呢?答案是无法100%的保证。因为在分布式领域没有哪一家公司或者架构师能够保证100%的不出问题,就连阿里这样的大公司、阿里的首席架构师这样的技术大牛也不敢保证100%的不出问题。

在分布式领域,无法做到100%无故障,我们追求的是几个9的目标,例如99.999%无故障。

CAP理论


在分布式领域,有一个非常重要的理论叫做CAP理论。

  • C:Consistency(一致性)

  • A:Availability(可用性)

  • P:Partition tolerance(分区容错性)

在分布式领域中,是必须要保证分区容错性的,也就是必须要保证“P”,所以,我们只能保证CP或者AP。

这里,我们可以使用Redis和Zookeeper来进行简单的对比,我们可以使用Redis实现AP架构的分布式锁,使用Zookeeper实现CP架构的分布式锁。

  • 基于Redis的AP架构的分布式锁模型

在这里插入图片描述

在基于Redis实现的AP架构的分布式锁模型中,向Redis节点1写入数据后,会立即返回结果,之后在Redis中会以异步的方式来同步数据。

  • 基于Zookeeper的CP架构的分布式锁模型

在这里插入图片描述

在基于Zookeeper实现的CP架构的分布式模型中,向节点1写入数据后,会等待数据的同步结果,当数据在大多数Zookeeper节点间同步成功后,才会返回结果数据。

当我们使用基于Redis的AP架构实现分布式锁时,需要注意一个问题,这个问题可以使用下图来表示。

在这里插入图片描述

也就是Redis主从节点之间的数据同步失败,假设线程向Master节点写入了数据,而Redis中Master节点向Slave节点同步数据失败了。此时,另一个线程读取的Slave节点中的数据,发现没有添加分布式锁,此时就会出现问题了!!!

所以,在设计分布式锁方案时,也需要注意Redis节点之间的数据同步问题。

红锁的实现


在Redisson框架中,实现了红锁的机制,Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。当红锁中超过半数的RLock加锁成功后,才会认为加锁是成功的,这就提高了分布式锁的高可用。

我们可以使用Redisson框架来实现红锁。

public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){

RLock lock1 = redisson1.getLock(“lock1”);

RLock lock2 = redisson2.getLock(“lock2”);

RLock lock3 = redisson3.getLock(“lock3”);

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);

try {

// 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。

lock.lock();

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁

boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

} catch (InterruptedException e) {

最后

针对以上面试题,小编已经把面试题+答案整理好了

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

面试专题

image

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

image

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

锁的高可用。

我们可以使用Redisson框架来实现红锁。

public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){

RLock lock1 = redisson1.getLock(“lock1”);

RLock lock2 = redisson2.getLock(“lock2”);

RLock lock3 = redisson3.getLock(“lock3”);

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);

try {

// 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。

lock.lock();

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁

boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

} catch (InterruptedException e) {

最后

针对以上面试题,小编已经把面试题+答案整理好了

[外链图片转存中…(img-GjqPRT9l-1715490218943)]

[外链图片转存中…(img-5JXrRYyd-1715490218943)]

[外链图片转存中…(img-oGTIKDPL-1715490218943)]

面试专题

[外链图片转存中…(img-BIR0dtgq-1715490218944)]

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

[外链图片转存中…(img-qtLDvnGw-1715490218944)]

[外链图片转存中…(img-cJIuHEca-1715490218944)]

[外链图片转存中…(img-E0fDmy3T-1715490218945)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值