Redis-更新策略,缓存穿透,缓存雪崩,缓存击穿

Redis-更新策略,缓存穿透,缓存雪崩,缓存击穿

1.缓存更新 策略

  1. 淘汰策略
  2. 超时剔除
  3. 主动更新
    1. 更新策略:先修改数据库还是先删除缓存 结论:先修改数据库,因为缓存的操作比较快,容易产生数据不一致
    2. 更新缓存还是删除缓存?

2.缓存穿透

客户端请求的数据在缓存和数据库中都不存在,这些请求会访问到数据库

解决方式

  1. 缓存空值:额外内存空间; 短期造成数据不一致
  2. 布隆过滤器,把数据转换成二进制的情况存储,即使在布隆过滤其中存在,实际上也可能不存在,因此有一定的风险
  3. 增加id复杂度,主动预防缓存穿透情况
  4. 增强用户的权限

3.缓存雪崩

是指在同一时间大量的缓存失效或redis服务器宕机,导致大量的请求同时访问数据库。

解决方式:

  1. 设置缓存key随机的TTL
  2. 增加redis服务高可用
  3. 大量的请求限流
  4. 多级缓存,nginx,jvm,浏览器等
  5. 设置热点数据不过期

4.缓存击穿

缓存击穿也叫热点Key问题,一个被高并发访问并且缓存业务重建复杂的key失效了,导致大量的key访问数据库带来冲击。

解决方式:

  1. 互斥锁

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 逻辑过期

    在这里插入图片描述

在这里插入图片描述

  1. 热点数永不过期

  2. 限流熔断

缓存击穿-互斥锁代码实现:
 /** 缓存数据KEY */
    private final String CACHE_SHOP_KEY = "CACHE_SHOP_KEY:";
    /** 缓存互斥锁KEY */
    private final String CACHE_SHOP_LOCK_KEY = "CACHE_SHOP_LOCK_KEY:";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;




