redis缓存穿透、案例

news2025/6/16 2:35:25

1、缓存穿透是什么

        缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

  • 其实:就是黑客利用不存在的数据(如:数据库id自增的情况下,黑客传递id为负数的参数),拼接在原接口上,对服务器进行大量的请求,从而加大数据库压力直至服务器崩溃

2、缓存穿透案例

需要使用的技术栈:

  • mysql
  • redis
  • springboot
  • Jmeter

2.1、模拟场景

  1. 先把mysql最大访问量设置为3
  2. 编写java接口获取商品详情数据,当redis有数据直接从redis获取,当redis无数据,从mysql获取,同时重新设置缓存
  3. 使用jmeter压测工具,同时发送id小于0的请求(非正常请求),所有的请求都会直接打到mysql,
  4. 此时,mysql最大访问量只有3,很快mysql就会报:too many connection,从而mysql无法正常工作,导致后端服务器无法获取数据直至无法正常运行
  5. 后端接口响应时间最好调至10s,当一个接口10s后未返回数据,证明服务器已崩溃

2.2、把mysql的最大访问量设置为3

目的:模拟当恶意用户使用大量非法参数的请求时,能快速增大mysql的压力,从而实现服务器性能下降

步骤如下:

<1>在连接mysql的客户端中(我使用的是IDEA),执行SQL语句:

-- 设置mysql最大连接数为3,当超出3时,mysql会报错too many connection
SET GLOBAL max_connections=3;

 <2>检查mysql当前最大连接数

SHOW VARIABLES LIKE 'max_connections';

如下:(注意:上述设置只是临时的,当mysql重启后,最大连接数会重置至默认状态)

2.3、初始化测试表

<1>DDL

create table goods
(
    id   int auto_increment  primary key,
    name     varchar(100) null,
    price    double       null,
    comments varchar(100) null
)
comment '商品表';

<2>添加表数据

INSERT INTO goods (id, name, price, comments) VALUES (1, '小米14', 4999, '【买即送199好礼 24期免息】Xiaomi 13Pro新品手机徕卡影像/2K屏/骁龙8 Gen2官方旗舰店官网正品小米13pro');
INSERT INTO goods (id, name, price, comments) VALUES (2, '苹果14', 5778, '顺丰速发【24期免息】iPhone/苹果14 Pro/Pro Max 5G新款手机官方旗舰店国行正品plus官网13直降的分期12');
INSERT INTO goods (id, name, price, comments) VALUES (3, '华为Mate50', 5449, '现货Huawei/华为Mate50Pro 手机原装正品旗舰华为mate50pro鸿蒙');

2.4、编写测试代码

<1>目录结构如下 

<2>所用依赖如下

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
	<version>2.2.12.RELEASE</version>
</dependency>
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus</artifactId>
	<version>3.3.1</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.2.5</version>
</dependency>

<!-- redis所需的连接池 -->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
</dependency>

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.75</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>

<3>application.properties

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/taobao
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.redis.host=192.168.101.8
spring.redis.port=6379
spring.redis.timeout=
#默认使用第一个数据库,一共16个
spring.redis.database=0
#关闭超时时间
spring.redis.lettuce.shutdown-timeout=18000
#连接池最大的连接数(使用负数表示无限制)
spring.redis.lettuce.pool.max-active=8
#最大阻塞等待时间(使用负数表示无限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
#设置过期时间为10s
spring.mvc.async.request-timeout=1000

<4>redis配置类

package com.shuizhu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.net.UnknownHostException;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        // 创建模板
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 设置序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer =
                new GenericJackson2JsonRedisSerializer();
        // key和 hashKey采用 string序列化
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        // value和 hashValue采用 JSON序列化
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        return redisTemplate;
    }
}

 <5>数据源配置类

package com.shuizhu.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.shuizhu.dao", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSourceConfig {

    @Primary
    @Bean(name = "db1DataSource")
    @ConfigurationProperties("spring.datasource")
    public DataSource db1DataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }

    @Bean(name = "db1SqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResource("classpath:mapper/db1/Demo.xml"));
        PathMatchingResourcePatternResolver resource = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resource.getResources("classpath:mybatis/*.xml"));
        return bean.getObject();
    }
}

