【Redis 6】缓存穿透、缓存雪崩、缓存击穿(附解决方案、代码)(一)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Redis 6】缓存穿透、缓存雪崩、缓存击穿(附解决方案、代码)(一)

🍖 缓存穿透

🥩 原理以及解决方案

 缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样请求就会先访问缓存再打到数据库。这原本是个正常现象并没有什么事情,但是如果有人借助这个漏洞一直请求缓存和数据库中都不存在的数据,那么就极有可能导致数据库崩溃。


 常见的解决方案有两种:第一种就是使用缓存空对象,也就是当请求发送过来缓存和数据库中都没有数据的时候,将该请求的结果为null并设置过期时间存入缓存中,然后返回401状态码,等到下一次请求时直接返回null即可。这样做的优点就是实现简单方便维护,但是造成额外内存损耗的缺点也很明显,过期时间就可以降低该缺点的影响


 第二种方案就是在缓存查询之前使用布隆过滤器,布隆过滤器就是由byte数组和一系列哈希函数两部分组成的数据结构,将数据使用hash函数计算出hash值,然后将这个hash值转成二进制位保存至布隆过滤器中,请求发送过来的话就计算出它的hash值,对应位置为1就说明存在0就是不存在。布隆过滤器的优点是不用频繁添加缓存内存占用小,但是缺点是实现相对复杂,而且会出现误判,布隆过滤器判断不存在的值一定不存在,它判断存在的值不一定就存在


🥩 缓存空对象代码实现

 缓存空对象和之前的查询相比无非就是两步,一是缓存和数据库中都查不到的话就往缓存中添加然后返回错误信息,二是缓存中查到数据进行非空判断,如果是""串的话就刷新TTL然后返回错误信息,下面的代码中的15~19行 22~26行中分别有体现


/**
 * 根据id查询商铺信息,涉及到redis的缓存
 * @param id  商铺id
 * @return 前端返回信息
 */
@Override
public Result queryById(Long id) {
    // 从redis查询商铺缓存
    String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
    // 判断该商铺缓存中是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        // 存在直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    } else if ("".equals(shopJson)) {
        // 缓存中存在但是结果为""空字符串 也就是说之前使用 缓存空对象 方案时存入的,这样的话就刷新它的缓存时间返回异常
        stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY + id, RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
        return Result.fail("店铺不存在");
    }
    // 不存在查询数据库
    Shop shop = getById(id);
    if (shop == null) {
        // 数据库中不存在 将null存入缓存设置过期时间2min并返回错误信息
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
        return Result.fail("店铺不存在");
    }
    // 数据库中存在写入redis
    stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
    // 返回
    return Result.ok(shop);
}

image.png



🍖 缓存雪崩

🥩 原理以及解决方案

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

 为了解决大量缓存数据同时失效,可以将数据的TTL设置为随机值,而不是使用一个固定值。为了解决Redis服务宕机,可以使用主从架构的集群提高服务的可用性,万一出现宕机可以使用从节点顶上。为了进一步防止缓存雪崩,我们还可以给缓存业务添加降级限流策略,也就是说当redis发生故障的时候可以直接拒绝服务而不是继续访问数据库;或者给业务添加多级缓存,在浏览器、nginx、redis、jvm、数据库等一层层的添加缓存


🍖 缓存击穿

🥩 原理以及解决方案

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


 为了解决缓存击穿可以使用互斥锁,如果发生缓存击穿后,第一个请求查询数据库中该数据的时候,使用一个锁锁住,后续的所有请求在锁未放开之前访问这个数据就让它休眠一会重新查询缓存。这个方案优点就不说了,缺点就是在第一个线程写缓存期间,其他访问该数据的线程拿不到锁就只能处于等待状态,所以说这就很损耗性能


 还有一种方案就是逻辑过期,顾名思义逻辑过期就是不作真正的删除,而是使用一个字段存储过期时间代替TTL的过期删除,所有的线程在获取到数据的时候都去通过过期时间字段判断是否过期,过期的话就新建一个线程先更新数据库再删除缓存,自己就返回已过期的数据,在此期间所有的访问都会返回过期数据,等到新建线程的任务完成之后再次访问的线程就负责添加新的缓存数据并返回新的数据


🥩 互斥锁代码实现

 互斥锁方案解决缓存击穿相比较于缓存空对象解决缓存穿透的方案而言,最大的不同就是,在从缓存中查询到数据的情况下,需要先判断一下是否可以获得该数据对应的锁,可以就查询数据库并写入缓存,否则线程休眠重新调用该方法,上述代码需要放在try catch中使用finally释放锁,以上思路在21~40代码中实现


