缓存相关问题:雪崩、穿透、预热、更新、降级的深度解析

✨✨祝屏幕前的小伙伴们每天都有好运相伴左右✨✨
🎈🎈作者主页: 喔的嘛呀🎈🎈

目录

引言

[1. 缓存雪崩](#1. 缓存雪崩)

[1.1 问题描述](#1.1 问题描述)

[1.2 解决方案](#1.2 解决方案)

[1.2.1 加锁防止并发重建缓存](#1.2.1 加锁防止并发重建缓存)

[2. 缓存穿透](#2. 缓存穿透)

[2.1 问题描述](#2.1 问题描述)

[2.2 解决方案](#2.2 解决方案)

[2.2.1 布隆过滤器防止无效请求](#2.2.1 布隆过滤器防止无效请求)

[3. 缓存预热](#3. 缓存预热)

[3.1 问题描述](#3.1 问题描述)

[3.2 分析与解决方案](#3.2 分析与解决方案)

[3.2.1 定时任务预热缓存](#3.2.1 定时任务预热缓存)

[4. 缓存更新](#4. 缓存更新)

[4.1 问题描述](#4.1 问题描述)

[4.2 分析与解决方案](#4.2 分析与解决方案)

[4.2.1 主动更新缓存](#4.2.1 主动更新缓存)

[5. 缓存降级](#5. 缓存降级)

[5.1 问题描述](#5.1 问题描述)

[5.2 分析与解决方案](#5.2 分析与解决方案)

[5.2.1 降级机制提供默认值](#5.2.1 降级机制提供默认值)

总结


引言

在系统开发中,缓存是提升性能和降低数据库负载的重要手段。然而,缓存并非没有问题,常见的问题包括缓存雪崩、缓存穿透、缓存预热、缓存更新和缓存降级等。本文将详细分析这些缓存相关的问题,并提供解决方案。

1. 缓存雪崩

1.1 问题描述

缓存雪崩是指在缓存中的大量数据同时过期或失效,导致大量请求直接落到数据库,压力剧增,可能导致系统崩溃。我们可以简单的理解为:由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

1.2 解决方案

1.2.1 加锁防止并发重建缓存

public class CacheService {

    private final Object lock = new Object();

    public Object getData(String key) {
        Object data = getFromCache(key);
        if (data == null) {
            synchronized (lock) {
                data = getFromCache(key);
                if (data == null) {
                    data = getFromDatabase(key);
                    putIntoCache(key, data);
                }
            }
        }
        return data;
    }
    
    // 其他业务逻辑...
}

2. 缓存穿透

2.1 问题描述

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

2.2 解决方案

2.2.1 布隆过滤器防止无效请求

public class CacheService {

    private final BloomFilter<String> bloomFilter = new BloomFilter<>();

    public Object getData(String key) {
        if (!bloomFilter.mightContain(key)) {
            return null;
        }

        Object data = getFromCache(key);
        if (data == null) {
            data = getFromDatabase(key);
            putIntoCache(key, data);
        }
        return data;
    }
    
    // 其他业务逻辑...
}

3. 缓存预热

3.1 问题描述

缓存预热是指在系统上线或重启后,将部分或全部数据预先加载到缓存中,防止大量请求直接访问数据库。

3.2 分析与解决方案

3.2.1 定时任务预热缓存

通过定时任务,在系统启动或每天凌晨1点等时机,将需要预热的数据加载到缓存中:

@Component
public class CacheWarmUpTask {

    @Autowired
    private CacheService cacheService;

    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
    public void warmUpCache() {
        List<String> keysToWarmUp = getKeysToWarmUp();
        for (String key : keysToWarmUp) {
            cacheService.getData(key);
        }
    }
    
    private List<String> getKeysToWarmUp() {
        // 根据业务逻辑获取需要预热的缓存键列表
        // ...
    }
}

4. 缓存更新

4.1 问题描述

缓存更新是指数据库中的数据更新后,及时将缓存中的数据进行同步。

4.2 分析与解决方案

4.2.1 主动更新缓存

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种: (1)定时去清理过期的缓存; (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

public class CacheService {

    public void updateCache(String key, Object newData) {
        // 更新缓存
        putIntoCache(key, newData);
    }
    
    // 其他业务逻辑...
}

5. 缓存降级

5.1 问题描述

缓存降级是指在系统遇到异常或缓存失效的情况下,通过某种方式提供默认值或兜底数据,保证系统正常运行。

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的而且有些服务是无法降级的(如加入购物车、结算)。 以参考日志级别设置预案: (1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; (2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; (3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; (4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

5.2 分析与解决方案

5.2.1 降级机制提供默认值

在缓存失效或异常时,提供默认值或兜底数据,确保系统正常运行:

public class CacheService {

    public Object getData(String key) {
        Object data = getFromCache(key);
        if (data == null) {
            data = getFromDatabase(key);
            if (data != null) {
                putIntoCache(key, data);
            } else {
                data = getDefaultData();
            }
        }
        return data;
    }

    private Object getDefaultData() {
        // 提供默认值或兜底数据
        // ...
    }
    
    // 其他业务逻辑...
}

总结

通过深入分析缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题,并提供相应的解决方案,可以有效提高系统的稳定性和性能。在实际应用中,应根据业务场景选择合适的方案,综合考虑多方面因素,以保障系统的高可用性和稳定性。长文分析力求全面,希望能为读者提供深度的理解和实践指导。

相关推荐
Themberfue8 分钟前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
深山夕照深秋雨mo17 分钟前
在Java中操作Redis
java·开发语言·redis
阳光阿盖尔18 分钟前
redis——哨兵机制
数据库·redis·缓存·主从复制·哨兵
努力的布布23 分钟前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
xujinwei_gingko23 分钟前
Spring MVC 常用注解
java·spring·mvc
PacosonSWJTU28 分钟前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
PacosonSWJTU29 分钟前
spring揭秘26-springmvc06-springmvc注解驱动的web应用
java·spring·springmvc
记得开心一点嘛36 分钟前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala
原野心存1 小时前
java基础进阶——继承、多态、异常捕获(2)
java·java基础知识·java代码审计
进阶的架构师1 小时前
互联网Java工程师面试题及答案整理(2024年最新版)
java·开发语言