当前位置: 首页 > backend >正文

Redis的缓存穿透、缓存击穿和缓存雪崩

文章目录

    • 一、Redis缓存问题概述
    • 二、缓存穿透
      • 1. 定义
      • 2. 解决方案
      • 3. 代码示例
    • 三、缓存击穿
      • 1. 定义
      • 2. 解决方案
      • 3. 代码示例
    • 四、缓存雪崩
      • 1. 定义
      • 2. 解决方案
      • 五、封装Redis工具类

一、Redis缓存问题概述

Redis缓存穿透、击穿和雪崩是缓存机制中常见的问题,具体如下:

  1. 缓存穿透(Cache Penetration):查询不存在的数据,请求穿过缓存层直达数据库,增加数据库压力。攻击者可构造恶意请求引发此问题。
  2. 缓存击穿(Cache Breakdown):热点数据失效,大量并发请求直接访问数据库,可能导致数据库崩溃。通常因热点数据过期,同时有大量请求访问该数据。
  3. 缓存雪崩(Cache Avalanche):大量缓存数据同时失效,大量请求直接访问数据库,造成数据库压力过大。

二、缓存穿透

1. 定义

客户端请求的数据在缓存和数据库中都不存在,缓存永远不生效,请求都打到数据库。

2. 解决方案

  • 缓存空对象:请求后发现数据不存在,将null值存入Redis。优点是实现简单、维护方便;缺点是有额外内存消耗,可能造成短期不一致。
  • 布隆过滤:在客户端与Redis间加布隆过滤器过滤请求。原理是数据库数据通过hash算法计算hash值存于布隆过滤器,判断数据是否存在时看hash值是0还是1。判断不存在时一定不存在,判断存在时不一定存在,有穿透风险。优点是内存占用少、无多余key;缺点是实现复杂、存在误判可能。
  • 其他方案:增强id复杂度、做好数据基础格式校验、加强用户权限校验、做好热点参数限流。

3. 代码示例

@Override
public Result queryById(Long id) {String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(shopJson)) {Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}if ("".equals(shopJson)) {return Result.fail("店铺不存在!");}Shop shop = getById(id);if (shop == null) {stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return Result.fail("店铺不存在!");}stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shop);
}

三、缓存击穿

1. 定义

热点Key突然失效,大量请求瞬间冲击数据库,也叫热点Key问题。

2. 解决方案

  • 互斥锁:只有持有锁的线程能访问数据库,会出现相互等待情况。
  • 逻辑过期:不设置TTL,用字段(如expire)表示过期时间,手动删除实现过期。优点是异步构建缓存,缺点是构建缓存前返回脏数据。

3. 代码示例

  • 互斥锁
private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.MINUTES);return BooleanUtil.isTrue(flag);
}private void unLock(String key) {stringRedisTemplate.delete(key);
}public Shop queryWithMutex(Long id) {String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(shopJson)) {return JSONUtil.toBean(shopJson, Shop.class);}if (shopJson != null) {return null;}String lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);if (!isLock) {Thread.sleep(50);return queryWithMutex(id);}shop = getById(id);if (shop == null) {stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);return null;}stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);} catch (InterruptedException ex) {throw new RuntimeException(ex);} finally {unLock(lockKey);}return shop;
}
  • 逻辑过期
@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}public void saveShopRedis(Long id, Long expireSeconds) {Shop shop = getById(id);RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public Shop queryWithLogicalExpire(Long id) {String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isBlank(shopJson)) {return null;}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;}String lockKey = LOCK_SHOP_KEY + id;boolean islock = tryLock(lockKey);if (islock) {CACHE_REBUILD_EXECUTOR.submit( () -> {try {saveShopRedis(id,20L);} catch (Exception ex) {throw new RuntimeException(ex);} finally {unLock(lockKey);}});}return shop;
}

四、缓存雪崩

1. 定义

同一时段大量缓存key同时失效或Redis服务宕机,大量请求到达数据库,带来巨大压力。

2. 解决方案

  • 给不同的Key的TTL添加随机值。
  • 利用Redis集群提高服务的可用性。
  • 给缓存业务添加降级限流策略。
  • 给业务添加多级缓存。

五、封装Redis工具类

@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;String json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(json)) {return JSONUtil.toBean(json, type);}if (json != null) {return null;}R r = dbFallback.apply(id);if (r == null) {stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}this.set(key, r, time, unit);return r;}public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;String json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isBlank(json)) {return null;}RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();if(expireTime.isAfter(LocalDateTime.now())) {return r;}String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);if (isLock){CACHE_REBUILD_EXECUTOR.submit(() -> {try {R newR = dbFallback.apply(id);this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {unlock(lockKey);}});}return r;}public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;String shopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(shopJson)) {return JSONUtil.toBean(shopJson, type);}if (shopJson != null) {return null;}String lockKey = LOCK_SHOP_KEY + id;R r = null;try {boolean isLock = tryLock(lockKey);if (!isLock) {Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}r = dbFallback.apply(id);if (r == null) {stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}this.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {unlock(lockKey);}return r;}private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {stringRedisTemplate.delete(key);}
}
http://www.xdnf.cn/news/4424.html

相关文章:

  • ai说什么是注解,并以angular ts为例
  • Go——项目实战
  • 【强化学习】强化学习算法 - 多臂老虎机问题
  • 精益数据分析(47/126):深挖UGC商业模式的关键要点与指标
  • 多模态大语言模型arxiv论文略读(六十二)
  • uniapp自定义底部导航栏h5有效果小程序无效的解决方案
  • 鞅与停时 - 一种特别的概率论问题
  • 讲解什么是快充诱骗协议芯片及它的工作原理和应用场景
  • 构建生命大模型,开拓教育新境界——启智书院举办十二周年庆典暨教育新生态跨界共拓峰会
  • 【存储管理—动态不等长存储资源分配算法】
  • 可执行文件格式(ELF格式)以及进程地址空间第二讲【Linux操作系统】
  • 【django.db.utils.OperationalError: unable to open database file】
  • Redis-黑马点评
  • 固件测试:mac串口工具推荐
  • 第1章 算法设计基础
  • draw.io流程图使用笔记
  • 机器人跑拉松是商业噱头还是技术进步的必然体现
  • 【愚公系列】《Manus极简入门》024-表演艺术教练:“舞台魔法师”
  • Matlab实现绘制任意自由曲线
  • 微调大模型的工具
  • 大语言模型中的“温度”参数到底是什么?如何正确设置?
  • 低空科技护航珞樱春色,技术引领助推广阔应用
  • 2025.05.07-华为机考第二题200分
  • uni-app 引入vconsole web端正常,安卓端报错 Cannot read property ‘sendBeacon‘ of undefined
  • 【论文阅读】Adversarial Training Towards Robust Multimedia Recommender System
  • 【神经网络与深度学习】VAE 和 GAN
  • Linux网络新手注意事项与配置指南
  • Dify平台下基于搜索引擎SearXNG 和文本转换工具Marp的PPT助手搭建
  • 电商双11美妆数据分析实验总结
  • sudo apt-get update 相关问题