/**
 * 查询商铺信息 互斥锁解决缓存击穿
 * @param id  商铺编号
 * @return 查询到的商铺信息
 */
public Shop queryWithMutex(Long id) {
    // 从redis查询商铺缓存
    String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
    // 判断该商铺缓存中是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        // 存在直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return shop;
    } else if ("".equals(shopJson)) {
        // 缓存中存在但是结果为""空字符串 也就是说之前使用 缓存空对象 方案时存入的,这样的话就刷新它的缓存时间返回异常
        stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY + id, RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
        return null;
    }
    // 不存在先查看是否可以获取到锁,如果可以就休眠重试,否则就查询数据库写缓存
    Shop shop = null;
    try {
        if (!tryLock(RedisConstants.LOCK_SHOP_KEY + id)) {
            // 获取锁失败
            Thread.sleep(50);
            queryWithMutex(id);
        }
        shop = getById(id);
        if (shop == null) {
            // 数据库中不存在 将null存入缓存设置过期时间2min并返回错误信息
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        // 数据库中存在写入redis
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        // 释放互斥锁
        unLock(RedisConstants.LOCK_SHOP_KEY + id);
    }
    // 返回
    return shop;
}

🥩 逻辑过期代码实现

 逻辑过期的前提条件是先向缓存中存入一个带有过期时间字段的商铺信息,也就是模拟现将需要做活动的商品信息存入到redis缓存中,然后再对缓存进行查询,如果缓存中不存在就返回null,存在的话就进行逻辑过期的操作


// 创建线程池 用于更新缓存时间过期的时候 更新缓存使用
private static final ExecutorService CACH_REBUILD_EXECUTOR = Executors.newFixedThreadPool(5);
/**
 * 查询商铺信息 逻辑过期解决缓存击穿
 * @param id  商铺编号
 * @return 查询到的商铺信息
 */
public Shop queryWithLogicalExpire(Long id) {
    // 从redis查询商铺缓存
    String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
    // 判断该商铺缓存中是否存在
    if (StrUtil.isBlank(shopJson)) {
        // 不存在直接返回null
        return null;
    }
    // 存在 反序列化获取 店铺信息 和 expire字段
    RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
    Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
    LocalDateTime expireTime = redisData.getExpireTime();
    // 判断是否过期 过期时间在当前时间之后即为未过期
    if (expireTime.isAfter(LocalDateTime.now())) {
        // 未过期直接返回店铺信息
        return shop;
    }
    // 已过期 尝试获取锁
    if (tryLock(RedisConstants.LOCK_SHOP_KEY + id)) {
        // 获取锁成功 开启独立线程去做缓存重建
        CACH_REBUILD_EXECUTOR.submit(() -> {
            // 创建缓存
            try {
                saveShopWithExpireTimeToRedis(id, 20L);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                // 释放锁
                unLock(RedisConstants.LOCK_SHOP_KEY + id);
            }
        });
    }
    // 返回
    return shop;
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
14天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
15天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
8天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
25 5
|
23天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
132 22
|
22天前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
121 7
|
27天前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
62 10
|
27天前
|
缓存 监控 NoSQL
Redis 缓存穿透的检测方法与分析
【10月更文挑战第23天】通过以上对 Redis 缓存穿透检测方法的深入探讨,我们对如何及时发现和处理这一问题有了更全面的认识。在实际应用中,我们需要综合运用多种检测手段,并结合业务场景和实际情况进行分析,以确保能够准确、及时地检测到缓存穿透现象,并采取有效的措施加以解决。同时,要不断优化和改进检测方法,提高检测的准确性和效率,为系统的稳定运行提供有力保障。
48 5
|
缓存 NoSQL 数据库
什么是redis的缓存雪崩与缓存穿透
什么是redis的缓存雪崩与缓存穿透今天来分享一下Redis几道常见的面试题: 如何解决缓存雪崩?如何解决缓存穿透?如何保证缓存与数据库双写时一致的问题?一、缓存雪崩1.1 什么是缓存雪崩?首先我们先来回答一下我们为什么要用缓存(Redis): 1、提高性能能:缓存查询是纯内存访问,而硬盘是磁盘访问,因此缓存查询速度比数据库查询速度快 2、提高并发能力:缓存分组了部分请求,支持更高的并发 现在有个问题,如果我们的缓存挂掉了,这意味着我们的全部请求都跑去数据库了。
1295 0
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
下一篇
无影云桌面