Redis 篇-深入了解查询缓存与缓存所带来的问题(读写不一致、缓存穿透、缓存雪崩、缓存击穿)

🔥博客主页: 【小扳_-CSDN博客】**
❤感谢大家点赞👍收藏⭐评论✍**

本章目录

[1.0 什么是缓存](#1.0 什么是缓存)

[2.0 项目中具体如何添加缓存](#2.0 项目中具体如何添加缓存)

[3.0 添加缓存后所带来的问题](#3.0 添加缓存后所带来的问题)

[3.1 读写不一致问题](#3.1 读写不一致问题)

[3.1.1 缓存更新策略](#3.1.1 缓存更新策略)

[3.1.2 具体实现缓存与数据库的双写一致](#3.1.2 具体实现缓存与数据库的双写一致)

[3.2 缓存穿透问题](#3.2 缓存穿透问题)

[3.2.1 具体解决缓存穿透问题](#3.2.1 具体解决缓存穿透问题)

[3.3 缓存雪崩问题](#3.3 缓存雪崩问题)

[3.4 缓存击穿问题](#3.4 缓存击穿问题)

[3.4.1 利用互斥锁解决缓存击穿问题](#3.4.1 利用互斥锁解决缓存击穿问题)

[3.4.2 利用逻辑过期解决缓存击穿问题](#3.4.2 利用逻辑过期解决缓存击穿问题)

[](#4.0 封装 Redis 工具类)[4.0 封装 Redis 工具类](#4.0 封装 Redis 工具类)


1.0 什么是缓存

缓存就是数据交换的缓冲器,称作为 Cache,是存放数据的零时地方,一般读写性能较高。缓存的作用可以降低后端负载,提高读写效率、降低响应时间。缓存的成本包括数据一致性成本、代码维护成本、运维成本等。

2.0 项目中具体如何添加缓存

举例子,在实现根据用户 id 来查询用户信息的功能中,添加缓存的步骤:

首先,提交用户 id ,先从缓存中查找是否命中目标,就是是否有相同的 id 关键字 key 。如果命中,直接返回该 key 对应的 value 即可;如果没有命中,就需要来到数据库中查询用户信息,继续判断数据库中是否存在该用户 id ,如果不存在,那么返回报错信息;如果存在,那么返回该用户信息的同时,将用户信息写回到 Redis 缓存中。

缓存作用模型图:

代码实现:

java 复制代码
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Override
    public String getUserNameById(Integer userId) throws Exception {
        //先判断userId是否为空
        if (userId == null){
            throw new Exception("userId is null");
        }
        //先从缓存中查看是否存在该key
        String s = stringRedisTemplate.opsForValue().get("user:" + userId);
        if (s != null){
            //如果缓存中不为null,则成功从缓存中获取值
            return s;
        }

        //如果从缓存中获取不到,则需要到数据库中获取数据
        String userName = adminMapper.getUserNameById(userId);
        //如果数据返回为null,那么数据库中查找不到数据
        if (userName == null){
            //直接抛出异常
            throw new Exception("根据该用户id查找不到用户信息");
        }
        //判断数据不为null之后,则需要将该用户信息写到redis中
        stringRedisTemplate.opsForValue().set("user:"+userId,userName);
        //最后返回值即可
        return userName;

    }

运行结果:

在第一次查询的时候,redis 第一次时找不到该用户信息,那么就会到数据库中查询,查询完毕之后,将数据写回到 redis 中,再到第二次查询的时候,就可以直接到 redis 中获取数据了。

发送的请求:

第一次获取数据:

到数据库中获取了

此时 redis 中:

已经存在该用户信息了

3.0 添加缓存后所带来的问题

添加缓存之后,会带来一些问题,比如说:数据库更新之后,缓存还没来得及更新所带来的缓存与数据库数据不一致问题,还有缓存穿透、缓存雪崩、缓存击穿等问题给数据库带来的沉重的"打击"。

3.1 读写不一致问题

顾名思义,数据库与缓存中的数据两者不一致,为了解决这个问题,就有了缓存更新策略,可以极大可能维护缓存中的数据和数据库中的数据一致性。

3.1.1 缓存更新策略

通常的方法有三种:

1)内存淘汰:不用自己维护,利用 redis 的内存淘汰机制,当内存不足自动淘汰部分数据,下次查询时更新缓存。该方法一致性比较差,无维护成本。

2)超时剔除:给缓存数据添加 TTL 时间,到期后自动删除缓存,下次查询时更新缓存。该方法一致性一般,维护成本低。

3)主动更新:

编写业务逻辑,在修改数据库的同时,更新缓存。该方法一致性比较好,维护成本高。主动更新包含三种常见的策略:

第一种:Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存。

第二种:Read/Write Through Pattern:缓存与数据库整合一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。

第三种:Write Behind Caching Pattern:调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致。

在主动更新中,第一种方式比较常见,实现比较简单。但是在操作缓存和数据库时有三个问题需要考虑:

第一个问题:删除缓存还是更新缓存?

更新缓存:每次更新数据库都更新缓存,无效写操作较多。

删除缓存:更新数据库时让缓存失效,查询时在更新缓存。

因此,一般来说,选择删除缓存。

第二个问题:如何保证缓存与数据库的操作的同时成功或失败?

将缓存与数据库操作放在同一个事务即可,保证其原子性。

第三个问题:先操作缓存还是先操作数据库?

先写数据库,然后删除缓存。

缓存更新策略的最佳实践方案:

1)低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存。

2)高一致性需求:主动更新,并以超时剔除作为兜底方案。

读操作:

缓存命中则直接返回,缓存未命中则查询数据库,并写入缓存,设定超时时间。

写操作:

先写数据库,然后再删除缓存,要确保数据库与缓存操作的原子性。

3.1.2 具体实现缓存与数据库的双写一致

实现高一致性需求:主动更新策略代码:

1)读操作:缓存命中则直接返回,缓存未命中则查询数据库,并写入缓存,设定超时时间。

java 复制代码
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Override
    public String getUserNameById(Integer userId) throws Exception {
        //先判断userId是否为空
        if (userId == null){
            throw new Exception("userId is null");
        }
        //先从缓存中查看是否存在该key
        String s = stringRedisTemplate.opsForValue().get("user:" + userId);
        if (s != null){
            //如果缓存中不为null,则成功从缓存中获取值
            return s;
        }

        //如果从缓存中获取不到,则需要到数据库中获取数据
        String userName = adminMapper.getUserNameById(userId);
        //如果数据返回为null,那么数据库中查找不到数据
        if (userName == null){
            //直接抛出异常
            throw new Exception("根据该用户id查找不到用户信息");
        }
        //判断数据不为null之后,则需要将该用户信息写到redis中,且设定超时时间
        stringRedisTemplate.opsForValue().set("user:"+userId,userName,100, TimeUnit.SECONDS);

        //最后返回值即可
        return userName;
    }

这里的重点是:设置超时时间。

2)写操作:先写数据库,然后再删除缓存,要确保数据库与缓存操作的原子性。

为了保证原子性,需要加上 @Transactional 注解

java 复制代码
    @Override
    @Transactional
    public void modifyUser(UserDTO userDTO) throws Exception {
        //先判断userDTO是否为null
        if (userDTO == null){
            throw new Exception("userDTO is null");
        }
        //先更新数据库
        adminMapper.modifyUser(userDTO);
        //再删除redis缓存
        Integer userId = userDTO.getUserId();
        stringRedisTemplate.delete("user"+userId);
    }

运行结果:

先查询用户信息,因为第一次 redis 不存在该用户信息,因此需要到数据库中获取该用户信息。

从数据库中查询信息:

redis 缓存情况:

接着去更新用户信息:

此时,redis 中的用户信息就被删除掉了:

下一次查询就需要到数据库中查询了。

再一次查询:

会到数据库中查询用户信息。

3.2 缓存穿透问题

是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远都不会生效,这些请求都会打到数据库。则会给数据库的压力非常大,因此需要解决这种情况发生。

常见的解决方案有四种:

1)增强 id 的复杂度,避免被猜测 id 规律。

2)做好数据的基础格式校验。

3)缓存空对象:实现简单,维护方便。

该方法的缺点:额外的内存消耗,因为设置 key 对应的 value 为 null ,占用了一定的缓存空间,因此为了减少内存浪费,会设置缓存时间 TTL ;还可能造成短期的不一致,当数据库中 key 有对应的 value 了,当前的 key 还在缓存中,value 还是为 null ,所以造成一定的不一致性。

4)布隆过滤:内存占用较少,没有多余 key ,该方法的缺点为实现复杂,存在误判的可能。

3.2.1 具体解决缓存穿透问题

使用缓存空对象来解决缓存穿透问题步骤:

首先,从缓存中查询用户,判断缓存是否命中,如果命中,则直接返回用户信息;如果没有命中,根据用户 id 到数据库中查询用户信息,如果用户信息不为 null ,则说明用户信息是存在的,那么将用户信息写回到缓存中,方便下一次查询可以直接从缓存中获取用户信息;如果用户信息为 null ,则说明数据库中也不存在该用户信息,那么下一次就不需要继续查询该用户信息了,让其在缓存中查询,再抛出异常即可。

具体的流程图:

代码如下:

java 复制代码
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Override
    public String getUserNameById(Integer userId) throws Exception {
        //先判断userId是否为空
        if (userId == null){
            throw new Exception("userId is null");
        }
        //先从缓存中查看是否存在该key
        String s = stringRedisTemplate.opsForValue().get("user:" + userId);

        if (StrUtil.isNotBlank(s)){
            //如果缓存中不为null,则成功从缓存中获取值
            return s;
        }

        if (s != null){
            //直接抛出异常
            throw new Exception("该用户信息不存在!");
        }

        //如果从缓存中获取不到,则需要到数据库中获取数据
        String userName = adminMapper.getUserNameById(userId);
        //如果数据返回为null,那么数据库中查找不到数据
        if (userName == null){
            //如果在数据库中找不到该信息,则将该 key 值对应的 value 为 "" 写到缓存中
            stringRedisTemplate.opsForValue().set("user:"+userId,"",100,TimeUnit.SECONDS);
        }
        //判断数据不为null之后,则需要将该用户信息写到redis中,且设定超时时间
        stringRedisTemplate.opsForValue().set("user:"+userId,userName,100, TimeUnit.SECONDS);

        //最后返回值即可
        return userName;
    }

运行结果:

查询数据库不存在的用户信息:

第一次会到数据库查询该用户信息,当该用户信息不存在时,则会在 redis 中设置空值,这样的好处,下一次的查询该用户,就不会打到数据库中了,减少了数据库的压力。

3.3 缓存雪崩问题

是指在同一时间段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库,带来巨大压力。

3.3.1 解决缓存雪崩方案

1)给不同的 key 的 TTL 添加随机值。

2)利用 Redis 集群提高服务的可用性。

