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

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

相关推荐
张铁铁是个小胖子1 分钟前
显示微服务间feign调用的日志
java·spring·微服务
Python大数据分析@2 分钟前
为什么用SQL而不是Excel+VBA?
数据库·sql·excel
Aniay_ivy12 分钟前
Java中的不可变集合:性能与安全并重的最佳实践
java·windows·安全
TomSmile_WorkSpace26 分钟前
RabbitMq项目实战--延迟队列实现超时订单处理
开发语言·后端·ruby
outofdilemma29 分钟前
Java复习41(PTA)
java
V+zmm101341 小时前
校园服务平台小程序ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·课程设计·1024程序员节
夏天vs不热1 小时前
EasyExcel级联下拉
后端
一条小小yu1 小时前
高频 SQL 50 题(基础版)连接部分
数据库·sql
Beekeeper&&P...1 小时前
sql中对象名称要加_的作用
数据库·sql
xserver21 小时前
Openstack9--安装etcd分布式键-值对存储系统
数据库·分布式·openstack·etcd