Redis 缓存穿透雪崩和击穿

在使用 Redis 缓存时,常见的问题包括缓存穿透、缓存雪崩和缓存击穿,以下分别从问题表现、可能原因及解决方法进行阐述:

1. 缓存穿透

  • 问题表现:客户端请求的数据在缓存和数据库中都不存在,每次请求都会穿透缓存直接查询数据库,导致数据库压力增大,甚至可能引发数据库宕机。
  • 可能原因
    • 恶意攻击,如黑客故意请求不存在的数据,频繁访问数据库。
    • 业务数据存在异常,某些数据在正常情况下不会存在于数据库中,但被误请求。
  • 解决方法
    • 布隆过滤器(Bloom Filter):在缓存之前使用布隆过滤器,布隆过滤器可以快速判断某个数据是否存在。如果布隆过滤器判断数据不存在,就直接返回,不再查询数据库。布隆过滤器存在一定的误判率,但可以通过调整参数来降低误判率。例如在 Java 中,可以使用 Google Guava 库中的 BloomFilter
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

// 创建布隆过滤器,预计元素数量为10000,误判率为0.01
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 10000, 0.01);
// 添加元素
bloomFilter.put(1);
// 判断元素是否存在
boolean mightContain = bloomFilter.mightContain(1);
- **缓存空值**:当查询数据库发现数据不存在时,也将空值缓存起来,并设置一个较短的过期时间,这样下次相同请求就可以直接从缓存获取空值,而不会穿透到数据库。
// 假设jedis是Jedis实例
String key = "nonexistent_key";
String valueFromDb = getValueFromDatabase(key);
if (valueFromDb == null) {
    // 缓存空值,过期时间设为1分钟
    jedis.setex(key, 60, "");
    return "";
} else {
    // 缓存正常数据
    jedis.setex(key, 3600, valueFromDb);
    return valueFromDb;
}

2. 缓存雪崩

  • 问题表现:大量的缓存数据在同一时间过期,导致这些过期数据的请求同时穿透到数据库,使数据库瞬间承受巨大压力,甚至可能导致数据库崩溃。
  • 可能原因
    • 缓存数据设置了相同的过期时间,比如在系统初始化时,将大量数据的缓存过期时间都设置为一天,一天后这些缓存同时过期。
    • 缓存服务器重启或故障,导致所有缓存数据丢失,重启后大量请求直接访问数据库。
  • 解决方法
    • 随机过期时间:在设置缓存过期时间时,采用一个随机的时间范围,避免大量缓存同时过期。例如原本过期时间设置为 1 小时,可以改为在 50 分钟到 70 分钟之间随机设置过期时间。
import java.util.Random;
// 假设jedis是Jedis实例
String key = "example_key";
String value = getValueFromDatabase(key);
// 随机过期时间在50到70分钟之间
Random random = new Random();
int expireTime = 50 + random.nextInt(21);
jedis.setex(key, expireTime * 60, value);
- **二级缓存**:使用两层缓存,第一层缓存失效后,先从第二层缓存获取数据。第二层缓存可以设置较长的过期时间或者不设置过期时间。例如,可以使用 Caffeine 作为本地缓存作为二级缓存。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

// 创建Caffeine缓存
Cache<String, String> caffeineCache = Caffeine.newBuilder()
      .build();

// 假设jedis是Jedis实例
String key = "example_key";
String valueFromRedis = jedis.get(key);
if (valueFromRedis == null) {
    String valueFromCaffeine = caffeineCache.getIfPresent(key);
    if (valueFromCaffeine != null) {
        return valueFromCaffeine;
    } else {
        String valueFromDb = getValueFromDatabase(key);
        if (valueFromDb != null) {
            jedis.setex(key, 3600, valueFromDb);
            caffeineCache.put(key, valueFromDb);
            return valueFromDb;
        }
    }
}
return valueFromRedis;
- **缓存预热**:在系统上线前,提前将部分热点数据加载到缓存中,并设置不同的过期时间,避免上线后大量数据同时过期。

3. 缓存击穿

  • 问题表现:一个热点数据在缓存过期的瞬间,大量并发请求同时访问该数据,这些请求会绕过缓存直接访问数据库,导致数据库压力瞬间增大。
  • 可能原因
    • 热点数据的过期时间设置不合理,或者由于某些原因,热点数据的缓存突然失效。
    • 高并发场景下,对热点数据的访问频率极高,缓存过期时大量请求同时到达。
  • 解决方法
    • 互斥锁:在缓存过期时,使用互斥锁(如 Redis 的 SETNX 命令)保证只有一个请求能去查询数据库并更新缓存,其他请求等待。获取到锁的请求查询数据库并更新缓存后,释放锁,其他请求就可以从缓存中获取数据。
import redis.clients.jedis.Jedis;

