【JAVA架构师成长之路】【Redis】第14集:Redis缓存穿透原理、规避、解决方案

30分钟自学教程:Redis缓存穿透原理与解决方案

目标

  1. 理解缓存穿透的成因及危害。
  2. 掌握布隆过滤器、空值缓存等核心防御技术。
  3. 能够通过代码实现请求拦截与缓存保护。
  4. 学会限流降级、异步加载等应急方案。

教程内容

0~2分钟:缓存穿透的定义与核心原因
  • 定义:恶意或异常请求频繁访问数据库中不存在的数据,绕过缓存直接冲击数据库。
  • 典型场景
    • 攻击者伪造大量非法ID(如负数、超长字符串)。
    • 业务未对查询参数校验,或未缓存空结果。
  • 危害
    • 数据库压力激增,甚至宕机。
    • 正常服务被恶意请求拖垮。

2~5分钟:代码模拟穿透场景(Java示例)
// 未做防护的查询方法(模拟穿透问题)  
public Product getProduct(String id) {  
    String key = "product:" + id;  
    Product product = redisTemplate.opsForValue().get(key);  
    if (product == null) {  
        // 直接查询数据库(未缓存空值)  
        product = productService.loadFromDB(id);  
        if (product != null) {  
            redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);  
        }  
    }  
    return product; // 恶意请求会反复查询数据库  
}  

验证问题

  • 使用JMeter发送100次id=-1的请求,观察数据库查询次数是否为100次(穿透发生)。

5~12分钟:解决方案1——布隆过滤器(Bloom Filter)
  • 原理:基于位数组和哈希函数,快速判断数据是否可能存在于数据库,拦截非法请求。
  • 代码实现(Redisson布隆过滤器)
// 初始化布隆过滤器并预热合法数据  
public class BloomFilterInit {  
    private RBloomFilter<String> bloomFilter;  

    @PostConstruct  
    public void init() {  
        bloomFilter = redissonClient.getBloomFilter("product_bloom");  
        bloomFilter.tryInit(100000L, 0.01); // 容量10万,误判率1%  
        List<String> validIds = productService.getAllValidIds();  
        validIds.forEach(bloomFilter::add);  
    }  
}  

// 查询时拦截非法请求  
public Product getProductWithBloomFilter(String id) {  
    if (!bloomFilter.contains(id)) {  
        return null; // 直接拦截  
    }  
    // 正常查询逻辑...  
}  
  • 注意事项
    • 误判率需根据业务容忍度调整(如0.1%更严格,但占用更多内存)。
    • 需定期同步布隆过滤器与数据库的合法数据(如定时任务)。

12~20分钟:解决方案2——空值缓存(Cache Null)
  • 原理:即使数据库不存在该数据,也缓存空值(如“NULL”),避免重复穿透。
  • 代码实现
public Product getProductWithNullCache(String id) {  
    String key = "product:" + id;  
    Product product = redisTemplate.opsForValue().get(key);  
    if (product == null) {  
        product = productService.loadFromDB(id);  
        if (product == null) {  
            // 缓存空值,5分钟过期  
            redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);  
            return null;  
        }  
        redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);  
    } else if ("NULL".equals(product)) {  
        return null; // 直接返回空结果  
    }  
    return product;  
}  
  • 优化点
    • 空值过期时间不宜过长(避免存储大量无效Key)。
    • 可结合布隆过滤器,减少空值缓存的数量。

20~25分钟:解决方案3——请求参数校验
  • 原理:在业务层拦截非法参数(如非数字ID、越界值)。
  • 代码实现(Spring Boot参数校验)
public Product getProduct(@PathVariable String id) {  
    // 校验ID格式(仅允许数字)  
    if (!id.matches("\\d+")) {  
        throw new IllegalArgumentException("非法ID格式");  
    }  
    // 校验ID范围(如大于0)  
    long numericId = Long.parseLong(id);  
    if (numericId <= 0) {  
        throw new IllegalArgumentException("ID必须为正数");  
    }  
    // 正常查询逻辑...  
}  
  • 扩展
    • 使用Hibernate Validator实现注解式校验(如@Min(1))。

25~28分钟:应急处理方案
  1. 限流降级(Guava RateLimiter)
private RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100个请求  

public Product getProduct(String id) {  
    if (!rateLimiter.tryAcquire()) {  
        throw new RuntimeException("请求过于频繁,请稍后重试");  
    }  
    // 正常查询逻辑...  
}  
  1. 异步加载(CompletableFuture)
