Redis之缓存穿透

Redis之缓存穿透

在这里插入图片描述

文章目录

  • Redis之缓存穿透
    • 一、什么是缓存穿透?
    • 二、缓存穿透常见的解决方案
      • 1. 缓存空对象(Null Caching)
      • 2. 布隆过滤器(Bloom Filter)​
      • 3. 互斥锁(Mutex Lock)​
      • 4. 接口层校验
      • 5. 热点数据永不过期
      • 6. 缓存预热
      • 7. 实时监控与限流
      • ☆☆☆组合方案推荐☆☆☆
    • 四、实践:缓存空对象

一、什么是缓存穿透?

  • 缓存穿透的定义:缓存穿透(Cache Penetration)是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库,导致数据库压力骤增甚至崩溃。
  • 触发原因:恶意攻击、参数伪造、业务逻辑漏洞。
  • 核心问题:大量请求访问数据库中不存在的数据,缓存无法拦截。
  • 示例场景:攻击者发送大量随机ID查询商品信息,这些ID在数据库中不存在,导致每次请求都穿透缓存直达数据库。
请求未命中
请求未命中
客户端
Redis
数据库
  • 与缓存击穿的区别

    • 缓存击穿的定义:缓存击穿(Cache Breakdown)​是指某个热点key​(如爆款商品信息)在缓存中过期后,大量并发请求同时访问数据库(请求数据存在),导致数据库压力骤增。
    • 触发原因:缓存过期时间到期,且高并发场景下请求集中失效。
    • 核心问题:单个热点key失效后,大量请求同时访问数据库。
    • 示例场景:某明星商品突然爆火,缓存中存储的商品信息过期后,所有用户请求同时涌入数据库查询。
    维度缓存穿透缓存击穿
    触发原因请求不存在的数据热点key过期后高并发请求
    数据合法性数据本身不存在(非法参数)数据存在但缓存失效(合法参数)
    攻击性可能是恶意攻击正常业务高并发
    影响范围分散的无效请求集中在某个热点key
    解决方案布隆过滤器、缓存空对象互斥锁、永不过期、后台更新

二、缓存穿透常见的解决方案

1. 缓存空对象(Null Caching)

  • 原理: 当查询数据库发现数据不存在时,将空结果(如 null)写入缓存,并设置较短的过期时间。
  • 优点:简单易实现,直接拦截后续相同请求。
  • 缺点
    • 内存浪费(存储大量无效 null 值)。
    • 可能出现短时不一致,如:数据已补录,但缓存未及时失效。(如需强一致性,可以在更新数据时,删除/覆盖缓存)
  • 实现
    请求未命中
    请求未命中
    缓存null 设置TTL
    客户端
    Redis
    数据库
    public Object getData(String key) {// 1. 查询缓存Object data = cache.get(key);if (data != null) return data;// 2. 查询数据库data = db.query(key);if (data == null) {// 缓存空对象,设置短期过期时间(如5分钟)cache.set(key, "NULL", 5 * 60);} else // 正常数据设置较长过期时间cache.set(key, data, 60 * 60);}return data;
    }
    

2. 布隆过滤器(Bloom Filter)​

  • 原理:​在缓存层前加布隆过滤器,预先存储所有合法 Key 的哈希值。查询时先检查布隆过滤器:
    • 若返回“不存在”,直接拦截请求。
    • 若返回“可能存在”,继续查询缓存/数据库。
  • 优点:内存占用低(没有多余的Key),适合海量数据;查询时间复杂度 O(1)。
  • 缺点
    • 存在误的可能(可能将不存在判断为存在)。
    • 实现复杂,删除元素困难(需重建过滤器)。
  • 适用场景:数据量大且允许误判(如黑名单校验)。
  • 实现
    请求
    可能存在,放行
    不存在,拒绝
    命中缓存,返回
    缓存未命中查询数据库
    缓存数据
    客户端
    布隆过滤器
    Redis
    数据库
    // 初始化布隆过滤器(伪代码)
    BloomFilter bloomFilter = BloomFilter.create(Funnels.stringFunnel(), expectedInsertions);// 数据预热时加载存在的key
    db.keys().forEach(k -> bloomFilter.put(k));public Object getData(String key) {// 1. 先查布隆过滤器if (!bloomFilter.mightContain(key)) {return null; // 直接拦截不存在的key}// 2. 查询缓存/数据库Object data = cache.get(key);if (data == null) {data = db.query(key);cache.set(key, data);}return data;
    }
    