// 假设jedis是Jedis实例
String key = "hot_key";
String mutexKey = "mutex:" + key;
String value = jedis.get(key);
if (value == null) {
    // 使用SETNX获取互斥锁
    if (jedis.setnx(mutexKey, "1") == 1) {
        try {
            value = getValueFromDatabase(key);
            if (value != null) {
                jedis.setex(key, 3600, value);
            }
        } finally {
            // 释放互斥锁
            jedis.del(mutexKey);
        }
    } else {
        // 未获取到锁,等待一段时间后重试
        Thread.sleep(100);
        return getValue(key);
    }
}
return value;
- **永不过期**:对于热点数据不设置过期时间,通过后台线程定时更新缓存数据,或者在数据发生变化时主动更新缓存。这样可以避免因缓存过期导致的缓存击穿问题。
// 后台线程定时更新缓存示例
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import redis.clients.jedis.Jedis;

public class CacheUpdater {
    private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    private static final String key = "hot_key";
    private static final Jedis jedis = new Jedis("localhost");

    static {
        executorService.scheduleAtFixedRate(() -> {
            String value = getValueFromDatabase(key);
            if (value != null) {
                jedis.set(key, value);
            }
        }, 0, 60, TimeUnit.MINUTES);
    }

    public static String getValue(String key) {
        return jedis.get(key);
    }

    private static String getValueFromDatabase(String key) {
        // 实际从数据库查询逻辑
        return "data from db";
    }
}
#牛客创作赏金赛##找工作如何保持松弛感?#
全部评论

相关推荐