<6>商品表实体类代码

package com.shuizhu.domain;
import lombok.Data;

@Data
public class Goods {
    private Integer id;
    private String name;
    private Double price;
    private String comments;
}

<7>dao层代码

package com.shuizhu.dao;

import com.shuizhu.domain.Goods;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface IGoodsMapper {
    //查询所有商品
    List<Goods> getAll();

    //根据商品id获取对应商品
    Goods getById(Integer id);
}

<8>dao映射文件xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shuizhu.dao.IGoodsMapper">

    <select id="getAll" resultType="com.shuizhu.domain.Goods">
        select
            id,name,price,comments
        from goods
    </select>

    <select id="getById" resultType="com.shuizhu.domain.Goods">
        select
            id,name,price,comments
        from goods
        where id = #{id}
    </select>

</mapper>

<9>controller层测试类

这里模拟的是正常的业务逻辑(未对缓存穿透做限制)

package com.shuizhu.controller;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.shuizhu.dao.IGoodsMapper;
import com.shuizhu.domain.Goods;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@Log4j2
public class TestController {

    @Autowired
    IGoodsMapper dao;
    @Autowired
    RedisTemplate redisTemplate;

    //存储商品集合的key
    public static final String GOODS_KEY = "goods_key";

    //当缓存不存在时,重新设置商品缓存
    @RequestMapping("/goods/setCacheForGoods")
    public String setCacheForGoods(){
        Long i = redisTemplate.opsForList().leftPushAll(GOODS_KEY, dao.getAll());
        return String.format("本次redis更新:%d条数据", i);
    }

    //模拟根据商品ID获取商品数据
    @RequestMapping("/goods/{id}")
    public Goods getById(@PathVariable("id") int id) {
        log.warn("当前访问的商品id为:{}", id);
        //1、访问redis获取所有数据    0,-1这个区间表示获取所有的数据
        List<Goods> range = redisTemplate.opsForList().range(GOODS_KEY, 0, -1);
        //2、判断range是否为null,为null则表示缓存中没有,需要从redis获取
        if (ObjectUtils.isEmpty(range)) {
            //3、读取数据库,更新缓存
            setCacheForGoods();
            List<Goods> nowGoods = dao.getAll().stream().filter(goods -> goods.getId() == id).collect(Collectors.toList());
            //4、当该id查询不到数据时,返回null,存在数据,直接返回该数据
            return ObjectUtils.isEmpty(nowGoods) ? null : nowGoods.get(0);
        }
        //5、rangenull,则在range中,根据当前id获取对应商品
        List<Goods> goodsList = range.stream().filter(goods -> goods.getId() == id).collect(Collectors.toList());
        //6、判断当前goodsList是否存在
        if (ObjectUtils.isNotEmpty(goodsList)) {
            //7、存在则直接返回
            return goodsList.get(0);
        }
        //8、缓存中有商品数据,但是该id对应的商品数据不存在,则需要去查询数据库,看是否存在
        Goods byId = dao.getById(id);
        if (ObjectUtils.isEmpty(byId)) {
            //9、该id没有对应的商品,证明是恶意id,返回null
            return null;
        }
        //10、数据库存在数据,证明是新的商品,先更新缓存,再返回数据
        setCacheForGoods();
        return byId;
    }
}

注意:

  • http://localhost:8080/goods/setCacheForGoods 请求:设置redis缓存所有的商品数据
  • http://localhost:8080/goods/商品id 请求:获取id下的详细信息,如传递3,则获取ID为3下的所有数据,若传递-1,则获取不到数据,需要查询数据库

2.5、jmeter压测

<1>使用浏览器访问接口

 试下id为-1的请求:


一切没有问题,下面我们开始使用压测工具,发送大量的非法请求,看下服务器最终状态: 

<2>简单配置下jmeter: 

<3>开始测试

点击开始:

 我们直接去看idea控制台打印内容,如下:

这时,我们直接使用浏览器,发送正常参数的请求,看下效果:

发现: 

访问正常参数的请求时,服务器无法正常响应! 

3、缓存穿透解决方案

大致3种:

方案1:对于mysql不存在的数据,redis直接缓存null,并设置过期时间(短时间)

缺点:随着不存在的数据缓存越多,redis内存也就越大

方案2:添加访问黑名单,当某个ip出现多次非法请求时,直接拉黑

缺点:攻击者可能会一直更换ip

方案3:使用布隆过滤器

  • 布隆过滤器其实就是一个白名单/黑名单的拦截器

白名单:把数据库查询的数据,同步到redis时,再把数据同步至布隆过滤器

缺点:mysql数据需要同步两份,一份到redis,一份到布隆过滤器

黑名单:初始化一个布隆过滤器,当存在非法请求时,把请求参数加入到黑名单,下次不允许查询redis合mysql

缺点:初始化的布隆过滤器中是没有数据的,也就意味着没有黑名单

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

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

相关文章

多线程冲突处理方法,锁

线程之间是可以共享进程的资源&#xff0c;比如代码段、堆空间、数据段、打开的文件等资源&#xff0c;但每个线程都有自己独立的栈空间。 那么问题就来了&#xff0c;多个线程如果竞争共享资源&#xff0c;如果不采取有效的措施&#xff0c;则会造成共享数据的混乱。 我们做…

STM32+收发器实现CAN和485总线

RS485总线是一种常见的(Recommended Standard)串行总线标准(485是它的标识号)&#xff0c;采用平衡发送与差分接收的方式&#xff0c;因此具有抑制共模干扰的能力。CAN是控制器局域网络(Controller Area Network, CAN)的简称&#xff0c;是一种能够实现分布式实时控制的串行通信…

推特爆火!揭晓大模型的未来何去何从

文 | 智商掉了一地巨大挑战 or 发展契机&#xff0c;ChatGPT 和 GPT-4 出现后&#xff0c;大模型的未来方向该何去何从&#xff1f;近期&#xff0c;自然语言处理领域的快速发展引起了广泛的关注&#xff0c;尤其是大型语言模型&#xff08;LLM&#xff09;的兴起已经推动了该领…

4.13、TCP通信流程

4.13、TCP通信流程1.TCP与UDP的区别&#xff08;传输层协议&#xff09;2.TCP通信流程①服务器端&#xff08;被动接受连接的角色&#xff09;②客户端&#xff08;主动发起连接&#xff09;1.TCP与UDP的区别&#xff08;传输层协议&#xff09; UDP:用户数据报协议&#xff0…

【Linux系统文件管理(cat,awk指令)和网络IP配置,广播地址,修改子网掩码以及ping网关地址】

文本操作实验 &#xff08;1&#xff09;使用cat&#xff0c;创建文件test1&#xff0c;输入"Line1"并且按下Ctrl-D保存文件。 创建text01.txt文件&#xff1a; cat > text01.txt 回车&#xff1b;输入自己想要输入的内容 键盘上面按下Ctrl-D就可以成功创建并保存…

【Java版oj】day24洗牌、MP3光标位置

目录 一、洗牌 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、MP3光标位置 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、洗牌 &#x…

西安石油大学C++上机实验 上机三:继承和派生程序设计(2学时)

上机三&#xff1a;继承和派生程序设计&#xff08;2学时&#xff09; 实验目的 了解继承在面向对象程序设计中的重要作用。 理解继承与派生的概念。 掌握通过继承派生出一个新类的方法。 掌握虚基类的作用和用法。 实验内容 P169&#xff1a;5.19, 5.22 上机内容 先设…

Windows 10 上使用 CMake GUI 编译 Krita 源代码并使用 MinGW 64 作为构建工具

krita系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文…

TCP和UDP(嵌入式学习)

TCP和UDPTCP&#xff08;即传输控制协议&#xff09;TCP连接建立(三次握手)TCP连接终止&#xff08;四次挥手&#xff09;UDP &#xff1a;用户数据报协议适用情况UDP 洪水UDP TCP 协议相同点TCP&#xff08;即传输控制协议&#xff09; 是一种面向连接的传输层协议&#xff0…

Vue ElementUI Axios 前后端案例(day01)

