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

Redis击穿,穿透和雪崩详解以及解决方案

在 Java 开发中,Redis 作为常用的缓存中间件,可能会面临击穿、穿透、雪崩这三类经典问题。以下是对这三个问题的详细解析及对应的 Java 解决方案:

一、Redis 缓存击穿(Cache Breakdown)

问题描述
  • 定义:大量请求同时访问一个过期的热点 key(如秒杀活动中的商品库存),导致请求直接穿透到数据库,引发瞬时高并发压力。
  • 核心原因
    • 热点 key 过期时,缓存失效。
    • 大量并发请求同时绕过缓存,直达数据库。
Java 解决方案
1. 互斥锁(Mutex Lock)
  • 思路:在缓存失效时,通过锁机制确保只有一个线程重建缓存,其他线程等待锁释放后从缓存获取数据。
  • 实现步骤
    1. 从 Redis 查询数据,若 key 过期或不存在,尝试获取分布式锁(如 Redisson、ZooKeeper 锁)。
    2. 获得锁的线程查询数据库,更新缓存,并释放锁。
    3. 其他线程在锁等待期间,休眠或重试查询缓存。
  • Java 代码示例(基于 Redisson)
    public String getProductInfo(String productId) {String cacheKey = "product:" + productId;String result = redisTemplate.opsForValue().get(cacheKey);if (result == null) { // 缓存失效RLock lock = redissonClient.getLock("mutex_lock:" + productId);try {lock.lock(); // 加锁// 二次验证(避免缓存重建期间其他线程重复查询)result = redisTemplate.opsForValue().get(cacheKey);if (result == null) {// 查询数据库String dbResult = queryFromDatabase(productId);if (dbResult != null) {redisTemplate.opsForValue().set(cacheKey, dbResult, 30, TimeUnit.SECONDS); // 重建缓存}}} finally {lock.unlock(); // 释放锁}}return result;
    }
    
2. 热点 key 永不过期
  • 思路:为热点 key 设置逻辑过期时间(如在 value 中存储过期时间戳),通过异步线程更新缓存,避免主动过期导致的击穿。
  • 实现步骤
    1. 缓存数据时,在 value 中添加 expireTime 字段。
    2. 每次访问时,检查 expireTime,若过期则启动异步线程更新缓存,当前请求仍返回旧数据。
  • Java 代码示例
    public class CachedData {private String value;private long expireTime;// getter and setter
    }public String getHotProductInfo(String productId) {String cacheKey = "hot_product:" + productId;CachedData cachedData = redisTemplate.opsForValue().get(cacheKey);if (cachedData == null || System.currentTimeMillis() > cachedData.getExpireTime()) {// 启动异步线程更新缓存(避免阻塞当前请求)CompletableFuture.runAsync(() -> {RLock lock = redissonClient.getLock("hot_product_lock:" + productId);try {lock.lock();// 二次验证cachedData = redisTemplate.opsForValue().get(cacheKey);if (cachedData == null || System.currentTimeMillis() > cachedData.getExpireTime()) {String dbResult = queryFromDatabase(productId);cachedData = new CachedData();cachedData.setValue(dbResult);cachedData.setExpireTime(System.currentTimeMillis() + 30 * 1000); // 逻辑过期时间redisTemplate.opsForValue().set(cacheKey, cachedData, 60, TimeUnit.SECONDS); // 物理过期时间设为逻辑过期时间的 2 倍}} finally {lock.unlock();}});// 返回旧数据或默认值(若首次查询)return cachedData != null ? cachedData.getValue() : defaultResponse();}return cachedData.getValue();
    }
    

二、Redis 缓存穿透(Cache Penetration)

问题描述
  • 定义:大量请求访问不存在的 key(如恶意攻击、非法参数),导致请求直接穿透缓存,每次都查询数据库,造成数据库压力激增。
  • 核心原因
    • 缓存层不存储无效 key,导致所有无效请求直达数据库。
    • 攻击方利用不存在的 key 进行批量请求。
Java 解决方案
1. 布隆过滤器(Bloom Filter)
  • 思路:在请求进入数据库前,使用布隆过滤器过滤掉不存在的 key,避免无效请求到达数据库。
  • 实现步骤
    1. 提前将数据库中存在的 key 加载到布隆过滤器中。
    2. 每次请求先通过布隆过滤器判断 key 是否存在,若不存在则直接返回无效响应。
  • Java 代码示例(基于 Google Guava)
    // 初始化布隆过滤器(建议使用 Redis 存储布隆过滤器数据,避免内存溢出)
    private static BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), 1000000, // 预计元素数量0.01 // 误判率
    );// 服务启动时加载现有 key 到布隆过滤器(示例)
    @PostConstruct
    public void loadExistingKeys() {List<String> productIds = productDao.getAllProductIds(); // 从数据库获取所有存在的 productIdbloomFilter.putAll(productIds);
    }public String getProductInfo(String productId) {if (!bloomFilter.mightContain(productId)) { // key 不存在return "无效的 productId";}// 正常查询缓存和数据库String cacheKey = "product:" + productId;String result = redisTemplate.opsForValue().get(cacheKey);if (result == null) {String dbResult = queryFromDatabase(productId);if (dbResult != null) {redisTemplate.opsForValue().set(cacheKey, dbResult, 30, TimeUnit.SECONDS);} else {// 缓存空值(防止重复查询)redisTemplate.opsForValue().set(cacheKey, "", 5, TimeUnit.MINUTES);}return dbResult;}return result;
    }
    
2. 缓存空值
  • 思路:当数据库查询结果为 null 时,将空值存入缓存(设置较短过期时间),避免后续相同请求穿透到数据库。
  • Java 代码示例
    public String getProductInfo(String productId) {String cacheKey = "product:" + productId;String result = redisTemplate.opsForValue().get(cacheKey);if (result == null) { // 缓存未命中String dbResult = queryFromDatabase(productId);redisTemplate.opsForValue().set(cacheKey, dbResult != null ? dbResult : "", // 空值存为 ""dbResult != null ? 30 : 5, // 存在数据则设正常过期时间,空值设短过期时间(如 5 分钟)TimeUnit.SECONDS);return dbResult;}return result.isEmpty() ? null : result; // 空值返回 null
    }
    

三、Redis 缓存雪崩(Cache Avalanche)

问题描述
  • 定义大量缓存 key 同时过期或 Redis 服务宕机,导致大量请求直接涌入数据库,造成数据库负载过高甚至崩溃。
  • 核心原因
    • 缓存层大面积失效(如同一批次 key 的过期时间集中设置)。
    • Redis 实例故障(如主从切换、集群节点宕机)。
Java 解决方案
1. 过期时间随机化
  • 思路:为缓存 key 设置随机过期时间(在固定时间基础上增加随机偏移量),避免大量 key 同时过期。
  • Java 代码示例
    public void setProductCache(String productId, String data) {int baseExpireTime = 30 * 60; // 30 分钟int randomOffset = ThreadLocalRandom.current().nextInt(10 * 60); // 随机偏移 0~10 分钟int expireTime = baseExpireTime + randomOffset;redisTemplate.opsForValue().set("product:" + productId, data, expireTime, TimeUnit.SECONDS);
    }
    
2. 限流与降级
  • 思路
    • 限流:通过令牌桶、信号量等机制限制单位时间内进入数据库的请求量(如使用 Hystrix、Resilience4j 或 Spring Cloud Sentinel)。
    • 降级:当数据库压力过大时,直接返回默认值或提示信息,保护数据库。
  • Java 代码示例(基于 Resilience4j)
    // 引入 Resilience4j 依赖
    // 添加限流注解
    @CircuitBreaker(name = "databaseCircuitBreaker", fallbackMethod = "fallbackGetProductInfo")
    public String getProductInfo(String productId) {String cacheKey = "product:" + productId;String result = redisTemplate.opsForValue().get(cacheKey);if (result == null) {String dbResult = queryFromDatabase(productId); // 可能触发限流if (dbResult != null) {redisTemplate.opsForValue().set(cacheKey, dbResult, 30, TimeUnit.SECONDS);}return dbResult;}return result;
    }// 降级方法
    public String fallbackGetProductInfo(String productId, Throwable throwable) {log.error("数据库查询失败,productId: {}, error: {}", productId, throwable.getMessage());return "服务繁忙,请稍后重试"; // 返回默认值或提示
    }
    
3. Redis 高可用架构
  • 思路:搭建 Redis 集群(如 Sentinel 或 Cluster 模式),避免单点故障导致缓存层整体不可用。
  • 配置示例(Spring Boot + Redis Cluster)
    spring.redis.cluster.nodes=redis://node1:7000,redis://node2:7001,redis://node3:7002
    spring.redis.cluster.max-redirects=3
    

四、总结对比

问题类型核心原因典型解决方案Java 关键技术 / 工具
击穿单个热点 key 过期互斥锁、热点 key 永不过期Redisson、异步线程
穿透大量无效 key 请求布隆过滤器、缓存空值Guava BloomFilter、Redis 空值缓存
雪崩大量 key 同时过期或 Redis 宕机过期时间随机化、限流降级、高可用架构Resilience4j、Redis Cluster

五、最佳实践建议

  1. 预防为主
    • 对热点数据提前预热缓存,避免突发流量击穿。
    • 接口层做参数校验,拦截非法 key(如空值、格式错误)。
  2. 监控与报警
    • 监控 Redis 内存使用率、缓存命中率、过期 key 数量。
    • 监控数据库 QPS、TPS,设置阈值触发报警。
  3. 综合方案
    • 针对高并发场景,组合使用互斥锁 + 布隆过滤器 + 限流降级,形成多层防护。

相关文章:

  • Polar编译码(SCL译码)和LDPC编译码(BP译码)的matlab性能仿真,并对比香浓限
  • BEVDepth- Acquisition of Reliable Depth for Multi-view 3D Object Detection
  • 数据库管理与高可用-MySQL数据库操作
  • C# Datatable筛选过滤各方式详解
  • 智变与重构:AI 赋能基础教育教学的范式转型研究报告
  • jmeter对数据库进行单独压测
  • 黑马程序员C++核心编程笔记--3 函数高级
  • 【前端】【css预处理器】Sass与Less全面对比与构建对应知识体系
  • Visual Studio 的下载安装
  • 22.代理模式:思考与解读
  • Spring AI 代理模式(Agent Agentic Patterns)
  • element ui 表格 勾选复选框后点击分页不保存之前的数据问题
  • React-native的新架构
  • MySQL 自增 ID 达到上限,如何巧妙化解危机
  • 力扣100题---字母异位词分组
  • Denoising Autoencoders 视频截图 DAEs简单实现 kaggle 去噪编码器
  • 计算机网络 | 1.1 计算机网络概述思维导图
  • 能按需拆分 PDF 为多个文档的工具
  • 集成电路制造设备防震基座选型指南:为稳定护航-江苏泊苏系统集成有限公司
  • 27、请求处理-【源码分析】-怎么改变默认的_method
  • 做网站实例/长春网站建设团队
  • wordpress网站的配置文件/一个新手怎么做电商
  • wordpress jenn 主题/sem优化服务公司
  • cms系统网站/站长推广网
  • 施工企业造价管理/seo培训多少钱
  • 全国网站建设排名/爱站工具查询