3)给缓存业务添加降级限流策略。

4)给业务添加多级缓存。

3.4 缓存击穿问题

缓存击穿问题也叫热点 Key 问题,就是一个被高并发访问并且缓存重建业务交复杂的 Key 突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

如图:

常见的解决方法:

1)利用互斥锁解决击穿问题

没有额外的内存消耗,保证一致性,实现简单。该方法的缺点:线程需要等待,性能受影响,可能有死锁的风险。

2)利用逻辑过期解决缓存击穿问题

线程无需等待,性能较好。该方法的缺点,不保证一致性,有额外的内存消耗,实现复杂。

3.4.1 利用互斥锁解决缓存击穿问题

利用互斥锁解决的步骤:

首先,查询缓存是否命中,如果命中,直接返回;如果没有命中,则需要判断是否能获取互斥锁,如果获取到了互斥锁,则查询数据库重建缓冲数据,最后释放锁,再返回数据;如果没有获取互斥锁,则休眠一段时间,再重试,直到从缓冲中获取到数据返回。

流程图:

代码如下:

解决缓存穿透与缓存击穿:

java 复制代码
    //解决缓存穿透与缓存击穿
    public String getUserNameById2(Integer userId) throws Exception {
        //先判断userId是否为空
        if (userId == null){
            throw new Exception("userId is null");
        }
        //先从缓存中查看是否存在该key
        String s = stringRedisTemplate.opsForValue().get("user:" + userId);

        if (StrUtil.isNotBlank(s)){
            //如果缓存中不为null,则成功从缓存中获取值
            return s;
        }

        if (s != null){
            //直接抛出异常
            throw new Exception("该用户信息不存在!");
        }

        //如果从缓存中获取不到,则需要到数据库中获取数据
        //判断释放可以获取到锁
        String lock = "getLock";

        String userName = null;
        try {
            boolean b = tryLock(lock);
            if (!b) {
                //如果没有获取到锁,休眠一会,再重新从缓存中获取数据
                Thread.sleep(50);
                return getUserNameById2(userId);

            }

            userName = adminMapper.getUserNameById(userId);
            //如果数据返回为null,那么数据库中查找不到数据
            if (userName == null){
                //如果在数据库中找不到该信息,则将该 key 值对应的 value 为 "" 写到缓存中
                stringRedisTemplate.opsForValue().set("user:"+userId,"",100,TimeUnit.SECONDS);
            }
            //判断数据不为null之后,则需要将该用户信息写到redis中,且设定超时时间
            stringRedisTemplate.opsForValue().set("user:"+userId,userName,100, TimeUnit.SECONDS);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            unlock(lock);
        }

        //返回值即可
        return userName;
    }


    //获取锁
    private boolean tryLock(String key){
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 200, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }

