首页 > 数据库 >Redisson锁误删除

Redisson锁误删除

时间:2024-07-08 10:56:57浏览次数:3  
标签:误删除 Redisson hash get lock list 线程 id

1、目标

本文的主要目标是探究Redisson分布式锁在设置过期时间的情况下多线程是否会误删除的问题,首先分析单线程执行的完整过程,然后分析多线程锁误删除的现象,接着进行源码分析,理解Redisson如何保证多线程场景下当前线程不会误删除其他线程id的锁,最后是总结

2、单线程执行的完整过程

为了研究多线程场景下redisson分布式锁的执行流程,可以先做一个单线程的demo

引入pom.xml文件的依赖

<!--redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置redisson

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://ip:port");
        return Redisson.create(config);
    }
}

测试redisson的controller

@RestController
@RequestMapping("/testRedisson")
@RequiredArgsConstructor
@Log4j2
public class TestRedissonController {

    private final StringRedisTemplate stringRedisTemplate;

    private final RedissonClient redissonClient;

    @GetMapping("/f1")
    public String f1() throws Exception {
        RLock lock = redissonClient.getLock("redisson.lock");
        tryLockBefore(lock);
        boolean isLock = lock.tryLock(8, 10, TimeUnit.SECONDS);
        if (!isLock) {
            return "f2 tryLock failed";
        }
        tryLockAfter(lock);
        try {
            // 业务
            Thread.sleep(1000 * 8);
        } finally {
            releaseLockBefore(lock);
            lock.unlock();
            releaseLockAfter(lock);
        }
        return "f2 ok";
    }

