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

Redis缓存穿透、雪崩、击穿的解决方案?

Redis 缓存问题解决方案及Java实现

一、缓存穿透解决方案

(缓存穿透指查询不存在数据,绕过缓存直接访问数据库)

1. 布隆过滤器 + 空值缓存

注意点:
1.布隆过滤器是需要预热数据的,就是需要输入当前数据库已经存在的缓存,这里会有不少的内存消耗
2.布隆过滤器会出现漏掉的情况,只是通过算法做一个筛选兜底,避免大量数据访问。
3.对于业务方来说,不是所有的缓存都需要添加进入布隆过滤器的,博主认为只有该数据访问有被外攻击的风险,才需要,如果没有的情况下,业务方需要自己兜底防止缓存穿透。

// 使用Guava布隆过滤器(需引入Guava依赖)
// 初始化布隆过滤器(需预热数据)
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),1000000,  // 预期数据量:根据业务历史数据量估算0.001     // 误判率:每1000次查询允许1次误判
);// 预热数据(系统启动时加载)
database.getAllKeys().forEach(key -> {bloomFilter.put(key);  // 将数据库已有key存入过滤器redisTemplate.opsForValue().set(key, database.get(key)); // 初始化缓存
});public Object getData(String key) {// 1. 布隆过滤器校验(存在性预判)if (!bloomFilter.mightContain(key)) {// 确定不存在时直接返回(拦截非法请求)return null; }// 2. 查询Redis缓存Object value = redisTemplate.opsForValue().get(key);if (value != null) {return "NULL".equals(value) ? null : value; // 空值处理逻辑}// 3. 查询数据库(通过过滤器的请求才放行)Object dbValue = database.get(key);// 4. 双写机制更新if (dbValue == null) {redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);} else {redisTemplate.opsForValue().set(key, dbValue, 30, TimeUnit.MINUTES);bloomFilter.put(key); // 动态维护布隆过滤器}return dbValue;
}

二、缓存雪崩解决方案

(大量缓存同时过期导致数据库压力激增)

1. 随机过期时间方案

public Object getDataWithRandomExpire(String key) {// 基础过期时间 + 随机偏移量(防止集体失效)int baseExpire = 30; // 基础30分钟int randomOffset = new Random().nextInt(10); // 0-9分钟随机偏移int totalExpire = baseExpire + randomOffset;Object value = redisTemplate.opsForValue().get(key);if (value != null) {return value;}// 查询数据库...Object dbValue = database.get(key);// 设置随机过期时间redisTemplate.opsForValue().set(key, dbValue, totalExpire, TimeUnit.MINUTES);return dbValue;
}

特点:随机会有概率导致时间过期重合(再小概率的事件只要有概率,生产都有可能发生)

2. 永不过期+后台更新方案

// 后台定时更新线程
@Scheduled(fixedDelay = 30 * 60 * 1000) // 每30分钟执行
public void refreshCache() {List<String> hotKeys = getHotKeysFromMonitor(); // 从监控系统获取热点keyhotKeys.parallelStream().forEach(key -> {Object dbValue = database.get(key);redisTemplate.opsForValue().set(key, dbValue); // 不设置过期时间});
}// 数据访问逻辑
public Object getDataWithPersist(String key) {Object value = redisTemplate.opsForValue().get(key);if (value == null) {value = database.get(key);redisTemplate.opsForValue().set(key, value); // 永不过期写入}return value;
}

特点:使用场景必须是很少更新的数据,如果数据库频繁变更,该数据不过期没有特别大的意义。

3. 多级缓存方案

// 本地缓存(使用Caffeine)
Cache<String, Object> localCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10000).build();public Object getDataWithMultiCache(String key) {// 1. 检查本地缓存Object value = localCache.getIfPresent(key);if (value != null) return value;// 2. 检查Redis缓存value = redisTemplate.opsForValue().get(key);if (value != null) {localCache.put(key, value); // 回填本地缓存return value;}// 3. 数据库查询value = database.get(key);// 4. 双写缓存(设置不同过期时间)redisTemplate.opsForValue().set(key, value, 30 + new Random().nextInt(10), TimeUnit.MINUTES);localCache.put(key, value);return value;
}

特点:实现复杂,维护缓存困难。

4. 熔断降级方案