3.4.2 利用逻辑过期解决缓存击穿问题

设置缓存中 key 的逻辑过期,顾名思义:在实际上,缓存中的 key 是设置永远不过期,将其添加过期字段,通过查看该字段,来判断该 key 在缓存中是否已经过期了。

利用逻辑过期解决缓存击穿问题步骤:

首先,判断缓存是否命中,如果没有命中,则返回空;如果命中,继续判断该字段是否过期,如果没有过期,则直接获取并且返回该值;如果已经过期,再继续判断能否获取锁,如果获取锁失败,则直接返回已经过期的值;如果获取锁成功,创建一个线程来做查询数据库,并且写入到缓存中,对于主线程来说,仍然返回旧的数据。

流程图:

代码实现:

利用逻辑过期实现解决缓存击穿问题:

java 复制代码
    //获取锁
    private boolean tryLock(String key){
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 200, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }

    //解决缓存穿透

    public String getUserNameById(Integer userId) throws Exception {
        //先判断userId是否为空
        if (userId == null){
            throw new Exception("userId is null");
        }
        //先从缓存中查看是否存在该key
        String s = stringRedisTemplate.opsForValue().get("user:" + userId);

        //如果从缓存中没有获取到数据,则直接抛出异常
        if (s == null){
            throw new Exception("该用户不存在!!!");
        }

        //反序列化
        RedisData redisData = JSON.parseObject(s, RedisData.class);
        String data = (String) redisData.getData();
        LocalDateTime localDateTime = redisData.getLocalDateTime();
        //判断是否过期
        if (localDateTime.isAfter(LocalDateTime.now())){
            //如果没有过期,则直接返回数据
            return data;

        }

        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //如果过期了
        //判断能否获取到互斥锁
        String lock = "getLock";

        boolean b = tryLock(lock);
        if (b) {
            //获取到锁,从线程池中获取一个线程来从数据库获取信息,再将信息写入到缓存中
            pool.submit(() -> {
                try {
                    //先从数据库中获取到数据
                    String userName = adminMapper.getUserNameById(userId);

                    //再将数据写入到缓存中
                    RedisData red = new RedisData();

                    //设置过期时间
                    red.setLocalDateTime(LocalDateTime.now().plusSeconds(100L));
                    red.setData(userName);
                    //将其序列化
                    String jsonString = JSON.toJSONString(red);
                    stringRedisTemplate.opsForValue().set("user:"+userId,jsonString);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lock);
                }
            });

        }

        //最后返回
        return data;
    }

