【Redis】缓存击穿与缓存雪崩:问题与解决方案

前言

用户数据通常存储在数据库中,而数据库的数据则保存在磁盘上。由于磁盘的读写很慢,为了避免用户直接访问数据库,我们可以使用 Redis 作为缓存层。而引入缓存层后,会出现三种缓存异常问题:缓存穿透缓存击穿缓存雪崩 ,这篇文章主要探讨缓存击穿缓存雪崩的问题。

想要了解缓存穿透及其解决方法,可以看我的这篇博客:【Redis】缓存穿透详解


1.缓存击穿

1.1 什么是缓存击穿

缓存击穿是指在缓存中没有命中数据(缓存不存在),并且该数据正好是一个热点数据(经常访问的、被频繁请求的数据)。由于缓存中没有该数据,系统会直接访问数据库,而在高并发的情况下,多个请求会同时穿透缓存,直接访问数据库,造成数据库压力骤增,可能导致数据库崩溃或系统性能下降。


1.2 缓存击穿的产生原因

缓存击穿通常发生在以下几种情况:

  • 热点数据的缓存失效:当某个热点数据的缓存过期时,大量请求涌入到数据库层,而此时数据库需要处理所有的请求,造成数据库的瞬时压力增大。
  • 缓存中没有数据:某些数据本身并没有被缓存,可能是由于缓存策略不当、数据没有被及时缓存,或者缓存过期。

1.3 如何解决缓存击穿

缓存击穿的核心问题是如何有效避免缓存失效后,多个请求直接击穿缓存访问数据库。

1.3.1 热点数据永不过期

对于特别重要的热点数据,可以考虑不设置缓存过期时间,让这些数据一直保存在缓存中。可以通过定时任务手动更新缓存中的数据来避免数据过期问题。


1.3.2 使用互斥锁(锁机制)

在缓存失效的情况下,采用分布式锁或互斥锁来确保只有一个线程/请求去加载数据库数据,并更新缓存。其他线程则等待,直到缓存被更新完毕。例如,可以通过 Redis 的 SETNX(set if not exists)命令来实现分布式锁。

java 复制代码
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
    // 获取分布式锁
    if (redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 10, TimeUnit.SECONDS)) {
        try {
            // Double-check
            value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                // 查询数据库
                value = database.get(key);
                // 将结果写入缓存
                redisTemplate.opsForValue().set(key, value, ttl, TimeUnit.SECONDS);
            }
        } finally {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    } else {
        // 等待锁释放后,再从缓存中读取数据
        Thread.sleep(100); // 自行调整等待时间
        value = redisTemplate.opsForValue().get(key);
    }
}

1.3.3 预防性缓存更新

在热点数据即将过期时,提前异步刷新缓存。通过检测热点数据的访问频率,当即将过期时触发自动更新操作,避免过期瞬间的击穿问题。


2.缓存雪崩

2.1 什么是缓存雪崩

缓存雪崩是指缓存中的大量数据在短时间内同时过期,导致大量请求同时访问数据库,造成数据库的瞬间负载激增,可能导致数据库崩溃。缓存雪崩的问题通常发生在缓存中的数据都在同一时刻过期,尤其是缓存的过期时间相同或过期时间比较集中时。


2.2 缓存雪崩的产生原因

缓存雪崩通常是以下几种原因导致的:

  • 缓存过期时间设计不当:多个缓存数据设置了相同的过期时间,导致它们在同一时刻过期,造成大量请求同时访问数据库。
  • 缓存服务器宕机或故障:缓存服务器出现故障,所有缓存数据无法提供服务,导致请求直接访问数据库。
  • 缓存容量不足:缓存空间不足,无法承载大量的数据,导致频繁失效和大量缓存穿透。

2.3 如何解决缓存雪崩

解决缓存雪崩的根本方法是避免大量缓存数据同时失效,或者通过其他手段避免请求直接访问数据库

2.3.1 设置不同的缓存过期时间

为避免缓存雪崩,最直接的解决办法是让缓存中的不同数据拥有不同的过期时间。可以为每个缓存项添加一定的随机值,避免缓存的失效时间集中在某一时刻。

java 复制代码
// 为每个缓存设置一个不同的过期时间,带有随机偏移量
long randomOffset = ThreadLocalRandom.current().nextLong(0, 60000);
redisTemplate.opsForValue().set(cacheKey, value, baseExpireTime + randomOffset, TimeUnit.MILLISECONDS);

2.3.2 使用缓存预热

在系统启动时,或者通过某种定时机制对缓存进行预热。即提前加载一些关键数据到缓存中,避免在数据首次请求时缓存为空,从而导致直接访问数据库。


2.3.3 降级策略

在缓存雪崩时,可以采取限流、降级等策略,减缓数据库的压力。如在缓存失效时,直接返回默认值或缓存过期的旧数据,避免数据库短时间内处理大量请求。

相关推荐
@insist12317 分钟前
信息系统管理工程师-运维人员管理与核心运维过程(上篇)
数据库·软考·软件水平考试·信息系统管理工程师·软考信管
muddjsv33 分钟前
SQLite 零基础 CRUD 实战详解:增删改查标准语法与高危避坑
数据库·sqlite
2501_9378609435 分钟前
Java 集合底层深度剖析:Map 与 Set、二叉搜索树、哈希表全解
java·数据结构·散列表
cyforkk1 小时前
并发控制与状态安全:Single-Flight 与幂等性的本质区别
java·安全·spring
Codelinghu1 小时前
LangSmith Evaluate实战评估Agent,不要在玩Demo了
后端
xingren1 小时前
人体模型部位分割:从 SCHP平面解析 映射到 3D 模型
后端
董员外1 小时前
RAG 系统进化论(五):Corrective RAG 与 Self-RAG,让系统发现并纠正错误
人工智能·后端·设计模式
暗黑小白1 小时前
路由策略与引擎可替换性
后端·python·ai agent
豆角焖肉1 小时前
MyBatis延迟加载、缓存机制与注解开发
java·spring·mybatis
所愿ღ1 小时前
SSM框架-Spring3
java·开发语言·笔记·spring