Redis缓存穿透-缓存雪崩-缓存击穿

什么叫缓存穿透?

模拟一个场景:

前端用户发送请求获取数据,后端首先会在缓存Redis中查询,如果能查到数据,则直接返回.如果缓存中查不到数据,则要去数据库查询,如果数据库有,将数据保存到Redis缓存中并且返回用户数据.如果数据库没有则返回null;

这个缓存穿透的问题就是这个返回的null上面,如果客户端恶意频繁的发起Redis不存在的Key,且数据库中也不存在的数据,返回永远是null.当洪流式的请求过来,给数据库造成极大压力,甚至压垮数据库.它永远越过Redis缓存而直接访问数据库,这个过程就是缓存穿透.

其实是个设计上的缺陷.

缓存穿透解决方案

业界比较成熟的一种解决方案:当越过缓存,且数据库没有该数据返回客户端null并且存到Redis,数据是null,给这个Key设置过期时间.这种方案一定程度上减少数据库频繁查询的压力.

实战过程

CREATE TABLE `item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) DEFAULT NULL COMMENT '商品编号',
  `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '商品名称',
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='商品信息表';

INSERT INTO `item` VALUES ('1', 'book_10010', 'Redis缓存穿透实战', '2019-03-17 17:21:16');

项目整体结构

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>redis1</artifactId><version>0.0.1-SNAPSHOT</version><name>redis1</name><description>Demo project for Spring Boot</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>3.0.3</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

启动类

@SpringBootApplication
@MapperScan({"com.example.redis1.mapper"})
public class Redis1Application {public static void main(String[] args) {SpringApplication.run(Redis1Application.class, args);}}

application.yml

