java-redis-穿透

news2024/9/17 8:32:48

Redis 缓存穿透是指当请求的数据在缓存和数据库中都不存在时,用户每次请求都会直接查询数据库,导致缓存失效,无法发挥作用。这种情况下,用户发出的每个请求都绕过了缓存,直接打到了数据库,可能导致数据库压力骤增,甚至崩溃。

在实际应用中,缓存穿透通常会由于用户发送恶意请求或非法数据请求导致。例如,用户传递的 id 永远不会在数据库中找到相应的数据(如负数 ID 或过大的 ID)。由于这些 ID 不存在于数据库中,缓存也没有保存这些值,结果是每次请求都会直接访问数据库。

Redis 缓存穿透的常见解决方案

  1. 缓存空值:将不存在的结果也缓存起来,避免重复查询数据库。
  2. 布隆过滤器:通过布隆过滤器提前拦截非法请求,避免查询数据库。
  3. 参数校验:在查询数据库之前,先对请求参数进行校验,直接过滤掉非法请求。

接下来,我们逐一解释这些方案,并给出示例代码。

1. 缓存空值

当请求的键在数据库中不存在时,我们可以将这个“空结果”也缓存起来,并设置一个较短的过期时间,避免缓存永远存储无效值。下次再请求这个键时,直接从缓存中获取到空值,而不会访问数据库。

示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class CacheService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 模拟查询数据库
     */
    public String getDataFromDB(String key) {
        // 模拟数据库查询
        if ("validKey".equals(key)) {
            return "valueFromDB";
        }
        return null; // 模拟数据库中不存在
    }

    /**
     * 查询数据,避免缓存穿透
     */
    public String getData(String key) {
        // 先从缓存中查询
        String value = redisTemplate.opsForValue().get(key);
        
        if (value != null) {
            // 缓存命中
            return "从缓存获取: " + value;
        }

        // 如果缓存没有命中,则查询数据库
        value = getDataFromDB(key);

        if (value != null) {
            // 如果数据库存在,则写入缓存
            redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);
            return "从数据库获取: " + value;
        } else {
            // 如果数据库不存在,则将空值缓存,并设置短期过期时间
            redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
            return "数据不存在,缓存空值";
        }
    }
}

在这个例子中,当数据库中找不到数据时,缓存会存储一个空字符串,防止后续的重复查询。可以根据实际场景设置空值的过期时间,例如设置为 5 分钟。这样可以减少数据库的压力。

2. 使用布隆过滤器

布隆过滤器(Bloom Filter)是一种高效的概率数据结构,用于快速判断某个元素是否存在于一个集合中。通过将所有合法的键加入布隆过滤器中,可以在查询数据库前先判断请求是否有意义,如果布隆过滤器认为该键不存在,则可以直接返回,不再查询缓存和数据库。

布隆过滤器的特点:

  • 空间效率高:布隆过滤器使用哈希函数和位数组来表示元素的存在状态,空间占用很小。
  • 存在误判:布隆过滤器可能会误判某个不存在的元素为存在(即假阳性),但不会误判存在的元素为不存在。
示例代码:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

@Service
public class CacheServiceWithBloomFilter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private BloomFilter<String> bloomFilter;

    // 初始化布隆过滤器
    public CacheServiceWithBloomFilter() {
        // 创建布隆过滤器,设置预计插入元素数量为10000,误判率为0.01
        this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 10000, 0.01);
        
        // 将已有数据添加到布隆过滤器
        bloomFilter.put("validKey");
        // 可以继续添加其他已知合法的键
    }

    /**
     * 模拟查询数据库
     */
    public String getDataFromDB(String key) {
        if ("validKey".equals(key)) {
            return "valueFromDB";
        }
        return null;
    }

    /**
     * 查询数据,使用布隆过滤器避免缓存穿透
     */
    public String getData(String key) {
        // 先通过布隆过滤器判断该key是否可能存在
        if (!bloomFilter.mightContain(key)) {
            return "该数据不存在(布隆过滤器拦截)";
        }

        // 如果布隆过滤器认为存在,继续查询缓存
        String value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return "从缓存获取: " + value;
        }

        // 缓存未命中,查询数据库
        value = getDataFromDB(key);
        if (value != null) {
            // 数据库存在,写入缓存
            redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);
            return "从数据库获取: " + value;
        } else {
            // 数据库不存在,缓存空值
            redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
            return "数据不存在,缓存空值";
        }
    }
}

在这个例子中,布隆过滤器用于快速判断请求的键是否可能存在。如果布隆过滤器判断该键不可能存在,则直接返回“数据不存在”,避免不必要的缓存和数据库查询。

3. 参数校验

对于明显不合法的请求参数(例如负数 ID、过大的 ID 等),可以在请求到达 Redis 或数据库之前直接进行参数校验,避免不合法的请求进入后端系统。