3. 互斥锁(Mutex Lock)​

  • 原理:缓存未命中时,通过互斥锁(如 Redis 的 SETNX)保证只有一个线程查询数据库,其他线程等待回填缓存。
  • 优点:避免大量请求同时穿透到数据库。
  • 缺点
    • 分布式环境下需使用分布式锁(如 Redis RedLock)。
    • 锁竞争可能成为性能瓶颈。
  • 实现
    存在
    不存在
    用户请求数据
    缓存中是否存在?
    返回缓存数据
    尝试获取互斥锁
    获取锁成功?
    查询数据库
    写入缓存
    释放锁
    等待随机时间
    public Object getData(String key) {Object data = cache.get(key);if (data != null) return data;// 加锁(如Redis的SETNX)String lockKey = "lock:" + key;if (redis.setnx(lockKey, "1", 10)) { // 10秒锁超时try {// 二次检查缓存(防止锁竞争期间其他线程已加载)data = cache.get(key);if (data != null) return data;data = db.query(key);cache.set(key, data);} finally {redis.del(lockKey); // 释放锁}} else {// 等待重试Thread.sleep(100);return getData(key);}return data;
    }
    

4. 接口层校验

  • 原理:在 API 入口处校验参数合法性,拦截明显无效的请求(如非法 ID 格式、负数等)。
  • 优点:低成本防御恶意攻击(如扫描全表 ID)。
  • 缺点:无法拦截合法参数但实际不存在的数据请求。
  • 示例:校验 ID 是否为正整数、长度是否符合预期。
  • 实现
    public ResponseEntity<?> handleRequest(@PathVariable String id) {if (!isValidId(id)) { // 校验ID合法性return ResponseEntity.badRequest().build();}// 继续处理业务逻辑
    }
    

5. 热点数据永不过期

  • 原理:对高频访问的热点数据设置永不过期,通过后台线程主动更新缓存。
  • 优点:彻底避免缓存失效导致的穿透。
  • 缺点:数据一致性依赖更新机制,需处理脏数据问题。
  • 实现:结合定时任务或事件驱动更新缓存。
    // 缓存写入时设置永不过期
    cache.set("hot_key", data);// 后台定时任务刷新数据
    @Scheduled(fixedRate = 300000)
    void refreshHotData() {Object newData = db.query("hot_key");cache.set("hot_key", newData);
    }
    

6. 缓存预热

  • 原理:在系统启动或低峰期,预先加载热点数据到缓存中。
  • 优点:减少冷启动时的缓存穿透风险。
  • ​缺点:需提前知道热点数据(可通过历史日志分析)。

7. 实时监控与限流

  • 原理:监控异常流量(如大量 null 响应),触发限流策略(如令牌桶、漏桶算法),保护数据库。
  • 优点:兜底防御,避免突发攻击。
  • 缺点:需配套监控和告警系统。

☆☆☆组合方案推荐☆☆☆

  • 常规场景:缓存空对象 + 接口参数校验。
  • 海量数据:布隆过滤器 + 缓存空对象。
  • 高并发热点数据:永不过期缓存 + 后台更新线程 + 互斥锁。

四、实践:缓存空对象

解决根据id查询商铺信息过程中的缓存穿透

命中
不是
未命中
存在
不存在
开始
提交商铺id
从Redis查询商铺缓存
判断缓存是否命中
判断是否空值
结束
返回商铺信息
根据id查询数据库
判断商铺是否存在
将商铺数据写入Redis
将空值写入Redis
    @Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 1.从redis查询商铺缓存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}// 判断命中的是否是空值if(shopJson != null) {// 返回错误信息,解决缓存穿透问题return Result.fail("店铺信息不存在!");}// 4.不存在,根据id查询数据库Shop shop = getById(id);if (shop == null) {// 5.数据库不存在,将空字串写入Redis,设置过期时间,解决缓存穿透问题stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息,解决缓存穿透问题return Result.fail("店铺不存在!");}// 6.存在,写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shop);}