// 使用Resilience4j熔断器
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("cacheCB");public Object getDataWithCircuitBreaker(String key) {return CircuitBreaker.decorateSupplier(circuitBreaker, () -> {Object value = redisTemplate.opsForValue().get(key);if (value != null) return value;// 超过阈值时触发熔断,返回兜底数据if (circuitBreaker.tryAcquirePermission()) {Object dbValue = database.get(key);redisTemplate.opsForValue().set(key, dbValue, 30, TimeUnit.MINUTES);return dbValue;} else {return getFallbackData(); // 返回预设默认值}}).get();
}

特点:会导致业务有段时间不可用,兜底数据返回,用户体验差

5. 热点数据预加载方案

// 监控系统集成
public void monitorAndPreload() {// 实时统计热点key(示例使用滑动窗口)ConcurrentHashMap<String, AtomicInteger> counter = new ConcurrentHashMap<>();// 数据访问埋点AspectJ.around("execution(* getData(..))", (joinPoint, result) -> {String key = (String) joinPoint.getArgs()[0];counter.computeIfAbsent(key, k -> new AtomicInteger(0)).incrementAndGet();});// 定时分析热点数据ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);scheduler.scheduleAtFixedRate(() -> {counter.entrySet().stream().filter(entry -> entry.getValue().get() > 1000) // 阈值判断.forEach(entry -> {String hotKey = entry.getKey();Object value = database.get(hotKey);redisTemplate.opsForValue().set(hotKey, value, 60, TimeUnit.MINUTES);});counter.clear();}, 5, 5, TimeUnit.MINUTES); // 每5分钟执行
}

特点:需要时间缓存热点数据。

所有方案均可组合使用,建议通过监控以下指标进行方案调优:

  • 缓存命中率(Redis/Memcached)
  • 数据库QPS(Queries Per Second)
  • 系统吞吐量(TPS)
  • 熔断器状态(Open/Half-Open/Closed)

总结

解决缓存雪崩就是设计缓存的时候,让缓存不要在同一时间大批量过期,部分很少变化的数据进行预热加载。

三、缓存击穿解决方案

(热点key过期后大量并发请求直达数据库)

1. 互斥锁方案

public Object getDataWithLock(String key) {// 1. 缓存存在时直接返回(无锁)Object value = redisTemplate.opsForValue().get(key);if (value != null) return value;// 2. 尝试获取分布式锁(关键控制点)String lockKey = "LOCK:" + key;try {// 原子性操作:setIfAbsent + expireBoolean isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, "LOCKED",10, // 锁持有时间(秒)TimeUnit.SECONDS);if (Boolean.TRUE.equals(isLock)) {// 3. 仅一个线程执行数据库查询(关键保护)Object dbValue = database.get(key);// 4. 双写缓存(重建热点数据)redisTemplate.opsForValue().set(key, dbValue, 30 + new Random().nextInt(10), // 随机过期时间TimeUnit.MINUTES);return dbValue;} else {// 5. 其他线程等待后重试(避免堆积)Thread.sleep(50);return getDataWithLock(key); }} finally {// 6. 释放锁(必须保证)redisTemplate.delete(lockKey); }
}

总结:缓存击穿的方案就是在重建缓存之前,防止接口并发行为,或者让缓存永不过期

四、组合方案建议

  1. 分级缓存架构:本地缓存(Caffeine)+ Redis集群
  2. 热点发现:实时监控Key访问频率,自动续期热点数据
  3. 熔断降级:Hystrix或Sentinel实现数据库保护
  4. 异步更新:使用消息队列异步更新缓存

实际生产环境中建议根据业务场景组合使用多种方案,并配合监控系统实时观察缓存命中率、数据库QPS等关键指标。

http://www.xdnf.cn/news/6329.html

相关文章:

  • WinFrom 使用 LiveCharts 实现动态折线图
  • 常用正则记录
  • 抽奖系统-奖品-活动
  • 外贸礼品禁忌
  • 【SSL证书系列】SSL证书工作原理解读
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(21):复习
  • 【测试开发知识储备】之Jacoco(Java Code Coverage)
  • SVNAdmin管理使用教程
  • Problem E: List练习
  • 力扣刷题(第二十六天)
  • 运筹说 第136期 | 其他类型对策简介之合作对策
  • BGP联邦和发射试验
  • Linux wlan 单频段 dual wifi创建
  • git中忽略文件.gitignore文件的用法
  • 2025年AI开发者在开发者占比?
  • 进阶2_1:QT5多线程与定时器共生死
  • 深度剖析火狐飞鸟 MIP 泛目录程序:技术原理与实践应用
  • .NET程序启动就报错,如何截获初期化时的问题json
  • E. 23 Kingdom【Codeforces Round 1024 (Div. 2)】
  • 1669上什么课
  • day29-IO(其他流)
  • Java基础(多线程1)
  • 鸿蒙-5.1.0-release构建编译环境
  • 分割等和子集习题分析
  • HCIP(OSPF的拓展配置及选路规则)
  • 矩阵乘法的优化与复杂度分析
  • 一个日志量突增的问题分析处理经历
  • 普通IT的股票交易成长史--20250514复盘
  • 机器学习任务的常用评估指标
  • JVM内存模型