public Product getProductAsync(String id) {  
    String key = "product:" + id;  
    Product product = redisTemplate.opsForValue().get(key);  
    if (product == null) {  
        CompletableFuture.runAsync(() -> {  
            Product dbProduct = productService.loadFromDB(id);  
            if (dbProduct != null) {  
                redisTemplate.opsForValue().set(key, dbProduct, 1, TimeUnit.HOURS);  
            }  
        });  
    }  
    return product; // 可能返回空,但避免阻塞请求  
}  

28~30分钟:总结与优化方向
  • 核心原则:拦截非法请求、缓存空值、业务兜底。
  • 高级优化
    • 结合Redis Module的RedisBloom扩展(生产级布隆过滤器)。
    • 动态调整限流阈值(如根据数据库负载自动限流)。

练习与拓展

练习

  1. 实现一个布隆过滤器,拦截id<=0的非法请求。
  2. 修改空值缓存逻辑,动态设置随机过期时间(如5~15分钟)。

推荐拓展

  1. 学习RedisBloom模块的安装与使用。
  2. 研究分布式限流框架(如Sentinel)的实现原理。
  3. 探索缓存穿透与缓存击穿的综合防护方案。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【CUDA】Reduce归约求和(下)

目录 前言1. 优化技巧4&#xff1a;展开最后一个warp减少同步2. 优化技巧5&#xff1a;完全展开循环3. 优化技巧6&#xff1a;调节GridSize和BlockSize4. 优化技巧7&#xff1a;使用shuffle指令5. 拓展—CUDA工具链的使用结语下载链接参考 前言 学习 UP 主 比飞鸟贵重的多_HKL …

IDE集成开发环境MyEclipse中安装SVN

打开Myeclipse的help菜单----install from site 点击add弹出对话框 在输入框中输入对应内容 http://subclipse.tigris.org/update_1.10.x 点击OK之后&#xff0c;会刷新出两个选项&#xff0c;需要选中的 点击next&#xff0c;出现许可的时候选中同意&#xff0c;一直结束等…

如何计算两个向量的余弦相似度

参考笔记&#xff1a; https://zhuanlan.zhihu.com/p/677639498 日常学习之&#xff1a;如何计算两个向量或者矩阵的余弦相似度-CSDN博客 1.余弦相似度定理 百度的解释&#xff1a;余弦相似度&#xff0c;又称为余弦相似性&#xff0c;是通过计算两个向量的夹角余弦值来评估…

国产编辑器EverEdit - 宏功能介绍

1 宏 1.1 应用场景 宏是一种重复执行简单工作的利器&#xff0c;可以让用户愉快的从繁琐的工作中解放出来&#xff0c;其本质是对键盘和菜单的操作序列的录制&#xff0c;并不会识别文件的内容&#xff0c;属于无差别无脑执行。 特别是对一些有规律的重复按键动作&#xff0c;…

vue安装stylelint

执行 npm install -D stylelint postcss-html stylelint-config-recommended-vue stylelint-config-standard stylelint-order stylelint-prettier postcss-less stylelint-config-property-sort-order-smacss 安装依赖&#xff0c;这里是less&#xff0c;sass换成postcss-scss…

(最新教程)Cursor Pro订阅升级开通教程,使用支付宝订阅Cursor Pro Plus

一、如何使用Cursor &#xff1f; 目前要使用Cursor - The AI Code Editor&#xff0c;直接去下载安装就可以了&#xff0c;不过基础版只能用两周&#xff0c;如果需要继续使用&#xff0c;就要订阅pro plus或者企业版了。 二、如何订阅Cursor Pro Plus &#xff1f; 因为基础…

Cursor 使用经验,一个需求开发全流程

软件开发中 Cursor 的使用经验成为关注焦点&#xff0c;尤其是处理大型数据集的需求。用户提到“Cursor 使用经验&#xff0c;一个需求开发全流程”&#xff0c;但“Cursor”可能指数据库游标&#xff0c;涉及逐行处理数据。本文将详细探讨开发一个需求的完整流程&#xff0c;包…

vue2实现组件库的自动按需引入,unplugin-auto-import,unplugin-vue-components

1.使用ant-design-vue或者element-ui时&#xff0c;如何每个组件都去import导入组件&#xff0c;大大降低了开发效率&#xff0c;如果全局一次性注册会增加项目体积&#xff0c;那么如何实现既不局部引入&#xff0c;也不全局注册&#xff1f; 2.在element-plus官网看到有说明…