/**
     * redis缓存
     * 缓存击穿-互斥锁版本
     * @param bookId
     */
    private BooksVo tryCacheMutex(Long bookId) {
        // RedisKey
        String cacheKey = CACHE_SHOP_KEY + bookId;
        // 1.从Redis查询商铺缓存
        // 获取缓存数据
        String contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否命中
        if (StringUtils.isNotBlank(contentBook)){
            // 3.1缓存命中 直接返回结果
            return JSONUtil.toBean(contentBook, BooksVo.class);
        }
        BooksVo booksVo = null;
        try {
            // 3.2缓存未命中,尝试获取互斥锁
            if (BooleanUtil.isFalse(tryLock(bookId))) {
                // 4.1获取互斥锁失败,尝试重试
                Thread.sleep(50);
                return tryCacheMutex(bookId);
            }
            // 4.2 获取互斥锁成功
            // 4.3 再次检测缓存是存在 doubleCheck
            contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
            if (StringUtils.isNotBlank(contentBook)){
                return JSONUtil.toBean(contentBook, BooksVo.class);
            }
            // 4.4 查询数据库,缓存重建
            booksVo = this.queryById(bookId);
            // 模拟缓存重建延迟
            Thread.sleep(200);
            String jsonBook = JSONUtil.toJsonStr(booksVo);
            stringRedisTemplate.opsForValue().set(cacheKey,jsonBook);
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }finally {
            // 5.释放锁
            stringRedisTemplate.delete(CACHE_SHOP_LOCK_KEY + bookId);
        }

        return booksVo;
    }

    /**
     * 获取互斥锁
     */
    public boolean tryLock(Long bookId){
        // redis: set xxx value nx ex 10 添加锁nx是互斥,ex设置过期时间
        Boolean aBoolean = stringRedisTemplate.opsForValue()
            .setIfAbsent(CACHE_SHOP_LOCK_KEY + bookId, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

后期更新逻辑过期的实现方式

========20240209

更新一波工具类完成互斥锁操作

@Slf4j
@Component
@AllArgsConstructor
public class PikerRedisUtils {

    private final StringRedisTemplate stringRedisTemplate;


    /**
     * 存储String值,设置TTL
     */
    private void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 存储逻辑过期时间
     */
    private void setExpiration(String key, Object value, Long time, TimeUnit unit){
        // 封装过期时间
        RedisData redisData = new RedisData();
        redisData.setData(JSONUtil.toJsonStr(value));
        redisData.setExpireSecond(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /**
     * <R, ID> 用于指定此方法所处理的泛型类型:
     *   - R:代表方法返回的结果类型,即从缓存或数据库中获取的具体业务对象类型。
     *   - ID:标识要查询的特定资源的唯一标识符类型,通常为某种主键或唯一键。

     * 方法功能:
     *   实现缓存击穿防护机制,结合互斥锁(Mutex)避免大量并发请求直接穿透缓存到达数据库,
     *   造成数据库压力过大。适用于高并发场景下对同一资源(由 `ID` 标识)的频繁访问。

     * 参数说明:
     *   - `key`:用于构造缓存键的基础部分,与 `id` 结合形成完整的缓存键。
     *   - `id`:指定要查询资源的唯一标识符,用于从缓存或数据库中定位具体数据。
     *   - `tClass`:`Class<R>` 类型参数,表示期望从缓存或数据库中获取数据的类类型,用于反序列化 JSON 数据。
     *   - `time`:表示在尝试获取互斥锁时愿意等待的最大时间量。
     *   - `unit`:时间单位,与 `time` 结合确定等待锁的实际时长。
     *   - `lockKey`:互斥锁的键名,在 Redis 中用于实现分布式锁。
     *   - `dbFallback`:`Function<ID, R>` 类型参数,提供一个回调函数,当缓存未命中时,用于查询数据库并返回 `R` 类型数据。

     * 方法逻辑:
     *   1. 构建基于 `key` 和 `id` 的 Redis 缓存键。
     *   2. 尝试从 Redis 中获取缓存数据。
     3. 若缓存命中,则直接反序列化并返回缓存中的数据。
     4. 若缓存未命中:
     a. 尝试获取互斥锁,若获取失败则短暂休眠后递归调用自身重试。
     b. 若成功获取互斥锁,进行双重检查以确认缓存是否在等待锁期间已被其他线程填充。
     c. 若缓存仍为空,则调用 `dbFallback` 函数查询数据库,获取 `R` 类型数据。
     d. 将数据库查询结果转化为 JSON 存储至 Redis,完成缓存重建。
     e. 最后释放互斥锁,确保资源的正确释放。

     * 返回值:
     *   返回与给定 `id` 对应的 `R` 类型数据,该数据来源于缓存(优先)或数据库查询(缓存未命中时)。
     *
     */
    public <R,ID> R getMutex(String key, ID id, Class<R> tClass, Long time,
                             TimeUnit unit, String lockKey, Function<ID, R> dbFallback){
        // RedisKey
        String cacheKey = key + id;
        // 1.从Redis查询商铺缓存
        // 获取缓存数据
        String contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否命中
        if (StringUtils.isNotBlank(contentBook)){
            // 3.1缓存命中 直接返回结果
            return JSONUtil.toBean(contentBook, tClass);
        }
        R r = null;
        try {
            // 3.2缓存未命中,尝试获取互斥锁
            if (BooleanUtil.isFalse(tryLock(lockKey, time ,unit))) {
                // 4.1获取互斥锁失败,尝试重试
                Thread.sleep(50);
                return getMutex(key, id, tClass, time, unit, lockKey, dbFallback);
            }
            // 4.2 获取互斥锁成功
            // 4.3 再次检测缓存是存在 doubleCheck
            contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
            if (StringUtils.isNotBlank(contentBook)){
                return JSONUtil.toBean(contentBook, tClass);
            }
            // 4.4 查询数据库,缓存重建
            r = dbFallback.apply(id);
            // 模拟缓存重建延迟
            // Thread.sleep(200);
            String jsonBook = JSONUtil.toJsonStr(r);
            stringRedisTemplate.opsForValue().set(cacheKey,jsonBook);
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }finally {
            // 5.释放锁
            stringRedisTemplate.delete(lockKey);
        }

        return r;
    }
    
    
    /**
     * 获取互斥锁
     */
    public boolean tryLock(String lockKey,Long time,  TimeUnit unit){
        // redis: set xxx value nx ex 10 添加锁nx是互斥,ex设置过期时间
        Boolean aBoolean = stringRedisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", time, unit);
        return BooleanUtil.isTrue(aBoolean);
    }

调用

@Override
    public BooksVo selectById(Long bookId) {
        // 缓存击穿-工具类-互斥锁
        return pikerRedisUtils.getMutex(CACHE_SHOP_KEY,bookId,BooksVo.class, 10L,
            TimeUnit.SECONDS, CACHE_SHOP_LOCK_KEY+bookId,item -> baseMapper.selectVoById(bookId));

        // 缓存击穿-互斥锁
        // return tryCacheMutex(bookId);

        // 缓存击穿-逻辑过期时间
        // return tryCacheMutex2(bookId);

    }

相关推荐

  1. Redis缓存击穿缓存雪崩缓存穿透

    2024-04-11 19:54:03       32 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-11 19:54:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-11 19:54:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-11 19:54:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-11 19:54:03       18 阅读

热门阅读

  1. Go-学会 map 的基本使用

    2024-04-11 19:54:03       17 阅读
  2. Android.mk文件中添加so

    2024-04-11 19:54:03       14 阅读
  3. 正交投影的矩阵(基变换与过渡矩阵的例子)

    2024-04-11 19:54:03       23 阅读
  4. 【QT教程】QT6 Web开发入门

    2024-04-11 19:54:03       15 阅读
  5. ubuntu22.04 静态IP设置脚本

    2024-04-11 19:54:03       14 阅读
  6. set和map

    set和map

    2024-04-11 19:54:03      11 阅读
  7. FP独立站收款必备!AB站跳转轮询全解析

    2024-04-11 19:54:03       13 阅读
  8. 创业之路:从市场洞察到产品实现的全方位指南

    2024-04-11 19:54:03       11 阅读