4.0 封装 Redis 工具类

基于 StringRedisTemplate 封装一个缓存工具类,满足下列需要:

1)方法1:将任意 Java 对象序列化为 json 并存储在 string 类型的 key 中,并且可以设置 TTL 过期时间。

代码如下:

java 复制代码
    public void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value),time,timeUnit);
    }

2)方法2:将任意 Java 对象序列化为 json 并存储在 string 类型的 key 中,并且可以设置逻辑过期时间,用于处理缓存击穿问题。

代码如下:

java 复制代码
    public void setWithLogicalExpire(String key,Object value,Long time,TimeUnit timeUnit){
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setLocalDateTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));

        String jsonString = JSON.toJSONString(redisData);
        stringRedisTemplate.opsForValue().set(key,jsonString);
        
    }

3)方法3:根据指定的 key 查询缓存,并反序列化为指定类型,利用缓存空值的方式解决穿透问题。

java 复制代码
    //利用缓存空值解决缓存穿透
    public <R,ID> R queryWithPassThrough(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit ){

        String key = prefix + id;
        //判断在缓存中是否能命中
        String jsonString = stringRedisTemplate.opsForValue().get(key);


        if (StrUtil.isNotBlank(jsonString)){
            //反序列化
            return JSON.parseObject(jsonString, type);
        }

        if (jsonString != null){
            return null;
        }

        //查询数据库,且将数据信息写入到缓存中
        R apply = function.apply(id);
        //判断是否为空值
        if (apply == null){
            //如果为空
            //将其写进缓存中
            stringRedisTemplate.opsForValue().set(key,"",50,TimeUnit.SECONDS);
            return null;
        }
        //序列化
        String json = JSON.toJSONString(apply);
        //如果不为空
        stringRedisTemplate.opsForValue().set(key,json,time,unit);
        return apply;

    }

4)方法4:根据指定的 key 查询缓存,并反序列为指定类型,需要利用逻辑过期解决缓存击穿问题。

