news 2026/4/18 8:45:01

说说 Redis 缓存穿透场景与相应的解决方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
说说 Redis 缓存穿透场景与相应的解决方法

Redis 缓存主要缓存穿透、缓存击穿与缓存雪崩异常场景,今天我们来讲讲缓存穿透。

1 场景描述

缓存穿透是指客户端请求一个缓存和数据库中都不存在的 key。由于缓存中不存在,所以请求会透过缓存查询数据库;由于数据库中也不存在,所以也没办法更新缓存。因此下一次同样的请求还是会打在数据库上。

好像缓存被穿透了一样,缓存形如虚设。所有的压力都在数据库之上,如果请求量巨大,可能造成数据库崩溃。

2 解决方法

缓存穿透有以下几种解决方法。

2.1 接口校验

在请求入口进行校验,比如对用户进行鉴权、数据合法性检查等操作,这样可以减少缓存穿透发生的概率。

这种方式减轻了对 Redis 以及数据库的压力,但是增加了客户端的编码与维护的工作量。如果请求的入口很多,那么工作量很大。

2.2 缓存空值

当缓存与数据库中都没有 key 时,就设置一个空值写入缓存,并同时设置一个比较短的过期时间。由于在缓存中设置空值,所以请求在缓存这一级别就返回,也就不会被穿透。这些所说的不会被穿透只是针对某个 key 而言的。其它没有设置空值的 key,仍然存在被穿透的可能。

该方法的问题是:由于不存在的 key 几乎是无限,不可被穷举的,所以不可能都设置到缓存中。而且大量这样的空值 key 设置到缓存,也会占用大量的内存空间。

解决:采用下面提到的布隆过滤器直接过滤掉不存在的 key。

2.3 布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于判断一个元素是否在一个集合中2。

布隆过滤器的特点是判断为不存在的,则一定不存在;判断存在的,则大概率存在。

2.3.1 布隆过滤器原理

布隆过滤器的原理是当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想3。

假设有这样一个集合 S,它包含 a b c 三个元素。那么布隆过滤器会利用多个哈希函数(图中是三个哈希函数 h1、h2、h3)来计算所在的位,然后将该位设置为 1。比如元素 a,经过三个哈希函数计算后,将相应的位设置为 1,也就是图中的红线。元素 b 和 c 也是按照相应的方法进行计算处理。这时布隆过滤器初始化完毕。

假设有一个元素 d,需要判断它是否在我们刚才所创建的布隆过滤器中(图中的黄色线条)。经过三个哈希函数 h1、h2、h3 计算后,发现相应的位都是 1,布隆过滤器会返回 true。也就是认为这个元素可能在,也可能不在集合中。看到这里,有同学就会问:“既然这个布隆过滤器都不知道这个元素是不是在集合中,对我们有什么用呢?”

布隆过滤器的强大之处是可以利用较小的缓存,就可以判断出某个元素是否不在集合中。比如又来了一个元素 e,经过三个哈希函数 h1、h2、h3 计算后,发现 h1(e) 所对应的位是 0。那么这个元素 e 肯定不在集合中。有同学又说了:“我用 HashMap 不是也能判断出某个元素在不在集合中呀?”

HashMap 是可以判断,但需要存储集合中所有的元素。如果集合中有上亿个元素,那么就会占用大量的内存。内存空间毕竟是有限,可能还不一定放的下这么多的元素。与HashMap相比, 布隆过滤器占用的空间很小,所以很适合判断大集合中某个元素是否不存在。

2.3.2 布隆过滤器误识别率

之前的示例中可以看出,布隆过滤器判断为不存在的元素,则一定不存在;而判断存在的元素,则大概率存在。也就是说,有的元素可能并不在集合中,但是布隆过滤器会认为它存在。这就涉及到一个概念:误识别率。误识别率指的错误判断一个元素在集合中的概率。

假设布隆过滤器有 m bit 大小,需要放入 n 个元素,每个元素使用 k 个哈希函数,那么它的误识别率如下表所示4。

2.3.3 使用布隆过滤器

Google 的 Guava 库提供了使用布隆过滤器的 API 类(BloomFilter.class),它是线程安全的。

首先创建布隆过滤器:

//创建存储整型的布隆过滤器 bloomFilter = BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp);

创建布隆过滤器的方法有以下几个入参:

入参说明
Funnels 实例用于后续把类对象转换为相应的 hash 值。
expectedInsertions期望插入过滤器的元素个数。
fpp误识别率,该值必须大于 0 且小于 1.0。

Funnel 类定义了如何把一个具体的对象类型分解为原生字段值,从而将值分解为 Byte 以供后面 BloomFilter 进行 hash 运算5。也就是说 Guava 的布隆过滤器会根据Funnel 类的定义,计算一个对象的哈希值,放入过滤器。

