【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 降级策略

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

相关推荐
NCIN EXPE4 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台4 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
lUie INGA4 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
极客on之路4 小时前
mysql explain type 各个字段解释
数据库·mysql
代码雕刻家4 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE4 小时前
开启mysql的binlog日志
数据库·mysql
hERS EOUS4 小时前
nginx 代理 redis
运维·redis·nginx
yejqvow124 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO4 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
geBR OTTE5 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端