总结:缓存穿透的解决方案需结合业务场景选择,通常需要多种手段协同(如布隆过滤器拦截非法 Key + 缓存空对象减少数据库压力)。同时需权衡内存、一致性和性能,避免过度设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/77064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯】颜色平衡树

思路 颜色平衡树&#xff0c;即子树中的节点颜色均匀分布。所以要确认一个子树是否为颜色平衡树&#xff0c;需要得到它的所有节点的颜色&#xff0c;也就是要深搜它所有的子树。 这个想法就很标准的启发式合并了&#xff0c;何为启发式合并&#xff1f;简单来说&#xff0c;…

自动化测试工具playwright中文文档-------14.Chrome 插件

介绍 注意 插件仅在以持久化上下文启动的 Chrome/Chromium 浏览器中工作。请谨慎使用自定义浏览器参数&#xff0c;因为其中一些可能会破坏 Playwright 的功能。 以下是获取位于 ./my-extension 的 Manifest v2 插件背景页面句柄的代码示例。 from playwright.sync_api imp…

让 Python 脚本在后台持续运行:架构级解决方案与工业级实践指南

让 Python 脚本在后台持续运行&#xff1a;架构级解决方案与工业级实践指南 一、生产环境需求全景分析 1.1 后台进程的工业级要求矩阵 维度开发环境要求生产环境要求容灾要求可靠性单点运行集群部署跨机房容灾可观测性控制台输出集中式日志分布式追踪资源管理无限制CPU/Memo…

MyBatis 详解

1. 什么是 MyBatis&#xff1f; MyBatis 是一款优秀的 持久层框架&#xff0c;它通过 XML 或注解配置&#xff0c;将 Java 对象&#xff08;POJO&#xff09;与数据库操作&#xff08;SQL&#xff09;进行灵活映射&#xff0c;简化了 JDBC 的复杂操作。 核心思想&#xff1a;S…

循环神经网络 - 深层循环神经网络

如果将深度定义为网络中信息传递路径长度的话&#xff0c;循环神经网络可以看作既“深”又“浅”的网络。 一方面来说&#xff0c;如果我们把循环网络按时间展开&#xff0c;长时间间隔的状态之间的路径很长&#xff0c;循环网络可以看作一个非常深的网络。 从另一方面来 说&…

GoLand 标红但程序可正常运行:由符号索引缓存失效引起的假报错问题

问题描述&#xff1a; 在 GoLand 中&#xff0c;api/tls.go 文件中引用了 api/type.go 中定义的结构体 Options&#xff0c;但 GoLand 把 Options 标红显示为未定义&#xff08;undefined symbol&#xff09;&#xff0c;尽管程序实际可以正常编译和运行&#xff08;go build /…

python-各种文件(txt,xls,csv,sql,二进制文件)读写操作、文件类型转换、数据分析代码讲解

1.文件txt读写标准用法 1.1写入文件 要读取文件&#xff0c;首先得使用 open() 函数打开文件。 file open(file_path, moder, encodingNone) file_path&#xff1a;文件的路径&#xff0c;可以是绝对路径或者相对路径。mode&#xff1a;文件打开模式&#xff0c;r 代表以…

Uniapp:确认框

目录 一、 出现场景二、 效果展示三、具体使用 一、 出现场景 在项目的开发中&#xff0c;会经常出现删除数据的情况&#xff0c;如果直接删除的话&#xff0c;可能会存在误删&#xff0c;用户体验不好&#xff0c;所以需要增加一个消息提示&#xff0c;提醒用户是否删除。 二…

解密 Vue 打包策略

1. 总体概述 在现代前端开发中&#xff0c;Vue 已成为流行框架之一&#xff0c;开发者通常使用 webpack、vite 或 vue-cli 来构建项目。可能会困惑&#xff1a; 为什么源码中的资源引用路径与打包后实际产出的路径会不一样&#xff1f;静态路径与动态路径到底如何正确书写&am…

Golang|接口并发测试和压力测试

文章目录 这里出现某些奖品和数据库中库存量不一致的问题原因就是在并发的情况下&#xff0c;sync.Map仍然会出现脏写问题&#xff0c;就是在同时操作下的操作覆盖问题可以先把数据放到channel里&#xff0c;然后用一个单一的协程负责读取channel并写入map