前言js 问js是什么&#xff0c;他有什么作用&#xff0c;与html和css的区别是什么 JavaScript&#xff08;简称JS&#xff09;是一种脚本语言&#xff0c;用于在网页上实现交互效果、动态效果和动态数据更新等功能。它是一种解释性语言&#xff0c;需要在浏览器中解释和执行。…

Linux中安装新版minio(centos7版本)

1. 背景需求 由于一些限制,在客户现场的Linux操作系统中,没有安装docker k8s等容器,无法直接使用镜像安装,而且客户要求只能在原始的操作系统中安装最新版的minio,(为什么需要安装最新版的minio,因为检测国网检测到之前版本的minio有漏洞,需要安装新版的minio). 2. 安装minio…

Direct3D 12——纹理——纹理缩小

纹理缩小(miniflcation )是纹理放大的逆运算。在缩小的过程中&#xff0c;大量纹素将被映射到少数纹理之 上 。例如&#xff0c;考虑下列情景&#xff1a;假设有一堵被256x256纹理所映射的墙壁&#xff0c;玩家的观察视角正紧盯着它&#xff0c;并 逐渐向后退却。在此过程中&am…

QML控件--Drawer

文章目录一、控件基本信息二、控件使用三、属性成员一、控件基本信息 Import Statement&#xff1a;import QtQuick.Controls 2.14 Since&#xff1a;Qt 5.7 Inherits&#xff1a;Popup 二、控件使用 Drawer&#xff1a;提供一个可以使用滑动手势打开和关闭的侧面板&#xff…

简述内网渗透中常用的隧道工具

frp 项目地址 https://github.com/fatedier/frp/ 简介 frp 是一个可用于内网穿透的高性能的反向代理应用&#xff0c;支持 tcp, udp 协议&#xff0c;为 http 和 https 应用协议提供了额外的能力&#xff0c;且尝试性支持了点对点穿透。 跨平台支持linux&#xff0c;win&a…

【cmake教程】find_path、find_library、find_program

目录 1、find_path 2、find_library 3、find_program find_path 参考文章&#xff1a;CMake中find_path的使用-CSDN博客 find_library 参考文章&#xff1a;CMake中find_library的使用 1、find_path find_path 一般用于在某个目录下查找一个或者多个头文件&#xff0c;命令…

ASP.NET Core - 依赖注入(一)

1. Ioc 与 DI Ioc 和DI 这两个词大家都应该比较熟悉&#xff0c;这两者已经在各种开发语言各种框架中普遍使用&#xff0c;成为框架中的一种基本设施了。 Ioc 是控制反转&#xff0c; Inversion of Control 的缩写&#xff0c;DI 是依赖注入&#xff0c;Inject Dependency 的…

WebSpider蓝蜘蛛网页抓取工具5.1用户手册

概述 关于网页抓取工具 本工具可以抓取互联网上的任何网页&#xff0c;包括需要登录后才能访问的页面。对抓取到页面内容进行解析&#xff0c;得到结构化的信息&#xff0c;比如&#xff1a;新闻标题、作者、来源、正文等。支持列表页的自动翻页抓取&#xff0c;支持正文页多页…

《Vue3实战》 第二章 创建项目和目录结构

1、创建项目 1.1、命令格式&#xff1a;vue create 项目名称 vue create vue3_example0011.2、运行项目 npm run serve1.2.1、增加run命令 启动时想修改命令&#xff0c;例如&#xff1a; npm run dev1、找到项目根路径下的package.json文件&#xff1b; 2、找到【scripts…

webgl-根据鼠标点击而移动

html <!DOCTYPE html> <head> <style> *{ margin: 0px; padding: 0px; } </style> </head> <body> <canvas id webgl> 您的浏览器不支持HTML5,请更换浏览器 </canvas> <script src"./main.js"></script&g…

DDoS攻击实验笔记

DoS&DDoS简介 DoS(Denial of Service)&#xff0c;拒绝服务攻击是通过一些方法影响服务的可用性&#xff0c;比如早期主要基于系统和应用程序的漏洞&#xff0c;只需要几个请求或数据包就能导致长时间的服务不可用&#xff0c;但易被入侵检测系统发现。 DDoS(Distributed D…