蓝桥杯备赛:一道数学题(练思维(同余的应用))

题目&#xff1a;请问由1-8组成的8位数中有多少个数字可以被1111整除&#xff1f; 首先这道题目看着很难&#xff0c;如果我们直接用代码做的话&#xff0c;也要跑很久&#xff0c;那能不呢想想有什么样的思路可以巧妙一点解开这道题目呢&#xff1f; 有的兄弟有的 这道题目的…

[Lc7_分治-快排] 快速选择排序 | 数组中的第K个最大元素 | 库存管理 III

目录 1. 数组中的第K个最大元素 题解 代码 2.库存管理 III 代码 1. 数组中的第K个最大元素 题目链接&#xff1a;215. 数组中的第K个最大元素 题目分析&#xff1a; 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要…

Unity引擎使用HybridCLR(华佗)热更新

大家好&#xff0c;我是阿赵。   阿赵我做手机游戏已经有十几年时间了。记得刚开始从做页游的公司转到去做手游的公司&#xff0c;在面试的时候很重要的一个点&#xff0c;就是会不会用Lua。使用Lua的原因很简单&#xff0c;就是为了热更新。   热更新游戏内容很重要。如果…

【神经网络】python实现神经网络(一)——数据集获取

一.概述 在文章【机器学习】一个例子带你了解神经网络是什么中&#xff0c;我们大致了解神经网络的正向信息传导、反向传导以及学习过程的大致流程&#xff0c;现在我们正式开始进行代码的实现&#xff0c;首先我们来实现第一步的运算过程模拟讲解&#xff1a;正向传导。本次代…

【Linux】冯诺依曼体系与操作系统理解

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、冯诺依曼体系结构 二、操作系统 1. 操作系统的概念 2. 操作系统存在的意义 3. 操作系统的管理方式 4. 补充&#xff1a;理解系统调用…

HTML-网页介绍

一、网页 1.什么是网页&#xff1a; 网站是指在因特网上根据一定的规则&#xff0c;使用 HTML 等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是 HTML 格式的文件&#xff0c;它要通过浏览器来阅读。 网页是构成网站的基本元素&#xf…

STM32——GPIO介绍

GPIO(General-Purpose IO ports,通用输入/输出接口)模块是STM32的外设接口的核心部分,用于感知外界信号(输入模式)和控制外部设备(输出模式),支持多种工作模式和配置选项。 1、GPIO 基本结构 STM32F407 的每个 GPIO 引脚均可独立配置,主要特性包括: 9 组 GPIO 端口…

字节码是由什么组成的?

Java字节码是Java程序编译后的中间产物&#xff0c;它是一种二进制格式的代码&#xff0c;可以在Java虚拟机&#xff08;JVM&#xff09;上运行。理解字节码的组成有助于我们更好地理解Java程序的运行机制。 1. Java字节码是什么&#xff1f; 定义 Java字节码是Java源代码经过…

链表算法题目

1.两数相加 两个非空链表&#xff0c;分别表示两个整数&#xff0c;只不过是反着存储的&#xff0c;即先存储低位在存储高位。要求计算这两个链表所表示数的和&#xff0c;然后再以相同的表示方式将结果表示出来。如示例一&#xff1a;两个数分别是342和465&#xff0c;和为807…

blender学习25.3.8

【04-进阶篇】Blender材质及灯光Cycle渲染&后期_哔哩哔哩_bilibili 注意的问题 这一节有一个大重点就是你得打开显卡的渲染&#xff0c;否则cpu直接跑满然后渲染的还十分慢 在这里你要打开GPU计算&#xff0c;但是这还不够 左上角编辑&#xff0c;偏好设置&#xff0c;系…

【godot4.4】布局函数库Layouts

概述 为了方便编写一些自定义容器和控件、节点时方便元素布局&#xff0c;所以编写了一套布局的求取函数&#xff0c;统一放置在一个名为Layouts的静态函数库中。 本文介绍我自定义的一些布局计算和实现以及函数编写的思路&#xff0c;并提供完整的函数库代码&#xff08;持续…

Windows下配置Conda环境路径

问题描述&#xff1a; 安装好Conda之后&#xff0c;创建好自己的虚拟环境&#xff0c;同时下载并安装了Pycharm&#xff0c;但在Pycharm中找不到自己使用Conda创建好的虚拟环境。显示“Conda executable is not found” 解决办法&#xff08;依次尝试以下&#xff09; 起初怀…