什么是Redisson分布式锁?有什么作用?

前言:

如果你的简历中写了做过电商项目,那么面试官基本都会从SpringBoot、SpringCloud以及Dubbo这些微服务架构涉及的知识问起,然后深入到问什么是分布事务、分布式锁以及分布式缓存等内容。

这篇文章主要聊聊什么是Redisson分布式锁,Redisson分布式锁的实现原理以及运用示例。

一、概念

Redisson是一个基于Java的分布式服务框架,提供了丰富的分布式锁实现。其底层使用了Redis作为存储介质,通过Redis的原子性操作实现了分布式锁的加锁和释放。

具体来说,Redisson的分布式锁实现分为以下几个步骤:

  • 获取分布式锁对象:使用Redisson的分布式锁API获取一个分布式锁对象。

  • 尝试加锁:调用分布式锁对象的tryLock()方法进行加锁操作。如果加锁成功,则返回true;否则返回false。

  • 执行业务逻辑:如果加锁成功,则执行相应的业务逻辑。

  • 释放锁:在业务逻辑执行完成后,调用分布式锁对象的unlock()方法释放锁

需要注意的是,为了避免死锁问题,Redisson的分布式锁还支持可重入锁和公平锁两种模式。可重入锁允许同一个线程多次获得同一个分布式锁;公平锁则按照线程请求锁的顺序来分配锁资源,避免出现饥饿现象。此外,Redisson还提供了超时机制,可以在一定时间内无响应时自动释放锁资源。

二、Redisson类型

Redisson分布式锁主要包括以下几种类型:

  1. 可重入锁(Reentrant Lock):基于CAS原子操作实现的互斥锁,线程执行时存在可重入锁的访问临界区。可重入锁可以保证同一时间只有一个线程可以访问临界区,从而保证数据的一致性。

  2. 读写锁(ReadWrite Lock):提供读写操作的互斥锁机制,允许多个线程同时读取数据,但只允许一个线程写入数据。

  3. 乐观锁(Optimistic Locking):在数据未被修改的情况下,提前返回结果;在数据发生变化的情况下,回滚事务,保证数据的一致性。乐观锁不保证锁的顺序,但是可以减少锁的释放次数,提高并发性能。

  4. 悲观锁(Pessimistic Locking):在数据未被修改的情况下,阻塞等待直到事务提交;在数据发生变化的情况下,回滚事务,保证数据的一致性。悲观锁保证了数据的正确性,但是会影响并发性能。
    不同的分布式锁在不同的场景中有不同的优缺点,开发者需要根据实际需求选择合适的锁类型。

  5. 公平锁(Fair Lock):

  6. 联锁(MultiLock):

  7. 红锁(RedLock):

  8. 信号量(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实例宕机的时候,可能导致多个客户端同时完成加锁。

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值