05-19 16:19
门头沟学院 Java
凉,原因是自己答得不是非常好以及他们要求学历是杭电及以上学历待遇可以1.&nbsp;反射知道吗,你说一下2.&nbsp;hashmap的底层结构3.&nbsp;hashtable知道吗4.&nbsp;hash方法是如何优化的5.&nbsp;多少会转为红黑树6.&nbsp;为什么要用高16位和低16位异或7.&nbsp;那他是怎么插入槽的,做了什么优化,过程说一下8.&nbsp;是怎么实现一个去重的9.&nbsp;concurrentHashMap了解吗10.&nbsp;那分段锁里面是怎么实现的11.&nbsp;cas+synchronized是如何操作,为什么要这样呢,先详细说一下cas12.&nbsp;是怎么上锁的呢(这里我说是对对象,他说是吗,这样不是整个锁住了吗)13.&nbsp;JVM的内存结构说一下14.&nbsp;线程池的那个参数说一下15.&nbsp;拒绝策略有哪些16.&nbsp;redis的aof重写说一下17.&nbsp;mysql的行锁说一下18.&nbsp;为什么里面用b+树19.&nbsp;索引失效情况有哪些(没答完全)20.&nbsp;索引覆盖是什么21.&nbsp;索引下推讲讲22.&nbsp;redission的看门狗机制说一下23.&nbsp;redis里面的数据你是怎么与数据库同步(定时)24.&nbsp;volatile25.&nbsp;如果我要求强一致性,然后每隔10秒进行一次同步,但是其中这个业务进行时间大于10秒了怎么办(消息队列,多线程去处理)26.&nbsp;lua脚本执行到一半中断,比如redis宕机,这个时候会回滚吗27.&nbsp;acid说一下28.&nbsp;异常处理机制29.&nbsp;synchronized的底层原理30.&nbsp;reentrantlock的原理,他底层是怎么实现的
丰川打工祥:不会是智晟未来吧。这是家培训公司。3300+500绩效+免费住宿。工作是给别人面试,没什么编码机会。可以仔细想想要不要去。
查看30道真题和解析
点赞 评论 收藏
分享
05-24 15:21
已编辑
衡水学院 安卓
二面基本上场景+八股+聊天&nbsp;个人觉得有点难一.&nbsp;个人方面1.从大学到现在做过最有挑战的一件事情2.在找第一份实习你做的最正确的一件事3.问毕设(大三没有qwq)4.参加了什么实验室吗(打竞赛qwq)5.什么时候接触的安卓6.期末demo花了多久,怎么写的,有没有借鉴开源项目二.&nbsp;java安卓八股1.安卓进程间通信,为什么新做了binder(不会)瞎说的2.有没有看过binder源码,有没有写过跨进程之间通信的demo3.handler和binder什么关系啊,message和binder的关系(我说message底层是binder,我瞎说的我真不知道qwq)4.安卓里面最难的知识点接触的是什么5.什么是协程&nbsp;和线程的关系&nbsp;为什么是轻量级的,节省的内存在哪里6.jvm虚拟机了解到多么,内存管理这方面了解吗(不会&nbsp;只知道垃圾回收)7.怎么判断一个对象有没有内存泄漏,要不要回收8.日常开发如何排查内存泄漏9.除了leakcanary还可以怎么样10.leakcanary的原理11.如何避免内存泄漏呢,在开发过程中12.安卓发起网络请求如何实现,代码具体怎么写三.&nbsp;场景1.有abc三个请求a,b独立,c依靠ab请求的数据,如果这样你怎么实现,代码具体怎么实现2.如果我想实现一个线程安全的方法,比如说我写了个单例,这个单例的方法我要是保证它线程安全的,那实现方式都有哪些?3.假如说我这个单例的初始化已经写完了,再去写其他的一些方法,那还有什么方式来去实现4.锁怎么加,加在哪,在返回之前加,在返回之后加5.violent是解决什么问题的,除了单例模式,还有什么情况需要用6.为什么java会有线程不安全(因为java有并发),为什么并发会导致不安全,底层原因是什么(我举了一个两个线程操作同一个变量,会有逻辑错误,所以线程不安全),他说我不对,问我如果想了解这方面的知识原理需要学什么(os)四.&nbsp;网络八股+场景1.网络层的协议都有什么2.https为什么更安全3.什么是对称加密&nbsp;什么是非对称加密,加密解密流程是什么(不会)4.dns协议解决的问题是什么5.我们在安卓的网络请求的时候,优化&nbsp;DNS&nbsp;解析的时间,有什么思路吗(缓存)6.缓存的策略要怎么去维护(没理解)补充:数据结构要怎么去设计代码的这个类要具备哪些能力7.存下来的数据怎么管理呢(纯拷打)五.&nbsp;其他询问成绩&nbsp;手撕二维螺旋数组后续觉得凉凉了就没录音,就是一些实习多久云云整体时间就是5,17一面&nbsp;下午约二面&nbsp;5.19上午二面&nbsp;下午oc&nbsp;5.&nbsp;22邮件
点赞 评论 收藏
分享
05-16 16:39
已编辑
门头沟学院 Java
2025.5.14&nbsp;40min面试官介绍部门非常详细,lazada东南亚最大电商平台主要是结合项目问八股,也有项目中某些细节的具体实现,和数据库表的设计面试官很好,在问的过程中,一边在记录面评,面试中学到了很多。虽然也有些没答上来,或者没答到位,但是比阿里云的体验好多了。面试官先介绍实习招聘的流程,说Bravo102实习生招聘是统一面试的,最后拿到offer,会让同学自己选择想去的部门,双向选择。第一个没让自我介绍的公司1.&nbsp;Redis的过期删除策略2.&nbsp;具体的过期删除算法有哪些,绕了好久,最后发现他想问的是内存淘汰策越(LRU、LFU、随机删)3.&nbsp;Spring拦截器用到了吗,拦截器的底层原理4.&nbsp;拦截器和过滤器的区别5.&nbsp;Kafka怎么保证消息不丢失6.&nbsp;项目中Kafka具体怎么使用的7.&nbsp;消息异常,没有发出去该怎么解决8.&nbsp;重试具体是怎么做的,循环吗9.&nbsp;重试多次失败,怎么办,抛出异常吗10.&nbsp;消息一直没发出去是什么原因,分析一下11.&nbsp;SQL怎么优化的12.&nbsp;怎么判断是慢查询的13.&nbsp;怎么设计一个好的数据库14.&nbsp;说说项目的数据库表是怎么设计的,可以说字段、索引、外键等一些设计15.&nbsp;主键怎么设计的,普通递增,分布式中可以用雪花算法16.&nbsp;除了雪花算法和UUID,还有什么可以让主键不重复17.&nbsp;问具体的字段用什么类型设计的,比如用户名18.&nbsp;什么时候用到了JOIN19.&nbsp;left&nbsp;join、right&nbsp;join和outer&nbsp;join20.&nbsp;加密算法有哪些,什么区别21.&nbsp;项目中用到哪些Spring特性22.&nbsp;简单说说AOP是什么23.&nbsp;动态代理,有的基于接口,有的不基于接口,具体说说什么区别24.&nbsp;项目中哪些地方用到了AOP25.&nbsp;说一下设计模式,以及知道哪些常用的设计模式,项目中怎么用到设计模式的26.&nbsp;模版模式了解吗&nbsp;27.&nbsp;说说Spring中事务传播级别有哪些28.&nbsp;两个方法嵌套调用,A调用B,A发生异常时事务传播机制怎么设置,B发生异常时事务传播机制怎么设置29.&nbsp;Redis和数据库怎么保证数据一致性30.&nbsp;SpringCache了解吗31.&nbsp;说说线程池32.&nbsp;核心线程数根据什么设置33.&nbsp;说说Synchronized34.&nbsp;Synchronized&nbsp;和volatile&nbsp;的区别35.&nbsp;项目中或者哪些场景下用到volatile反问(虽然是东南亚平台,但是做技术的不需要国外出差,非常详细的介绍部门和业务大概有5分多钟)最后对问的问题也进行了总结,说可以钻研深入些,还有多看看源码。对于他问的问题,每个问题可以多说一点(比如说慢查询优化,可以从怎么选择存储引擎,项目数据量是多少,每个表怎么设计的,索引怎么设计,这些方面都可以说),不用等着他来问。关于分库分表,也要考虑表的规模。
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客企业服务