示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class CacheServiceWithValidation {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 模拟查询数据库
     */
    public String getDataFromDB(String key) {
        if ("validKey".equals(key)) {
            return "valueFromDB";
        }
        return null;
    }

    /**
     * 查询数据,增加参数校验
     */
    public String getData(String key) {
        // 进行参数合法性校验,过滤掉明显不合法的请求
        if (key == null || key.length() == 0 || key.length() > 20) {
            return "请求参数不合法";
        }

        // 查询缓存
        String value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return "从缓存获取: " + value;
        }

        // 缓存未命中,查询数据库
        value = getDataFromDB(key);
        if (value != null) {
            redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);
            return "从数据库获取: " + value;
        } else {
            redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
            return "数据不存在,缓存空值";
        }
    }
}

在这个例子中,首先对请求的 key 进行参数校验,确保请求是合法的。如果 key 为空、长度过长等情况,直接返回错误信息,避免查询缓存和数据库。

Redis 穿透、击穿和雪崩

除了缓存穿透,还有两个常见的缓存问题:

  • 缓存击穿:缓存中没有但数据库中有的数据,并且该数据在短时间内有大量请求。如果这类请求同时访问数据库,可能导致数据库压力激增。

    解决方案:使用互斥锁(如分布式锁)防止多个线程同时查询数据库,或者为热点数据设置较长的过期时间。

  • 缓存雪崩:由于缓存服务器宕机或大量缓存同时失效,导致大量请求直接打到数据库,给数据库造成很大压力。

    解决方案:为缓存设置不同的过期时间(缓存失效时间的随机化),避免缓存集中失效;增加缓存节点的冗余。

总结

缓存穿透是 Redis 缓存系统中的一个常见问题,它会导致大量请求绕过缓存直接访问数据库,给数据库带来巨大的压力。针对缓存穿透,我们可以使用以下方案:

  1. **缓存空值

**:将数据库中不存在的值缓存起来,防止重复查询。
2. 布隆过滤器:在查询缓存和数据库之前,利用布隆过滤器判断请求是否合法,从而减少不必要的数据库查询。
3. 参数校验:在应用层进行参数合法性校验,直接拦截明显非法的请求。

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

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

相关文章

闯关leetcode——3.Longest Substring Without Repeating Characters

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/longest-substring-without-repeating-characters/description/ 内容 Given a string s, find the length of the longest substring without repeating characters. Example 1: Input: s “abc…

力扣最热一百题——矩阵置零

目录 题目链接&#xff1a;73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; 题目描述 示例 提示&#xff1a; 解法一&#xff1a;采用标记数组遍历处理 Java写法&#xff1a; C写法&#xff1a; 优化 解法二&#xff1a;优化解法之标记变量 Java写法&#xff1a;…

【鸿蒙HarmonyOS NEXT】UIAbility的生命周期

【鸿蒙HarmonyOS NEXT】UIAbility的生命周期 一、环境说明二、UIAbility的生命周期三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、UIAbility的生命周期 概念&#xff1a; HarmonyOS中的UIAbility是一种包含…

入门pytorch

卷积神经网络模型 卷积神经网络&#xff08;简称 CNN&#xff09;是一种专为图像输入而设计的网络。它最明显的特征就是具有三个层次&#xff0c;卷积层&#xff0c;池化层&#xff0c;全连接层。 借用一张图&#xff0c;下图很好的表示了什么是卷积&#xff08;提取特征&…

机器学习:多种算法处理填充后的数据

在机器学习中&#xff0c;填充数据&#xff08;即处理缺失值&#xff09;后&#xff0c;选择合适的算法并优化模型以提高召回率是一个常见的任务。召回率是指模型正确识别的正例占所有实际正例的比例。 代码思路&#xff1a; 数据预处理&#xff1a; 导入填充后的数据 …

FastGPT自定义插件的icon

最近研究FastGPT的自定义插件&#xff0c;经过好几天的折磨&#xff0c;终于实现了一个简单的发送邮件功能&#xff0c;但是呢在使用的时候发现插件的icon是默认的fastgpt的logo&#xff0c;那肯定得自定义一个啊。直接说方法&#xff1a; 1、自定义插件下面的template.json文件…

zookeeper相关面试题

zk的数据同步原理&#xff1f;zk的集群会出现脑裂的问题吗&#xff1f;zk的watch机制实现原理&#xff1f;zk是如何保证一致性的&#xff1f;zk的快速选举leader原理&#xff1f;zk的典型应用场景zk中一个客户端修改了数据之后&#xff0c;其他客户端能够马上获取到最新的数据吗…

握 手 问 题

