当前位置:

首页 > 系统应用 > 一文教你弄懂缓存穿透、击穿、雪崩、预热等异常

一文教你弄懂缓存穿透、击穿、雪崩、预热等异常

本文目录

    对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一。在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很容易就遇到缓存击穿、雪崩等严重异常情景,从而给系统带来难以预料的灾害。为了避免缓存使用不当带来的损失,我们有必要了解每种异常产生的原因和解决

    对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一。在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很容易就遇到缓存击穿、雪崩等严重异常情景,从而给系统带来难以预料的灾害。

    为了避免缓存使用不当带来的损失,我们有必要了解每种异常产生的原因和解决办法,从而做出更好的预防措施。

    缓存穿透

    而缓存穿透是指缓存和数据库中都没有的数据,这样每次请求都会去查库,不会查缓存,如果同一时间有大量请求进来的话,就会给数据库造成巨大的查询压力,甚至击垮db系统。

    比如说查询id为-1的商品,这样的id在商品表里肯定不存在,如果没做特殊处理的话,攻击者很容易可以让系统奔溃,那我们该如何避免这种情况发生呢?

    一般来说,缓存穿透常用的解决方案大概有两种:

    一、缓存空对象

    当缓存和数据都查不到对应key的数据时,可以将返回的空对象写到缓存中,这样下次请求该key时直接从缓存中查询返回空对象,就不用走db了。当然,为了避免存储过多空对象,通常会给空对象设置一个比较短的过期时间,就比如像这样给key设置30秒的过期时间:

    redisTemplate.opsForValue().set(key, null, 30, TimeUnit.SECONDS);

    这种方法会存在两个问题:

    • 如果有大量的key穿透,缓存空对象会占用宝贵的内存空间。
    • 空对象的key设置了过期时间,这段时间内可能数据库刚好有了该key的数据,从而导致数据不一致的情况。

    这种情况下,我们可以用更好的解决方案,也就是布隆过滤器

    二、Bloom Filter

    布隆过滤器(Bloom Filter)是1970年由一个叫布隆的小伙子提出的,是一种由一个很长的二进制向量和一系列随机映射函数构成的概率型数据结构,这种数据结构的空间效率非常高,可以用于检索集合中是否存在特定的元素。

    设计思想

    布隆过滤器由一个长度为m比特的位数组(bit array)与k个哈希函数(hash function)组成的数据结构。原理是当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就大约知道集合中有没有它了,也就是说,如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。

    至于说为什么都是1的情况只是可能存在检索元素,这是因为不同的元素计算的哈希值有可能一样,会出现哈希碰撞,导致一个不存在的元素有可能对应的比特位为1。

    举个例子:下图是一个布隆过滤器,共有18个比特位,3个哈希函数。当查询某个元素w时,通过三个哈希函数计算,发现有一个比特位的值为0,可以肯定认为该元素不在集合中。

    优缺点

    优点:

    • 节省空间:不需要存储数据本身,只需要存储数据对应hash比特位
    • 时间复杂度低:基于哈希算法来查找元素,插入和查找的时间复杂度都为O(k),k为哈希函数的个数

    缺点:

    • 准确率有误:布隆过滤器判断存在,可能出现元素不在集合中;判断准确率取决于哈希函数的个数
    • 不能删除元素:如果一个元素被删除,但是却不能从布隆过滤器中删除,这样进一步导致了不存在的元素也会显示1的情况。

    适用场景

    • 爬虫系统url去重
    • 垃圾邮件过滤
    • 黑名单

    缓存击穿

    缓存击穿从字面上看很容易让人跟穿透搞混,这也是很多面试官喜欢埋坑的地方,当然,只要我们对知识点了然于心的话,面试的时候也不会那么被糊弄

    简单来说,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就好像堤坝突然破了一个口,大量洪水汹涌而入。

    当发生缓存击穿的时候,数据库的查询压力会倍增,导致大量的请求阻塞。

    解决办法也不难,既然是热点key,那么说明该key会一直被访问,既然如此,我们就不对这个key设置失效时间了,如果数据需要更新的话,我们可以后台开启一个异步线程,发现过期的key直接重写缓存即可。

    当然,这种解决方案只适用于不要求数据严格一致性的情况,因为当后台线程在构建缓存的时候,其他的线程很有可能也在读取数据,这样就会访问到旧数据了。

    如果要严格保证数据一致的话,可以用互斥锁

    互斥锁

    互斥锁就是说,当key失效的时候,让一个线程读取数据并构建到缓存中,其他线程就先等待,直到缓存构建完后重新读取缓存即可。

    如果是单机系统,用JDK本身的同步工具Synchronized或ReentrantLock就可以实现,但一般来说,都达到防止缓存击穿的流量了谁还搞什么单机系统,肯定是分布式高大上点啊,这种情况我们就可以用分布式锁来做互斥效果。

    为了你们能更懂流程,作为暖男的我还是一如既往的给你们准备了伪代码啦:

    public String getData(String key){
    String data = redisTemplate.opsForValue().get(key);
    if (StringUtils.isNotEmpty(data)){
    return data;
    }
    String lockKey = this.getClass().getName() + ":" + key;
    RLock lock = redissonClient.getLock(lockKey);
    try {
    boolean boo = lock.tryLock(5, 5, TimeUnit.SECONDS);
    if (!boo) {
    // 休眠一会儿,然后再请求
    Thread.sleep(200L);
    data = getData(key);
    }
    // 读取数据库的数据
    data = getDataByDB(key);
    if (StringUtils.isNotEmpty(data)){
    // 把数据构建到缓存中
    setDataToRedis(key,data);
    }
    } catch (InterruptedException e) {
    // 异常处理,记录日志或者抛异常什么的
    }finally {
    if (lock != null && lock.isLocked()){
    lock.unlock();
    }
    }
    return data;
    }

    不过,采用互斥锁方案也并非十全十美。缓存失效时,同一时间仅一个线程能读取数据库并回写缓存,其他线程只能处于阻塞状态。若处于高并发场景,大量线程阻塞必然会降低系统吞吐量。这种情况下该如何应对呢?要知道,任何设计都不可能完美无缺,你既想保证数据一致,又想确保系统吞吐量,哪有如此两全其美的事呢?为了让系统更加完善,在必要的时候适当牺牲一些性能,也是可以考虑的措施。至于如何在两者之间进行权衡,需要根据实际的业务场景来决定,那种万能的技术方案根本就不存在。

    缓存雪崩

    缓存雪崩同样是key失效后大量请求涌向数据库的异常状况。不过,与缓存击穿有所不同,缓存击穿是因一个热点key失效所致,而缓存雪崩则是指缓存中大量数据同时过期,庞大的请求量直接砸向db层,致使db压力过大甚至宕机,这也正契合了“雪崩”这一说法。

    解决方案

    缓存雪崩的解决方案和击穿的思路一致,可以设置key不过期或者互斥锁的方式。

    除此之外,因为是预防大面积的key同时失效,可以给不同的key过期时间加上随机值,让缓存失效的时间点尽量均匀 ,这样可以保证数据不会在同一时间大面积失效。

    redisTemplate.opsForValue().set(Key, value, time + Math.random() * 1000, TimeUnit.SECONDS);

    同时还可以结合主备缓存策略来让互斥锁的方式更加的可靠,

    主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。

    备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存。

    一般来说,上面三种缓存异常场景问的比较多,了解这几种基本就够了,但有些面试官可能喜欢剑走偏锋,进一步延伸其他的异常情景做询问,以防万一,我们也加个菜,介绍下另外两种常见缓存异常。

    缓存预热

    缓存预热就是系统上线后,先将相关的数据构建到缓存中,这样就可以避免用户请求的时候直接查库。

    这部分预热的数据主要取决于访问量和数据量大小,如果数据的访问量不大的话,那么就没必要做预热,都没什么多少请求了,直接按正常的缓存读取流程执行就好。

    访问量大的话,也要看数据的大小来做预热措施。

    • 数据量不大的时候,工程启动的时候进行加载缓存动作,这种数据一般可以是电商首页的运营位之类的信息;
    • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
    • 数据量太大的时候,优先保证热点数据进行提前加载到缓存,并且确保访问期间不能更改缓存,比如用定时器在秒杀活动前30分钟就把商品信息之类的刷新到缓存,同时规定后台运营人员不能在秒杀期间更改商品属性。

    缓存降级

    缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。

    在项目实战中通常会将部分热点数据缓存到服务的内存中,类似HashMap、Gua va这样的工具,一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。

    当然,这样的操作对于业务是有损害的,分布式系统中很容易就出现数据不一致的问题,所以,一般这种情况下,我们都优先保证从运维角度确保缓存服务器的高可用性,比如Redis的部署采用集群方式,同时做好备份,总之,尽量避免出现降级的影响。

    最后

    关于缓存的几大异常处理我们就讲解到这了,虽然每种异常我们都给出了解决的方案,但不是说这玩意直接套上就能用了。现实开发过程中还是要根据实际情况来针对缓存做相应措施,比如用布隆过滤器预防缓存穿透虽然很有效,但并不算特别常用,这年头,防止恶意攻击什么的都是先在运维层面做限制,业务代码层面更多的是对参数和数据做校验。

    如果每个使用缓存的地方都要考虑的这么复杂的话,那工作量无疑会更加繁杂,过度设计只会让代码维护起来也麻烦,而且实用性还不一定强,没必要啊。程序员嘛,给自己增添烦恼的事情越少越好,毕竟我们最大的敌人不是996,而是那珍贵的发量啊。

    本文同步分享在 博客“鄙人薛某”(CSDN)。
    如有侵权,请联系 support@oschina.cn 删除。
    本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

    本文内容来源于网友投稿,如有侵权请联系删除。
    作者最新文章
    系统应用
    相关文章 更多
    win10升级新版本后取消开机密码失效怎么修复
    win10升级新版本后取消开机密码失效怎么修复

    Windows 10更新后开机突然要求输入密码?本文解析自动登录失效的真实原因,提供通过netplwiz重新保存凭据、关闭Windows Hello干扰及排查账户策略的完整步骤,助你恢复免密进入桌面。

    win10更新没下载完反复重试该怎么停止任务
    win10更新没下载完反复重试该怎么停止任务

    Windows 10更新下载失败反复重试怎么办?不要直接停用服务。本文教你通过设置页面暂停更新、启用按流量计费连接以及调整活动时间,安全地暂时停止下载任务并避免意外重启。

    win10怎么取消系统准备强制升级新版本
    win10怎么取消系统准备强制升级新版本

    面对Win10强制升级新版本的提示,本文解析家庭版与专业版的应对差异。介绍如何通过暂停更新、设置活动时间临时避让,以及利用组策略控制功能版本。强调Windows 10支持终止后的安全风险,避免使用高风险的禁用服务手段。

    win10组策略找不到更新选项该怎么处理
    win10组策略找不到更新选项该怎么处理

    Win10组策略编辑器中找不到Windows Update选项怎么办?本文分析家庭版与专业版的权限差异,厘清本地策略与域策略的区别,提供设置页替代方案及企业环境排查思路,避免盲目修改注册表或安装非官方补丁。

    win10取消开机密码会影响电脑里的文件安全吗?
    win10取消开机密码会影响电脑里的文件安全吗?

    很多人为了便捷选择取消 Win10 开机密码,这会导致文件丢失吗?本文详解取消密码后文件访问权限的变化、BitLocker 加密的有效性以及更安全的替代方案,帮助用户在便利与数据安全之间做出正确选择。

    win10弹出立即重启更新怎么延后这个操作
    win10弹出立即重启更新怎么延后这个操作

    Win10弹出立即重启更新会打断工作?本文介绍如何通过Windows Update页面安排重启时间、设置活动时段以及暂停后续更新,有效延后重启操作,保护未保存的工作成果。

    win10自动更新在夜间偷偷运行怎么限制
    win10自动更新在夜间偷偷运行怎么限制

    Win10晚上自动下载或重启怎么办?不要直接禁用服务。本文教你通过设置活动时间、安排指定重启时间及短期暂停更新,精准控制Windows Update行为,避免夜间网络占用和意外重启,同时保留系统安全性。

    win10本地账户取消开机密码有哪些简单方法
    win10本地账户取消开机密码有哪些简单方法

    想跳过Win10开机输入密码?本文详解本地账户清空密码、netplwiz自动登录及PIN设置的步骤与区别,同时提醒Microsoft账户和域策略下的限制与风险。

    win10更新重启倒计时怎么取消不让电脑重启
    win10更新重启倒计时怎么取消不让电脑重启

    面对Win10更新重启倒计时,如何通过设置界面取消或延后?本文详解活动时间、安排重启及暂停更新的位置与作用,帮助你在不影响工作的前提下管理系统重启。

    win10取消开机密码重启后还要输密码怎么办?
    win10取消开机密码重启后还要输密码怎么办?

    Win10设置取消密码后重启依然要求输入?这通常不是系统故障,而是自动登录配置错误或睡眠唤醒验证未关闭。本文教你区分本地账户与微软账户,正确设置netplwiz自动登录,并检查锁屏策略,彻底解决开机免密问题。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    WINDOWS 更多
    3dmax(3ds max)
    3dmax(3ds max)
    Windows

    Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。