• 在SpringBoot中利用Redis实现互斥锁


    在SpringBoot中利用Redis实现互斥锁

    基本知识

    前提条件,有一个能够在Springboot中使用Redis的项目,或者能够直接开也行

    为什么要实现互斥锁:当我们利用Redis存储热点数据时,突然就过期失效或者被删除了,导致大量请求同时访问数据库,增加了数据库的负载。为减轻数据库的负载我们利用互斥锁。

    业务的一个逻辑图流程:

    在这里插入图片描述

    核心思路:相较于原来从缓存中查询不到数据后直接查询数据库而言,现在的方案是 进行查询之后,如果从缓存没有查询到数据,则进行互斥锁的获取,获取互斥锁后,判断是否获得到了锁,如果没有获得到,则休眠,过一会再进行尝试,直到获取到锁为止(这个尝试,要重新从Redis再次尝试获取数据,可能别的锁已经获取到了),才能进行查询

    如果获取到了锁的线程,再去进行查询,查询后将数据写入redis,再释放锁,返回数据,利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑,防止缓存击穿

    操作锁的核心思路就是利用redis的setnx方法来表示获取锁,该方法含义是redis中如果没有这个key,则插入成功,返回1

    具体实现

    • 设置锁,删除锁
       /**
         * 根据name对特定的数据进行锁
         * @param name
         * @return
         */
    public boolean setLock(String name) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(name, true, 10, TimeUnit.SECONDS));
    }
    
    public boolean releaseLock(String name) {
        return Boolean.TRUE.equals(redisTemplate.delete(name));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 具体流程实现
    @GetMapping("/getOneByLock/{sequence}")
    public BaseResponse<Sentences> getOneByLock(@PathVariable long sequence) {
        // 从redis中查信息
        String name = "test:redis:sentences:"+ sequence;
        Sentences sentence = (Sentences) redisTemplate.opsForValue().get(name);
        // 命中返回数据
        if(sentence != null ){
            redisTemplate.expire(name,2,TimeUnit.MINUTES);
            return ResultUtils.success(sentence);
        }
        // 未命中获取锁
        String LOCK_NAME = "test:redis:lock:" + sequence;
        boolean lock = redisTemplate.opsForValue().get(LOCK_NAME) != null && (boolean) redisTemplate.opsForValue().get(LOCK_NAME);
        //如果lock等于false 那么就可以获取到锁并且,锁住不许其他人操作
        if(!lock){
           return ResultUtils.success(setLockReleaseLockAboutSentence(LOCK_NAME,name,sequence));
        }
        // 没有获取到锁 休眠一段时间,并且反复检测redis中的数据是否存在,或者锁是否释放
        while(true){
            try {
                Thread.sleep(1000);
                log.error("等待中");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 检查是否存在值
            sentence =  (Sentences) redisTemplate.opsForValue().get(name);
            if(sentence != null){
                return ResultUtils.success(sentence);
            }
            boolean checkAgain = (boolean) redisTemplate.opsForValue().get(LOCK_NAME);
            if(!checkAgain){
                sentence =  setLockReleaseLockAboutSentence(LOCK_NAME,name,sequence);
            }
            return ResultUtils.success(sentence);
        }
    }
    
    public Sentences setLockReleaseLockAboutSentence(String LOCK_NAME,String redisName, long sequence){
        // 设置 锁值 为true
        setLock(LOCK_NAME);
        // 并且从数据中查取数据
        Sentences sentence = sentencesService.getById(sequence);
        // 这里为了明显不能抢锁设置一个睡眠时间
        try {
            log.error("休眠中");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    //            把数据写入Redis
        redisTemplate.opsForValue().set(redisName,sentence,2, TimeUnit.MINUTES);
        // 释放锁
        releaseLock(LOCK_NAME);
        // 返回数据
        return sentence;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    代码说明,在这个代码中为了演示明显,获取锁中延迟3s,竞争锁会延迟1s,下面的演示,初始时Redis中没有数据,只能去数据库中取数据,但是设置了互斥锁,所以只能够一个线程进入数据库取数据,其他只能等待数据得到结果。

    结果示意

    • redis中无数据

    在这里插入图片描述

    • 结果

    在这里插入图片描述

    最终效果是好的。redis中已存入数据

    在这里插入图片描述

  • 相关阅读:
    互换性测量技术-几何误差
    pandas分组与聚合groupby()函数详解
    2024 年最新 windows 操作系统部署安装 redis 数据库详细教程(更新中)
    Jmeter入门
    C#版本与.NET版本对应关系以及各版本的特性
    PCR检测试剂——博迈伦
    yxy销售网站后台管理系统
    python从入门到实践:python常用模块
    生成器建造者模式(Builder)——创建型模式
    linux技巧:vim分屏显示文件
  • 原文地址:https://blog.csdn.net/Go_ahead_forever/article/details/133325591