Guava 官方提供了这样一个创建可插入自定义类的布隆过滤器示例。

首先创建一个 Person 类:

@Data @AllArgsConstructor public class Person { private String firstName; private String lastName; private int age; }

然后创建一个 PersonFunnel 类,它实现了 Funnel 类中的 funnel(Person from, PrimitiveSink into) 方法:

public class PersonFunnel implements Funnel<Person> { @Override public void funnel(Person from, PrimitiveSink into) { into.putUnencodedChars(from.getFirstName()).putUnencodedChars(from.getLastName()).putInt(from.getAge()); } }

这个方法主要是把 Person 类中的各个属性(名字、年龄)写入 PrimitiveSink 对象。 PrimitiveSink 提供了支持各种写入类型的方法:

接着把元素放入布隆过滤器:

bloomFilter.put(new Person("deniro","lee",20)); bloomFilter.put(new Person("lily","lee",16));

最后就是判断某个元素是否在布隆过滤器中:

Assert.assertFalse(bloomFilter.mightContain(new Person("jack","lee",20))); Assert.assertTrue(bloomFilter.mightContain(new Person("deniro","lee",20)));

Funnels 是个工具类,内置了一些创建基本类型的 Funnel:

我们可以利用这些 Funnel,来创建包含基本类型元素的布隆过滤器,比如创建一个包含整型元素的布隆过滤器:

BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

最后,让我们总结一波。

可以看到接口校验方法与 Guava 版的布隆过滤器方法都是在客户侧进行处理。布隆过滤器也可以在缓存层进行处理。相对来说,布隆过滤器方法比接口校验方法少了很多代码量与维护成本。缓存空值不可取,毕竟内存空间是有限的。

利用布隆过滤器,我们可以拦截绝大多数不存在的 key,因此很适合解决 Redis 缓存穿透问题。

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 12:27:37

【5分钟背八股】击穿、穿透、雪崩、预热解决方案?

缓存穿透 缓存穿透是指查询一个一定不存在的数据&#xff0c;由于缓存是不命中时被动写的&#xff0c;并且出于容错考虑&#xff0c;如果从存储层查不到数据则不写入缓存&#xff0c;这将导致这个不存在的数据每次请求都要到存储层去查询&#xff0c;失去了缓存的意义。在流量…

作者头像 李华
网站建设 2026/4/18 11:05:00

堪称是真正的“Markdown笔记之王”?!支持跨平台使用,本地离线访问使用、多格式导出,且免费用 维克日记

下载链接 https://pan.freedw.com/s/fYW41k 维克日记是一款功能全面、界面简洁的跨平台本地笔记应用&#xff0c;它支持Markdown语法输入和实时预览&#xff0c;主要为用户提供高效、私密的写作体验。软件支持每日日记记录编写&#xff0c;按日期分类&#xff0c;而且界面干净…

作者头像 李华
网站建设 2026/4/17 17:43:49

MinIO最新稳定版本说明

到了 2025 年 10 月份&#xff0c;MinIO 在 RELEASE.2025-10-15T17-29-55Z 版本修复了提权漏洞&#xff0c;但并没有同步构建社区版的二进制文件 和 Docker 镜像&#xff0c;停留在了 RELEASE.2025-09-07T16-13-09Z&#xff0c;也就是说现在的 MinIO 只提供源码&#xff0c;如果…

作者头像 李华
网站建设 2026/4/17 20:39:15

Linux 进程脱离父进程控制设置方法

在Linux中&#xff0c;要使进程完全脱离父进程控制&#xff0c;通常需要使用以下系统调用组合&#xff1a; 1. setsid() - 创建新会话 这是最关键的一步&#xff0c;它会&#xff1a; 创建一个新的会话使调用进程成为会话首进程使调用进程成为新进程组的组长使调用进程脱离控…

作者头像 李华
网站建设 2026/4/18 8:49:42

【收藏级】大模型学习全路径:从零基础到实战落地,程序员转型必看

当前AI大模型浪潮席卷全球&#xff0c;不仅众多程序员计划转型入局分一杯羹&#xff0c;零基础小白也渴望抓住这波时代机遇。但大模型知识体系繁杂庞大&#xff0c;不少学习者刚入门就陷入“不知从何学起”“学了就忘”的困境。本文基于系统学习框架&#xff0c;精准对标新手痛…

作者头像 李华
网站建设 2026/4/18 8:38:34

巧用Node.js REPL自定义提示符和命令技巧

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 目录 巧用Node.js REPL&#xff1a;深度定制提示符与命令的进阶技巧 一、为何深度定制REPL&#xff1f;超越“临时测试”的认知边…

作者头像 李华