server:port: 80
spring:application:name: redis-testredis:##redis 单机环境配置##将docker脚本部署的redis服务映射为宿主机ip##生产环境推荐使用阿里云高可用redis服务并设置密码host: 127.0.0.1port: 6379password:database: 0ssl: false##redis 集群环境配置#cluster:#  nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003#  commandTimeout: 5000datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://1111111:3306/redis-test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=GMT%2B8&useCursorFetch=trueusername: xxxxpassword: xxxxxxxx
mybatis:mapper-locations: classpath:mappers/*Mapper.xml  # 指定mapper文件位置type-aliases-package: com.example.redis1.pojoconfiguration:map-underscore-to-camel-case: true
logging:level:com.example.redis1.mapper: debug

数据库映射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.example.redis1.mapper.ItemMapper" ><resultMap id="BaseResultMap" type="com.example.redis1.pojo.Item" ><id column="id" property="id" jdbcType="INTEGER" /><result column="code" property="code" jdbcType="VARCHAR" /><result column="name" property="name" jdbcType="VARCHAR" /><result column="create_time" property="createTime" jdbcType="TIMESTAMP" /></resultMap><sql id="Base_Column_List" >id, code, name, create_time</sql><select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >select<include refid="Base_Column_List" />from itemwhere id = #{id,jdbcType=INTEGER}</select><delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >delete from itemwhere id = #{id,jdbcType=INTEGER}</delete><insert id="insert" parameterType="item" >insert into item (id, code, name,create_time)values (#{id,jdbcType=INTEGER}, #{code,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP})</insert><insert id="insertSelective" parameterType="item" >insert into item<trim prefix="(" suffix=")" suffixOverrides="," ><if test="id != null" >id,</if><if test="code != null" >code,</if><if test="name != null" >name,</if><if test="createTime != null" >create_time,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," ><if test="id != null" >#{id,jdbcType=INTEGER},</if><if test="code != null" >#{code,jdbcType=VARCHAR},</if><if test="name != null" >#{name,jdbcType=VARCHAR},</if><if test="createTime != null" >#{createTime,jdbcType=TIMESTAMP},</if></trim></insert><update id="updateByPrimaryKeySelective" parameterType="item" >update item<set ><if test="code != null" >code = #{code,jdbcType=VARCHAR},</if><if test="name != null" >name = #{name,jdbcType=VARCHAR},</if><if test="createTime != null" >create_time = #{createTime,jdbcType=TIMESTAMP},</if></set>where id = #{id,jdbcType=INTEGER}</update><update id="updateByPrimaryKey" parameterType="item" >update itemset code = #{code,jdbcType=VARCHAR},name = #{name,jdbcType=VARCHAR},create_time = #{createTime,jdbcType=TIMESTAMP}where id = #{id,jdbcType=INTEGER}</update><!--根据商品编码查询--><select id="selectByCode" resultType="item">select<include refid="Base_Column_List" />from itemwhere code = #{code}</select></mapper>

pojo

package com.example.redis1.pojo;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;import java.util.Date;@Data
public class Item {private Integer id;private String code;private String name;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")private Date createTime;}

mapper

package com.example.redis1.mapper;import com.example.redis1.pojo.Item;
import org.apache.ibatis.annotations.Param;public interface ItemMapper {int deleteByPrimaryKey(Integer id);int insert(Item record);int insertSelective(Item record);Item selectByPrimaryKey(Integer id);int updateByPrimaryKeySelective(Item record);int updateByPrimaryKey(Item record);Item selectByCode(@Param("code") String code);
}

service

package com.example.redis1.service;import com.example.redis1.mapper.ItemMapper;
import com.example.redis1.pojo.Item;
import com.fasterxml.jackson.databind.ObjectMapper;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;/*** 缓存穿透service* Created by Administrator on 2019/3/17.*/
@Service
public class CachePassService {private static final Logger log= LoggerFactory.getLogger(CachePassService.class);@Autowiredprivate ItemMapper itemMapper;@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate ObjectMapper objectMapper;private static final String keyPrefix="item:";/*** 获取商品详情-如果缓存有,则从缓存中获取;如果没有,则从数据库查询,并将查询结果塞入缓存中* @param itemCode* @return* @throws Exception*/public Item getItemInfo(String itemCode) throws Exception{Item item=null;final String key=keyPrefix+itemCode;ValueOperations valueOperations=redisTemplate.opsForValue();if (redisTemplate.hasKey(key)){log.info("---获取商品详情-缓存中存在该商品---商品编号为:{} ",itemCode);//从缓存中查询该商品详情Object res=valueOperations.get(key);if (res!=null&&!(res.equals(""))){item=objectMapper.readValue(res.toString(),Item.class);}}else{log.info("---获取商品详情-缓存中不存在该商品-从数据库中查询---商品编号为:{} ",itemCode);//从数据库中获取该商品详情item=itemMapper.selectByCode(itemCode);if (item!=null){valueOperations.set(key,objectMapper.writeValueAsString(item));}else{//过期失效时间TTL设置为30分钟-当然实际情况要根据实际业务决定valueOperations.set(key,"",30L, TimeUnit.MINUTES);}}return item;}
}

controller

package com.example.redis1.controller;import com.example.redis1.service.CachePassService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 缓存穿透实战* @Author:debug (SteadyJack)* @Date: 2019/3/17 18:33**/
@RestController
public class CachePassController {private static final Logger log= LoggerFactory.getLogger(CachePassController.class);private static final String prefix="cache/pass";@Autowiredprivate CachePassService cachePassService;/*** 获取热销商品信息* @param itemCode* @return*/@RequestMapping(value = prefix+"/item/info",method = RequestMethod.GET)public Map<String,Object> getItem(@RequestParam String itemCode){Map<String,Object> resMap=new HashMap<>();resMap.put("code",0);resMap.put("msg","成功");try {resMap.put("data",cachePassService.getItemInfo(itemCode));}catch (Exception e){resMap.put("code",-1);resMap.put("msg","失败"+e.getMessage());}return resMap;}
}

第一次访问

localhost/cache/pass/item/info?itemCode=book_10010

查看日志输出

用个数据库不存在的

localhost/cache/pass/item/info?itemCode=book_10012

后端的处理是将不存在的key存到redis并指定过期时间

其他典型问题介绍

缓存雪崩:指的的某个时间点,缓存中的Key集体发生过期失效,导致大量查询的请求落到数据库上,导致数据库负载过高,压力暴增的现象

解决方案:设置错开不同的过期时间

缓存击穿:指缓存中某个频繁被访问的Key(热点Key),突然过期时间到了失效了,持续的高并发访问瞬间就像击破缓存一样瞬间到达数据库。

解决办法:设置热点Key永不过期

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

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

相关文章

uboot分区介绍

RK平台的U-Boot支持两种分区表 RK paramter格式&#xff08;旧&#xff09;和 标准GPT格式&#xff08;新&#xff09;&#xff0c;当机器上同时存在 两种分区表时&#xff0c;优先使用GPT分区表。无论是 GPT 还是 RK parameter&#xff0c;烧写用的分区表文件都叫parameter.t…

linux安装todesk

xunilToDesk远程桌面软件-免费安全流畅的远程连接电脑手机ToDesk远程控制软件是一款稳定流畅的远程控制电脑手机连接软件,可远程桌面办公,远程协助运维.采用端对端加密,让每一次远程访问都安全可靠。https://www.todesk.com/linux.htmlToDesk远程控制软件是一款稳定流畅的远程控…

设备监察系统(Facilities Monitoring System)详解

设备监察系统&#xff08;Facilities Monitoring System&#xff0c;简称FMS&#xff09;是一个综合性的设施管理系统&#xff0c;主要用于监控、管理和优化各种设备、系统和设施的性能。这个系统可以应用于各种行业&#xff0c;如建筑、制造业、能源、交通等&#xff0c;以实现…

【考研数学】李林《880》vs 李永乐《660》完美使用搭配

没有说谁一定好&#xff0c;只有适不适合自身情况&#xff0c;针对自身弱点选择性价比才最高。 两者侧重点不同&#xff0c;660适合强化前期&#xff0c;弥补基础的不足&#xff0c;880适合强化后期&#xff0c;题型全面&#xff0c;提高我们对综合运用知识的能力。 选择习题…

蓝桥杯2023年-买瓜(dfs,类型转换同样耗时)

题目描述 小蓝正在一个瓜摊上买瓜。瓜摊上共有 n 个瓜&#xff0c;每个瓜的重量为 Ai 。 小蓝刀功了得&#xff0c;他可以把任何瓜劈成完全等重的两份&#xff0c;不过每个瓜只能劈一刀。 小蓝希望买到的瓜的重量的和恰好为 m 。 请问小蓝至少要劈多少个瓜才能买到重量恰好…

某IC交易网 js逆向解析学习【2024/03/7】更新

文章目录 文章目录 文章目录前言网址确定加密方式ICNet[sct] 加密查看bbc加密上半部分下半部分结果前言 可以关注我哟,一起学习,主页有更多练习例子 如果哪个练习我没有写清楚,可以留言我会补充 如果有加密的网站可以留言发给我,一起学习共享学习路程 如侵权,联系我删除 …

计算机组成原理之机器:存储器之辅助存储器

计算机组成原理之机器&#xff1a;存储器之辅助存储器 笔记来源&#xff1a;哈尔滨工业大学计算机组成原理&#xff08;哈工大刘宏伟&#xff09; Chapter3&#xff1a;存储器之辅助存储器 3.1 概述 3.2 磁记录原理 通不同方向电流时磁化方向不同&#xff0c;由此区分写入…

BUUCTF:[MRCTF2020]ezmisc

题目地址&#xff1a;https://buuoj.cn/challenges#[MRCTF2020]ezmisc 下载附件打开是一张照片&#xff1a; 放到kali中发现crc校验错误&#xff0c;修改照片宽高&#xff1a; 保存即可发现flag flag为&#xff1a; flag{1ts_vEryyyyyy_ez!}

报错:ModuleNotFoundError: No module named ‘tensorrt’

写在前面 我安装了tensorRT,在运行它自带的模型都没问题。 但是在代码中import tensorrt就报错&#xff1a; ModuleNotFoundError: No module named ‘tensorrt’。 网上搜了一大堆&#xff0c;发现是没有在自己的python环境下安装。 所以特意写这篇文章记录一下。 在进行下一…

给你一个二叉树的根节点 root , 检查它是否轴对称。

我们可以实现这样一个递归函数&#xff0c;通过「同步移动」两个指针的方法来遍历这棵树&#xff0c;p 指针和 q指针一开始都指向这棵树的根&#xff0c;随后 p右移时&#xff0c;q 左移&#xff0c;p 左移时&#xff0c;q右移。每次检查当前 ppp 和 qqq 节点的值是否相等&…

PAT部分题目相关知识点——python

python中的整除 在Python中&#xff0c;整除&#xff08;也称为地板除&#xff09;可以使用**//**运算符来实现。当使用//运算符时&#xff0c;结果将是一个整数&#xff0c;它表示除法运算的整数部分&#xff0c;舍去任何小数部分。 示例&#xff1a; # 使用整除运算符 // …

Flink-Java报错之org.apache.flink.api.common.functions.InvalidTypesException

在启动Flink作业时报错。可以看到是在实现FlatMapFunction时InvalidTypesException类型无效。提示使用匿名类或者明确指定类型来解决。An easy workaround is to use an (anonymous) class instead that implements the org.apache.flink.api.common.functions.FlatMapFunction…

webUI自动化测试框架

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

什么场景只能用HTTP,不能用RPC?

1、在异构系统(跨语言和跨平台)&#xff0c;HTTP具有更好的兼容性&#xff0c;因为HTTP是一种通用的协议&#xff0c;几乎所有的编程语言和操作系统都支持HTTP协议&#xff0c;而不是所有的编程语言和操作系统都支持相同的RPC协议。 2、RPC适合用在企业内部&#xff0c;要求使用…

C#中实现接口的一些小知识(C#用abstract或virtual来实现接口成员)

文章目录 不可用的修饰可用的修饰非抽象类实现接口抽象类实现接口抽象类与接口方法同名时一同实现 不可用的修饰 在C#中实现接口时&#xff0c;我们不能直接使用static或const来实现接口成员&#xff0c;因为接口中的成员默认都是实例成员&#xff0c;并且它们表示一种契约&am…

c++的STL(2)-- vector容器

目录 1. 默认构造 代码: 相关知识点: 2. 有参构造函数 以及 使用{}初始化对象 代码: 相关知识点: 3. vector容器在尾部添加和删除元素 代码: 使用push_back()和pop_back()进行尾部元素的添加和删除 相关知识点: 代码: 使用emplace_back在尾部添…

1. Typescript入门

TS 基础 Typescript 在线编译平台 基础类型 boolean、number 和 string 类型 boolean let isHandsome: boolean true赋值与定义的不一致&#xff0c;会报错&#xff0c;静态类型语言的优势就体现出来了&#xff0c;可以帮助我们提前发现代码中的错误。 let isHandsome: …

人工蜂群算法

人工蜂群算法 人工蜂群算法&#xff08;Artificial Bee Colony Optimization,ABC&#xff09;是一种基于蜜蜂觅食行为的优化算法&#xff0c;由土耳其学者Karaboga于2005年提出&#xff0c;算法模拟蜜蜂的采蜜行为对优化问题进行求解。 算法原理 ABC算法的核心思想是将优化问…

ThreadLocal 与 synchronized 区别

我的理解 目的都是为了一个大前提:操作内容的线程安全。 任务不同&#xff1a;synchronized 解决的是多线程下线程操作权限的问题&#xff0c;以及原子性的保证。通过对锁的竞争&#xff0c;达到对资源的访问有序。 ThreadLocal是解决的事多线程下资源的隔离问题&#xff0c;即…

Mysql学习笔记之事务详解(读未提交、读以提交、可重复读、串行化读)

在这个博主的基础上&#xff0c;增加两种情况的对比&#xff1a;https://blog.csdn.net/llllllkkkkkooooo/article/details/108068919 可重复读中幻读现象&#xff08;未使用MVCC&#xff09; 设置可重复读的隔离级别 set global transaction isolation level repeatable read…