CentOS下,Xftp中文文件名乱码的处理方式

乱码原因 中文版Windows默认使用GBK编码&#xff0c;现代Linux发行版&#xff08;如CentOS、Ubuntu等&#xff09;默认使用UTF-8编码。Windows下正常的编码&#xff0c;可能在linux下无法识别&#xff0c;例如&#xff1a;Windows的GBK字节0xD6D0被Linux用UTF-8解码时&#xf…

解决 Vue 中 input 输入框被赋值后,无法再修改和编辑的问题

目录 需求&#xff1a; 出现 BUG&#xff1a; Bug 代码复现 解决问题&#xff1a; 解决方法1&#xff1a; 解决方法2 关于 $set() 的补充&#xff1a; 需求&#xff1a; 前段时间&#xff0c;接到了一个需求&#xff1a;在选择框中选中某个下拉菜单时&#xff0c;对应的…

【含文档+PPT+源码】基于微信小程序的卫生院预约挂号管理系统的设计与实现

项目视频介绍&#xff1a; 毕业作品基于微信小程序的卫生院预约挂号管理系统的设计与实现 课程简介&#xff1a; 本课程演示的是一款基于微信小程序的卫生院预约挂号管理系统的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习…

【Vue】案例——To do list:

【Vue】案例——To do list&#xff1a; 一、案例介绍&#xff1a;二、效果展示&#xff08;如图&#xff09;三、主要功能&#xff1a;四、技术要点&#xff1a;补充&#xff1a;【Vue】Vue模板语法(点击可跳转)补充&#xff1a;【Vue】数据绑定&#xff08;单双向&#xff09…

导入 .sql 文件到 云服务器上的MySQL中

导入 .sql 文件到 云服务器上的MySQL中 步骤 1&#xff1a;确保 .sql 文件已上传到云服务器步骤 2&#xff1a;登录到云服务器步骤 3&#xff1a;检查文件是否成功传输步骤 4&#xff1a;登录 MySQL步骤 5&#xff1a;创建空数据库&#xff08;如果尚未创建&#xff09;步骤 6&…

我的机器学习之路(初稿)

文章目录 一、机器学习定义二、核心三要素三、算法类型详解1. 监督学习&#xff08;带标签数据&#xff09;2. 无监督学习&#xff08;无标签数据&#xff09;3. 强化学习&#xff08;决策优化&#xff09;(我之后主攻的方向) 四、典型应用场景五、学习路线图六、常见误区警示七…

VueDOMPurifyHTML 防止 ​​XSS(跨站脚本攻击)​​ 风险

VueDOMPurifyHTML 是一个 ​​Vue.js 插件​​&#xff0c;用于在 v-html 指令中安全地渲染 HTML 内容&#xff0c;防止 ​​XSS&#xff08;跨站脚本攻击&#xff09;​​ 风险。 ​​作用​​ ​​解决 v-html 的安全问题​​ Vue 的 v-html 会直接渲染原始 HTML&#xff0…

【数据结构】之散列

一、定义与基本术语 &#xff08;一&#xff09;、定义 散列&#xff08;Hash&#xff09;是一种将键&#xff08;key&#xff09;通过散列函数映射到一个固定大小的数组中的技术&#xff0c;因为键值对的映射关系&#xff0c;散列表可以实现快速的插入、删除和查找操作。在这…

How AI could empower any business - Andrew Ng

How AI could empower any business - Andrew Ng References 人工智能如何为任何业务提供支持 empower /ɪmˈpaʊə(r)/ vt. 授权&#xff1b;给 (某人) ...的权力&#xff1b;使控制局势&#xff1b;增加 (某人的) 自主权When I think about the rise of AI, I’m reminded …

微服务的服务调用详解以及常见解决方案对比

微服务服务调用详解 1. 服务调用分类 服务调用根据通信方式、同步性、实现模式可分为以下类型&#xff1a; 按通信协议分类 类型典型协议/框架特点RPC&#xff08;远程过程调用&#xff09;Dubbo、gRPC、Apache Thrift高性能、二进制协议、强类型定义HTTP/RESTSpring RestTe…