目录 一&#xff1a;问题描述 二&#xff1a;思路: 三&#xff1a;代码&#xff1a; 四&#xff1a;结果&#xff1a;1204 一&#xff1a;问题描述 小蓝组织了一场算法交流会议&#xff0c;总共有50 5050 人参加了本次会议。在会议上&#xff0c;大家进行了握手交流。按照…

excel如何删除某列或者某区域的重复数据

先&#xff0c;鼠标选中想要去除重复数据的某列或者某区域 然后&#xff0c;点击上方栏中的【数据】-【删除重复数据】&#xff1a; 之后&#xff0c;表格里只留下了无重复的数据

STM32(十一):ADC数模转换器实验

AD单通道&#xff1a; 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO&#xff0c;把GPIO配置成模拟输入的模式。 3.配置多路开关&#xff0c;把左面通道接入到右面规则组列表里。 4.配置ADC转换器&#xff0c; 包括AD转换器和AD数据寄存器。单次转换&#xff0c;连…

Kafka (快速)安装部署

文章目录 1、软件下载&配置环境1_JDK安装2_Zookeeper安装3_Kafka安装 2、单机安装1_配置主机名和IP映射2_单机Kafka配置 3、集群安装1_配置主机名和IP的映射关系2_时钟同步3_Zookeeper配置信息4_集群Kafka配置 4、kafka的其他脚本命令5、监控工具Kafka-eagle安装 1、软件下…

Java并发编程实战 07 | 如何正确停止线程

什么时候需要停止一个线程&#xff1f; 一个线程被创建并启动之后&#xff0c;大部分情况下都会自然运行至结束&#xff0c;但是也有一些情况需要主动停止线程&#xff0c;比如&#xff1a; 用户主动取消执行&#xff1a;用户可能会中止一个正在进行的操作&#xff0c;这时需…

Python系统教程004(字符串)

一、input函数的算术运算 一包奥特曼卡片卖0.5元&#xff0c;小华想编写一个只需要输入卡片的包数就能自动计算价格的程序&#xff0c;请你帮帮它。 解题 报出类型错误 注意&#xff1a; input函数接收到的键盘信息&#xff0c;默认都是字符串的数据类型。 字符串的数据类型…

书生浦语三期实战营 [进阶] LMDeploy 量化部署进阶实践

LMDeploy 量化部署进阶实践 1 配置LMDeploy环境 1.1 InternStudio开发机创建与环境搭建 在终端中&#xff0c;让我们输入以下指令&#xff0c;来创建一个名为lmdeploy的conda环境&#xff0c;python版本为3.10&#xff0c;创建成功后激活环境并安装0.5.3版本的lmdeploy及相关…

IM即时通讯,稳定可靠的即时通讯服务-WorkPlus

在现代企业日常工作中&#xff0c;即时通讯已成为了一种不可或缺的沟通工具。为了满足企业对稳定可靠的即时通讯服务的需求&#xff0c;WorkPlus提供了一款优秀的IM即时通讯平台&#xff0c;以满足企业高效沟通和协作的要求。本文将深入探讨IM即时通讯服务的重要性以及WorkPlus…

Linux下的RTC应用

RTC RTC基础知识 1. RTC简介 RTC 全称是 Real-Time clock&#xff0c;翻译过来是实时时钟。实时时钟在日常生活中的应用也比较泛&#xff0c;比如电子时钟。实时时钟可以为系统提供精确的实时时间&#xff0c;通常带有电池&#xff0c;可以保证系统断电时还可以正常工作&…

JVM虚拟机 - 基础篇

一、初始JVM 1. JVM是什么 2. JVM的三大核心功能是什么&#xff1f; 3. 常见的JVM虚拟机有哪些&#xff1f; 二、字节码文件详解 1. Java虚拟机的组成 2. 字节码文件的组成 &#xff08;1&#xff09;基本信息 Magic魔数 主副版本号 &#xff08;2&#xff09;常量池 &#…

RabbitMQ练习(AMQP 0-9-1 Overview)

1、What is AMQP 0-9-1 AMQP 0-9-1&#xff08;高级消息队列协议&#xff09;是一种网络协议&#xff0c;它允许遵从该协议的客户端&#xff08;Publisher或者Consumer&#xff09;应用程序与遵从该协议的消息中间件代理&#xff08;Broker&#xff0c;如RabbitMQ&#xff09;…

Day19_0.1基础学习MATLAB学习小技巧总结(19)——MATLAB绘图篇(2)

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a;《MATLAB基础教程 (第三版) (薛山)》 之前的章节都是…

H5漂流瓶社交系统源码

一个非常有创意的H5漂流瓶社交系统源码&#xff0c;带完整前端h5和后台管理系统。 环境&#xff1a;Nginx 1.20.1-MySQL 5.6.50-PHP-7.3 代码下载