java 复制代码
    //利用逻辑过期解决缓存击穿
    public <R,ID> R queryWithLogicalExpire(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit){
        String key = prefix + id;
        //判断在缓存中是否命中
        String s = stringRedisTemplate.opsForValue().get(key);
        //如果不存在,直接返回null
        if (s == null){
            return null;
        }
        //如果存在,还得判断是否过期
        //反序列化
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject d = (JSONObject) redisData.getData();
        R data = JSONUtil.toBean(d, type);

        LocalDateTime localDateTime = redisData.getLocalDateTime();
        if (localDateTime.isAfter(LocalDateTime.now())){
            //如果没有过期
            //直接返回数据
            return data;
        }
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //过期了,判断是否可以获取锁
        String lock = "getLock";
        boolean b = tryLock(lock);
        if (b){
            //如果获取锁成功,
            pool.submit(() -> {
                //从数据库中获取数据,再将数据写回缓存中
                try {
                    R apply = function.apply(id);
                    setWithLogicalExpire(key,apply,time,unit);

                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lock);
                }
            });
        }
        return data;
    }

5)完整 Redis 的工具类

java 复制代码
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.example.bookproject20.pojo.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    public CacheClient(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }


    public void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value),time,timeUnit);
    }

    public void setWithLogicalExpire(String key,Object value,Long time,TimeUnit timeUnit){
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setLocalDateTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));

        String jsonString = JSON.toJSONString(redisData);
        stringRedisTemplate.opsForValue().set(key,jsonString);
        
    }

    //利用缓存空值解决缓存穿透
    public <R,ID> R queryWithPassThrough(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit ){

        String key = prefix + id;
        //判断在缓存中是否能命中
        String jsonString = stringRedisTemplate.opsForValue().get(key);


        if (StrUtil.isNotBlank(jsonString)){
            //反序列化
            return JSON.parseObject(jsonString, type);
        }

        if (jsonString != null){
            return null;
        }

        //查询数据库,且将数据信息写入到缓存中
        R apply = function.apply(id);
        //判断是否为空值
        if (apply == null){
            //如果为空
            //将其写进缓存中
            stringRedisTemplate.opsForValue().set(key,"",50,TimeUnit.SECONDS);
            return null;
        }
        //序列化
        String json = JSON.toJSONString(apply);
        //如果不为空
        stringRedisTemplate.opsForValue().set(key,json,time,unit);
        return apply;

    }

    //利用逻辑过期解决缓存击穿
    public <R,ID> R queryWithLogicalExpire(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit){
        String key = prefix + id;
        //判断在缓存中是否命中
        String s = stringRedisTemplate.opsForValue().get(key);
        //如果不存在,直接返回null
        if (s == null){
            return null;
        }
        //如果存在,还得判断是否过期
        //反序列化
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject d = (JSONObject) redisData.getData();
        R data = JSONUtil.toBean(d, type);

        LocalDateTime localDateTime = redisData.getLocalDateTime();
        if (localDateTime.isAfter(LocalDateTime.now())){
            //如果没有过期
            //直接返回数据
            return data;
        }
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //过期了,判断是否可以获取锁
        String lock = "getLock";
        boolean b = tryLock(lock);
        if (b){
            //如果获取锁成功,
            pool.submit(() -> {
                //从数据库中获取数据,再将数据写回缓存中
                try {
                    R apply = function.apply(id);
                    setWithLogicalExpire(key,apply,time,unit);

                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lock);
                }
            });
        }
        return data;
    }


    //获取锁
    private boolean tryLock(String key){
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 200, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }



}

6)依赖:

XML 复制代码
        <!--fastJSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <!--redis、redis连接池依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>

7)Redis 配置:

XML 复制代码
  data:
    redis:
      password: 你的redis密码
      host: 你的redis主机号,IP地址
      lettuce:
        pool:
          max-active: 10
          max-idle: 10
          min-idle: 1
          time-between-eviction-runs: 10s
      database: 0
相关推荐
武昌库里写JAVA3 分钟前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_192849990610 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
Q_192849990614 分钟前
基于Spring Boot的营销项目系统
spring boot
sdaxue.com16 分钟前
帝国CMS:如何去掉帝国CMS登录界面的认证码登录
数据库·github·网站·帝国cms·认证码
张国荣家的弟弟27 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S38 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
o(╥﹏╥)1 小时前
linux(ubuntu )卡死怎么强制重启
linux·数据库·ubuntu·系统安全
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
海海不掉头发1 小时前
苍穹外卖-day05redis 缓存的学习
学习·缓存