    private void tryLockBefore(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("threadId: {}, tryLock before, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private void tryLockAfter(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("threadId: {}, tryLock after, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private void releaseLockBefore(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("lock.isHeldByCurrentThread() : {}", lock.isHeldByCurrentThread());
        log.info("threadId: {}, releaseLock before, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private void releaseLockAfter(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("threadId: {}, releaseLock after, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private List<Object> isExistRedissonKey(RLock lock) throws Exception {
        RedissonObject redissonObject = (RedissonObject) lock;
        Class<RedissonObject> cls = RedissonObject.class;
        Field field = cls.getDeclaredField("name");
        field.setAccessible(true);
        String name = (String) field.get(redissonObject);
        //log.info("name = {}", name);

        RedissonLock redissonLock = (RedissonLock) lock;
        Class<? extends RedissonLock> aClass = redissonLock.getClass();
        Method method = aClass.getDeclaredMethod("getLockName", long.class);
        method.setAccessible(true);
        String lockName = (String) method.invoke(redissonLock, Thread.currentThread().getId());
        //log.info("lockName = {}", lockName);

        Boolean b1 = stringRedisTemplate.hasKey(name);
        Boolean b2 = stringRedisTemplate.opsForHash().hasKey(name, lockName);
        Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(name);
        //log.info("b1 = {}, b2 = {}", b1, b2);
        List<Object> res = new ArrayList<>(2);
        res.add(b1);
        res.add(b2);
        res.add(JSON.toJSONString(map));
        return res;
    }

}

这个controller是在tryLock方法前后、unlock方法前后分别打印redisson底层存储的获取锁信息,它是一个hash类型数据,打印数据的思路是利用反射调用redisson底层存储数据的非公有属性和方法,具体实现在“4、源码分析”中讲解

单线程执行会打印如下信息

在这里插入图片描述

(1)执行tryLock方法会获取锁,将获取锁的信息存储到一个hash类型的数据中,这个hash类型数据的key是执行getLock(“redisson.lock”)方法中锁的名称redisson.lock,这个hash类型数据的field是lockName,它包含获取锁时当前线程id,这个hash类型数据的value是当前线程id获取锁的锁重入次数
① 打印信息中tryLock before会展示这个hash类型的key对应的所有fields和所有values数据,tryLock before数据为空表示执行tryLock方法之前hash类型中没有field和value
② 打印信息中tryLock after会展示这个hash类型的key对应的所有fields和所有values数据,field是"e4f10c81-bec7-4997-b410-569017db6e2d:75",其中75是当前线程id,value是1,即线程id为75的线程获取锁的锁重入次数,tryLock after表示在执行tryLock方法之后已经将75和1存储到hash类型中

(2)代码中这个线程执行业务的时间是8秒,redisson锁的超时时间是10秒,可以正常执行unlock方法
① 打印信息中releaseLock before会展示这个hash类型的key对应的所有fields和所有values数据,releaseLock before可以看到存储的数据仍然是tryLock after保存的数据
② 打印信息中releaseLock after会展示这个hash类型的key对应的所有fields和所有values数据,releaseLock after数据为空表示执行unlock方法之后已经删除了hash类型中线程id为75的数据

3、多线程锁误删除的现象

在单线程场景下执行tryLock方法redisson可以将数据保存到一个hash类型的数据中,执行unlock方法redisson会删除hash类型中对应线程id的field和value数据,但是多线程场景下如果设置了过期时间并且业务执行时间过长可能会导致多线程锁的误删除,锁的误删除可能会导致一些问题比如共享数据的并发修改,多线程锁的误删除可能出现的场景如下图

在这里插入图片描述

线程1获取锁设置过期时间是5秒,业务执行时间是8秒,线程2获取锁设置过期时间是10秒,业务执行时间是5秒,可能出现的问题是线程1获取锁成功,线程2等待锁被释放,线程1在5秒的时候锁过期了就会自动释放锁,线程2就获取锁成功,但是线程1的业务还没有执行完成就会一直执行直到业务执行结束后执行unlock方法,此时释放的锁是获取锁成功的线程2的锁,会造成线程2不持有锁了,从而导致共享数据的并发修改问题

4、源码分析

4.1 执行tryLock方法

在这里插入图片描述

执行tryLock方法会判断hash类型数据是否存在,如果不存在就会获取锁成功并添加数据到hash,如果存在就判断hash有没有当前线程id,如果有就获取锁成功并将当前线程id的锁重入次数加1,如果hash没有当前线程id就会返回这个hash的剩余过期时间表示获取锁失败

其中,这里设置了当前线程id的过期时间,只有设置过期时间然后当过期时间到了之后才会删除hash的field和value,如果没有设置过期时间过期时间默认是-1如果业务没有执行完成就会自动续期,这是tryLock的另一个逻辑,field是当前线程id的lockName,value是当前线程id的锁重入次数

在这里插入图片描述
hash类型数据的key是RedissonObject的name属性,因此需要采用反射获取属性

在这里插入图片描述

hash类型数据的field是RedissonLock的getLockName方法,因此需要采用反射获取方法

4.2 执行unlock方法

在这里插入图片描述

执行unlock方法会判断hash类型数据中field是当前线程id的是否存在,如果不存在就返回空,这样就可以避免锁的误删除,即避免当前线程id删除其他线程id的锁信息

如果存在就将锁重入次数减1,然后判断锁重入次数是否为0,如果大于0就重新设置过期时间,如果等于0就释放锁并发布锁被释放的消息

在这里插入图片描述

当前线程id想要删除其他线程id的锁信息会抛出异常

4.3 多线程测试controller

@RestController
@RequestMapping("/testRedisson")
@RequiredArgsConstructor
@Log4j2
public class TestRedissonController {

    private final StringRedisTemplate stringRedisTemplate;

    private final RedissonClient redissonClient;

    @GetMapping("/f1")
    public String f1() throws Exception {
        RLock lock = redissonClient.getLock("redisson.lock");
        tryLockBefore(lock);
        boolean isLock = lock.tryLock(10, 5, TimeUnit.SECONDS);
        if (!isLock) {
            return "f2 tryLock failed";
        }
        tryLockAfter(lock);
        try {
            // 业务
            Thread.sleep(1000 * 8);
        } finally {
            releaseLockBefore(lock);
            lock.unlock();
            releaseLockAfter(lock);
        }
        return "f1 ok";
    }

    @GetMapping("/f2")
    public String f2() throws Exception {
        RLock lock = redissonClient.getLock("redisson.lock");
        tryLockBefore(lock);
        boolean isLock = lock.tryLock(10, 10, TimeUnit.SECONDS);
        if (!isLock) {
            return "f2 tryLock failed";
        }
        tryLockAfter(lock);
        try {
            // 业务
            Thread.sleep(1000 * 5);
        } finally {
            releaseLockBefore(lock);
            lock.unlock();
            releaseLockAfter(lock);
        }
        return "f2 ok";
    }

    private void tryLockBefore(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("threadId: {}, tryLock before, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private void tryLockAfter(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("threadId: {}, tryLock after, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private void releaseLockBefore(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("lock.isHeldByCurrentThread() : {}", lock.isHeldByCurrentThread());
        log.info("threadId: {}, releaseLock before, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private void releaseLockAfter(RLock lock) throws Exception {
        List<Object> list = isExistRedissonKey(lock);
        log.info("threadId: {}, releaseLock after, hash key isExist = {}, hash field isExist = {}, hash all fields and values = {}",
                Thread.currentThread().getId(), list.get(0), list.get(1), list.get(2));
    }

    private List<Object> isExistRedissonKey(RLock lock) throws Exception {
        RedissonObject redissonObject = (RedissonObject) lock;
        Class<RedissonObject> cls = RedissonObject.class;
        Field field = cls.getDeclaredField("name");
        field.setAccessible(true);
        String name = (String) field.get(redissonObject);
        //log.info("name = {}", name);

        RedissonLock redissonLock = (RedissonLock) lock;
        Class<? extends RedissonLock> aClass = redissonLock.getClass();
        Method method = aClass.getDeclaredMethod("getLockName", long.class);
        method.setAccessible(true);
        String lockName = (String) method.invoke(redissonLock, Thread.currentThread().getId());
        //log.info("lockName = {}", lockName);

        Boolean b1 = stringRedisTemplate.hasKey(name);
        Boolean b2 = stringRedisTemplate.opsForHash().hasKey(name, lockName);
        Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(name);
        //log.info("b1 = {}, b2 = {}", b1, b2);
        List<Object> res = new ArrayList<>(2);
        res.add(b1);
        res.add(b2);
        res.add(JSON.toJSONString(map));
        return res;
    }

}

测试controller设置f1方法和f2方法,f1方法获取锁设置过期时间是5秒,业务执行时间是8秒,f2方法获取锁设置过期时间是10秒,业务执行时间是5秒,先调用f1方法,然后调用f2方法,会出现f1方法的线程执行unlock方法想要删除f2方法线程id的锁,导致抛出一个锁不能误删除的异常,从而避免了f1方法线程删除f2方法线程id的锁

测试controller在tryLock方法的前后和unlock方法的前后分别查询hash类型的数据

查询hash类型的一个key对应的所有fields和values数据用stringRedisTemplate.opsForHash().entries(name)方法,其中key是name锁的名字,field是线程id的lockName,value是这个线程id对应锁重入次数

key通过反射RedissonObject的name属性,field通过反射RedissonLock的getLockName方法

4.4 多线程测试controller测试结果

在这里插入图片描述

线程74获取锁成功,等待线程74的锁超时自动释放锁,线程76获取锁成功,当线程74的业务执行完成后会执行unlock方法,不会误删除线程76的锁,而是抛出异常

在这里插入图片描述

当线程76的业务执行完成后会执行unlock方法,线程76删除自己的锁成功,这表示线程76的锁没有被线程74误删除

5、总结

多线程场景下Redisson锁不会被误删除指的是当前线程id不会删除其他线程id的锁,这是通过Redisson存储的一个hash类型数据,记录了获取锁成功的线程id和锁重入次数,释放锁的时候会判断当前线程id是否在hash类型的field中,如果不在就不能删除,这样就保证了锁不会被其他线程误删除

标签:误删除,Redisson,hash,get,lock,list,线程,id
From: https://blog.csdn.net/weixin_43823462/article/details/140228516

相关文章

  • redisson WRONGPASS invalid username-password pair or user is disable
    1、技术架构:若依微服务框架<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2021.1</version></dependency><dependency>......
  • Linux创建回收站,防止误删文件 误删除文件恢复
    使用为Centos7创建回收站的方法,可以有效地防止误删文件,并对删除信息进行记录。实现:每个用户都可以使用回收站功能每个用户具有独立的回收站,用户删除的文件会移动到自己专属的回收站中,不会被未授权的用户看到。回收站内按照天建立文件夹,移入的文件添加时间后缀进行重命名,防止......
  • EasyRecovery数据恢复软件电脑的超级救星,无论是误删除的文件、格式化的硬盘还是病毒攻
    EasyRecovery数据恢复软件,是我近期用过最神奇的产品之一了!它就像是电脑的超级救星,无论是误删除的文件、格式化的硬盘还是病毒攻击,都能轻松搞定!让我给大家详细介绍一下这个神器吧!EasyRecovery数据恢复软件的功能真的是非常强大!它可以扫描并恢复各种类型的文件,包括照片、视频、......
  • Redisson详解
    目录第1章:Redisson简介第2章:Redisson的架构与原理第3章:Redisson的基本使用连接Redis基本操作高级数据结构操作分布式锁的使用第4章:Redisson的高级特性分布式数据结构发布/订阅模型延迟队列与阻塞队列第5章:Redisson的分布式服务分布式锁的实现与应用分布式集合......
  • 用Redisson的延迟队列RDelayedQueue处理延迟任务或者定时任务
    什么是RedissonRedisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。什么是RDelayedQueue获取RDelayedQueue:public<V>RDelayedQueue<V>getDelayedQueue(R......
  • Redisson 限流器源码分析
    Redisson限流器源码分析对上篇文章网友评论给出问题进行解答:redis的key是否会过期可以先阅读上篇文章:redis+AOP+自定义注解实现接口限流-古渡蓝按-博客园(cnblogs.com)注解AOP代码部分提取//调用Reids工具类的rateLimiter方法longnumber=RedisUtils.rat......
  • synchronized、Lock本地锁和Redisson分布式锁的简单使用
    文章目录概念准备工作synchronized本地锁演示JUC包的Lock本地锁演示Redisson的RLock分布式锁演示源码地址参考来源概念redisson是一个简单易用的Redis客户端工具。不仅如此,它还具备分布式锁的功能准备工作快速整合SSMP请参考我这篇文章SpringBoot快速整合Spring......
  • Redis教程(十七):Redis的Redisson分布式锁
    Redis分布式锁 Redis分布式锁的主要作用是在分布式系统环境下提供一种机制,用于确保在同一时间只有一个进程(或线程)能够执行某个关键代码段或访问特定的资源。这主要用于控制对共享资源的并发访问,以避免因多个进程同时修改同一数据而导致的数据不一致或其他竞争条件问题。 ......
  • 记录一次Redisson使用synchronized和分布式锁不生效的原因
    最近在开发的过程中,遇到了一个并发场景,用户进行方案复制的时候,当快速点击两次操作的时候,出现了复制方案重名的情况,实际上是复制方案的方案名称,是由后端根据数据库已有的方案名称和当前要复制的方案名称进行逻辑处理,保证方案名称不能重复,比如:要复制的方案名称为“我的方案”,......
  • 如何从U盘恢复误删除的文件
    在许多情况下,用户可能会发现其U盘上的数据误删,并且无法访问或恢复它。在这篇文章中,我们将看到如何使用命令提示符尝试从U盘恢复损坏的文件和数据。我们还将列出一些免费的U盘恢复软件及其独特的功能,以便在前一种方法无法产生所需的结果时可以使用它们。使用CMD从U盘恢复误删......