Redis缓存常见问题之预热、雪崩、击穿、穿透

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • Redis缓存常见问题之预热、雪崩、击穿、穿透
    • 缓存预热
    • 缓存雪崩
      • 发生
      • 预防+解决
    • 缓存穿透
      • 解决
      • 方案一:空对象缓存或者缺省值
      • 方案2:Google布隆过滤器Guava解决缓存穿透
        • 白名单过滤器
        • Coding实战
        • GuavaBloomFilterService
        • 说明
      • 思考问题:黑名单的使用
    • 缓存穿透
      • 危害
      • 解决
      • 案例
        • coding
        • Bug和隐患说明
      • 进一步解决
        • 差异失效时间

Redis缓存常见问题之预热、雪崩、击穿、穿透

缓存预热

假设mysql有100条新数据,redis无

1 比较懒,什么都不做,之前对mysql做了数据新增,利用redis的回写机制,让它逐步实现100条新增记录的同步,最好提前晚上部署发布版本的时候,由自己人提前做一次,让redis同步了,不要把这个问题留给客户。

2 @PostConstruct初始化白名单数据,微服务启动的时候进行初始化

缓存雪崩

发生

redis主机挂了,Redis全盘崩溃,偏硬件运维

redis中有大量key同时国企大面积失效,偏软件开发

预防+解决

  • redis中key设置为永不过期 or 过期时间错开
  • redis缓存集群实现高可用
  • 多级缓存结合预防雪崩:本地缓存 + redis缓存
  • 服务降级:Sentinel限流降级策略

缓存穿透

请求去查询一条记录,先插redis无,后查mysql无,都查询不到该条记录,但是请求每次都会打到数据库上面去,导致后台数据库压力暴增,这种现象我们成为缓存穿透,这个redis变成了一个摆设。

简单说就是,本来无一物,两库都没有,机不再redis缓存库,也不再mysql,数据库存在被多次暴击风险。

在这里插入图片描述

解决

在这里插入图片描述

方案一:空对象缓存或者缺省值

第一种解决方案,回写增强

如果发生了缓存穿透,我们可以针对要查询的数据,在Redis里存一个和业务部门商量后确定的缺省值(比如,零、负数、defaultNull等)。

比如,键uid:abcdxxx,值defaultNull作为案例的key和value

先去redis查键uid:abcdxxx没有,再去mysql查没有获得 ,这就发生了一次穿透现象。

but,可以增强回写机制

mysql也查不到的话也让redis存入刚刚查不到的key并保护mysql。

第一次来查询uid:abcdxxx,redis和mysql都没有,返回null给调用者,但是增强回写后第二次来查uid:abcdxxx,此时redis就有值了。

可以直接从Redis中读取default缺省值返回给业务应用程序,避免了把大量请求发送给mysql处理,打爆mysql。

但是,此方法架不住黑客的恶意攻击,有缺陷…,只能解决key相同的情况

比如黑客或者恶意攻击:

黑客会对你的系统进行攻击,拿一个不存在的id去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕机。

key相同打你系统:第一次打到mysql,空对象缓存后第二次返回defaultNull缺省值,避免mysql被攻击,不用再到数据库中去走一圈了。

key不同打你系统:由于存在空对象缓存和缓存回写,redis中的无关紧要的key也会越写越多(记得设置redis的过期时间)

方案2:Google布隆过滤器Guava解决缓存穿透

白名单过滤器

在这里插入图片描述

存在误判问题,但是概率下可以接受,缺点是不能从布隆过滤器删除

全部合法的key都需要放入Guava版布隆过滤器 + redis里面,不然数据就返回null

Coding实战
		<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency>
@Testpublic void testGuavaWithBloomFilter(){// 创建布隆过滤器对象BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), 100);// 判断指定元素是否存在System.out.println(filter.mightContain(1));System.out.println(filter.mightContain(2));// 将元素添加进布隆过滤器filter.put(1);filter.put(2);System.out.println(filter.mightContain(1));System.out.println(filter.mightContain(2));}

对比redis的实现,其解耦性做的非常的好,不需要与redis交互

GuavaBloomFilterService
@Service
@Slf4j
public class GuavaBloomFilterService{public static final int _1W = 10000;//布隆过滤器里预计要插入多少数据public static int size = 100 * _1W;//误判率,它越小误判的个数也就越少(思考,是不是可以设置的无限小,没有误判岂不更好)//fpp the desired false positive probabilitypublic static double fpp = 0.03;// 构建布隆过滤器private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size,fpp);public void guavaBloomFilter(){ //1 先往布隆过滤器里面插入100万的样本数据for (int i = 1; i <=size; i++) {bloomFilter.put(i);}//故意取10万个不在过滤器里的值,看看有多少个会被认为在过滤器里List<Integer> list = new ArrayList<>(10 * _1W);for (int i = size+1; i <= size + (10 *_1W); i++) {if (bloomFilter.mightContain(i)) {log.info("被误判了:{}",i);list.add(i);}}log.info("误判的总数量::{}",list.size());}
}

取样本100W数据,查查不在100W范围内,其它10W数据是否存在?

现在总共有10万数据是不存在的,误判了3033次,

原始样本:100W

不存在数据:1000001W—1100000W

在这里插入图片描述

前面提到了误判率,通过debug发现

在这里插入图片描述

在这里插入图片描述

也就是说误判率越小,要求精度越高,需要用的资源也就越多,hash函数也就越来越多。

当设置为非常小的值的时候

在这里插入图片描述

缺点是数据量太大,加载很慢,资源消耗很大

加入说什么都不写,默认就是0.03

在这里插入图片描述

说明

在这里插入图片描述

思考问题:黑名单的使用

在这里插入图片描述

缓存穿透

大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都达到数据库上面去

简单地说就是热点key突然失效了,暴打mysql

危害

会造成某一时刻数据库请求量过大,压力暴增。

一般技术部门需要知道热点key时那些个?做到心里有数防止被击穿

解决

热点key失效:时间到了自然清楚但还被访问到,delete掉的key,刚巧又被访问

方案一:差异失效时间,对于访问频繁的热点key,干脆就不设置过期时间

方案二:互斥更新,采用双检加锁策略

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

在这里插入图片描述

案例

天猫聚划算功能 + 防止缓存击穿

在这里插入图片描述

存在问题就是热点key突然失效导致了缓存击穿

步骤说明
1100%高并发,绝对不可以用mysql实现
2先把mysql里面参加活动的数据抽取进redis,一般采用定时器扫描来决定上线活动还是下线取消。
3支持分页功能,一页20条记录

在这里插入图片描述

coding
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "聚划算活动producet信息")
public class Product
{//产品IDprivate Long id;//产品名称private String name;//产品价格private Integer price;//产品详情private String detail;
}
// 采用定时器将参与聚划算活动的特价商品新增进入redis@Service
@Slf4j
public class JHSTaskService
{public  static final String JHS_KEY="jhs";public  static final String JHS_KEY_A="jhs:a";public  static final String JHS_KEY_B="jhs:b";@Autowiredprivate RedisTemplate redisTemplate;/*** 偷个懒不加mybatis了,模拟从数据库读取100件特价商品,用于加载到聚划算的页面中* @return*/private List<Product> getProductsFromMysql() {List<Product> list=new ArrayList<>();for (int i = 1; i <=20; i++) {Random rand = new Random();int id= rand.nextInt(10000);Product obj=new Product((long) id,"product"+i,i,"detail");list.add(obj);}return list;}@PostConstructpublic void initJHS(){log.info("启动定时器淘宝聚划算功能模拟.........."+ DateUtil.now());new Thread(() -> {//模拟定时器一个后台任务,定时把数据库的特价商品,刷新到redis中while (true){//模拟从数据库读取100件特价商品,用于加载到聚划算的页面中List<Product> list=this.getProductsFromMysql();//采用redis list数据结构的lpush来实现存储this.redisTemplate.delete(JHS_KEY);//lpush命令this.redisTemplate.opsForList().leftPushAll(JHS_KEY,list);//间隔一分钟 执行一遍,模拟聚划算每3天刷新一批次参加活动try { TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("runJhs定时刷新..............");}},"t1").start();}
}

至此一个聚划算的基本功能就算是实现了,但是在高并发的场景下会有什么经典生产问题呢?

Bug和隐患说明

在这里插入图片描述

两条命令的原子性还是其次,主要是防止热key突然失效暴击mysql打爆系统

进一步解决

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

在这里插入图片描述

差异失效时间

在这里插入图片描述

两份缓存,过期时间不一致

@Service
@Slf4j
public class JHSTaskService
{public  static final String JHS_KEY="jhs";public  static final String JHS_KEY_A="jhs:a";public  static final String JHS_KEY_B="jhs:b";@Autowiredprivate RedisTemplate redisTemplate;/*** 偷个懒不加mybatis了,模拟从数据库读取100件特价商品,用于加载到聚划算的页面中* @return*/private List<Product> getProductsFromMysql() {List<Product> list=new ArrayList<>();for (int i = 1; i <=20; i++) {Random rand = new Random();int id= rand.nextInt(10000);Product obj=new Product((long) id,"product"+i,i,"detail");list.add(obj);}return list;}//@PostConstructpublic void initJHS(){log.info("启动定时器淘宝聚划算功能模拟.........."+ DateUtil.now());new Thread(() -> {//模拟定时器,定时把数据库的特价商品,刷新到redis中while (true){//模拟从数据库读取100件特价商品,用于加载到聚划算的页面中List<Product> list=this.getProductsFromMysql();//采用redis list数据结构的lpush来实现存储this.redisTemplate.delete(JHS_KEY);//lpush命令this.redisTemplate.opsForList().leftPushAll(JHS_KEY,list);//间隔一分钟 执行一遍try { TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("runJhs定时刷新..............");}},"t1").start();}@PostConstructpublic void initJHSAB(){log.info("启动AB定时器计划任务淘宝聚划算功能模拟.........."+DateUtil.now());new Thread(() -> {//模拟定时器,定时把数据库的特价商品,刷新到redis中while (true){//模拟从数据库读取100件特价商品,用于加载到聚划算的页面中List<Product> list=this.getProductsFromMysql();//先更新B缓存this.redisTemplate.delete(JHS_KEY_B);this.redisTemplate.opsForList().leftPushAll(JHS_KEY_B,list);this.redisTemplate.expire(JHS_KEY_B,20L,TimeUnit.DAYS);//再更新A缓存this.redisTemplate.delete(JHS_KEY_A);this.redisTemplate.opsForList().leftPushAll(JHS_KEY_A,list);this.redisTemplate.expire(JHS_KEY_A,15L,TimeUnit.DAYS);//间隔一分钟 执行一遍try { TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("runJhs定时刷新双缓存AB两层..............");}},"t1").start();}
}
@RestController
@Slf4j
@Api(tags = "聚划算商品列表接口")
public class JHSProductController
{public  static final String JHS_KEY="jhs";public  static final String JHS_KEY_A="jhs:a";public  static final String JHS_KEY_B="jhs:b";@Autowiredprivate RedisTemplate redisTemplate;/*** 分页查询:在高并发的情况下,只能走redis查询,走db的话必定会把db打垮* @param page* @param size* @return*/@RequestMapping(value = "/pruduct/find",method = RequestMethod.GET)@ApiOperation("按照分页和每页显示容量,点击查看")public List<Product> find(int page, int size) {List<Product> list=null;long start = (page - 1) * size;long end = start + size - 1;try {//采用redis list数据结构的lrange命令实现分页查询list = this.redisTemplate.opsForList().range(JHS_KEY, start, end);if (CollectionUtils.isEmpty(list)) {//TODO 走DB查询}log.info("查询结果:{}", list);} catch (Exception ex) {//这里的异常,一般是redis瘫痪 ,或 redis网络timeoutlog.error("exception:", ex);//TODO 走DB查询}return list;}@RequestMapping(value = "/pruduct/findab",method = RequestMethod.GET)@ApiOperation("防止热点key突然失效,AB双缓存架构")public List<Product> findAB(int page, int size) {List<Product> list=null;long start = (page - 1) * size;long end = start + size - 1;try {//采用redis list数据结构的lrange命令实现分页查询list = this.redisTemplate.opsForList().range(JHS_KEY_A, start, end);if (CollectionUtils.isEmpty(list)) {log.info("=========A缓存已经失效了,记得人工修补,B缓存自动延续5天");//用户先查询缓存A(上面的代码),如果缓存A查询不到(例如,更新缓存的时候删除了),再查询缓存Bthis.redisTemplate.opsForList().range(JHS_KEY_B, start, end);//TODO 走DB查询}log.info("查询结果:{}", list);} catch (Exception ex) {//这里的异常,一般是redis瘫痪 ,或 redis网络timeoutlog.error("exception:", ex);//TODO 走DB查询}return list;}
}

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

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

相关文章

MongoDB数据库本地部署并结合内网穿透实现navicat公网访问

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…

处理etcd源码包编译异常

1、下载etcd包&#xff0c;执行go build报异常&#xff1a; client\v2\example_keys_test.go:1:1: expected package, found . client\v3\example_auth_test.go:1:1: expected package, found . client\v3\concurrency\example_election_test.go:1:1: expected package, found…

大语言模型说明书

在浩瀚的信息宇宙中&#xff0c;大语言模型如同一颗璀璨的星星正在熠熠生辉。21世纪以来&#xff0c;人工智能可谓是飞速发展&#xff0c;从简单的神经网络到大语言模型、生成式AI&#xff0c;这并非仅仅是一种技术的进步&#xff0c;更是人类智慧的飞跃。大语言模型不仅仅是语…

CGAL的3D Alpha Shapes

假设我们给定一个二维或三维的点集S&#xff0c;我们希望得到类似“这些点形成的形状”的东西。这是一个相当模糊的概念&#xff0c;可能有许多可能的解释&#xff0c;阿尔法形状就是其中之一。阿尔法形状可用于从密集的无组织数据点集进行形状重建。事实上&#xff0c;阿尔法形…

2023年最新版的linux运维面试题(二)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 目录 写在前面 11. LVS三种负载均衡模式的比较 12…

需求分析工程师岗位的职责描述(合集)

需求分析工程师岗位的职责描述1 职责&#xff1a; 1&#xff0c;负责需求调研&#xff0c;对需求进行分析&#xff0c;编写解决方案、需求规格说明书等 2&#xff0c;根据需求制作原型&#xff0c;并负责原型展示以及客户沟通等工作 3&#xff0c;负责向技术团队精确地传达业务…

数据挖掘-11-利用python进行信用卡欺诈检测(包含数据代码)

文章目录 0. 数据代码下载1. 项目介绍1.1 背景描述1.2 常见信用卡欺诈使用的情况有&#xff1a;1.3 数据描述a. 数据集内容b. 属性描述c. 注意 2. 提出问题3. 数据预处理3.1 加载数据3.2 查看数据类型&#xff0c;是否需要做数据转换处理3.3 对数据进行简单的统计&#xff0c;检…

跨平台Markdown编辑软件Typora mac功能介绍

Typora mac是一款跨平台的Markdown编辑器&#xff0c;支持Windows、MacOS和Linux操作系统。它具有实时预览功能&#xff0c;能够自动将Markdown文本转换为漂亮的排版效果&#xff0c;让用户专注于写作内容而不必关心格式调整。Typora Mac版除了支持常见的Markdown语法外&#x…

线程的同步与互斥

抢票的例子 竞争过程 进程A被切走 进程B被切走 结论&#xff1a; 互斥 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); mutex: 指向要初始化的互斥锁的指针。attr: 用于设置互斥锁属性的指针&#xff0c;通常可以传入 NULL 以使用默认属性…

react 路由v6

这里是区别&#xff1a;V5 vs V6 这里是官网&#xff1a;可以查看更多高级属性 一、基本使用&#xff1a; 1、配置文件 src/routes/index import React from "react";const Home React.lazy(() > import("../Pages/Home")); const About React.laz…

Spring之国际化:i18n

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

【强化学习】PPO:近端策略优化算法

近端策略优化算法 《Proximal Policy Optimization Algorithms》 论文地址&#xff1a;https://arxiv.org/pdf/1707.06347.pdf 一、 置信域方法(Trust Region Methods) ​ 设 π θ o l d \pi_{\theta_{old}} πθold​​是先前参数为 θ o l d \theta_{old} θold​的策略网…

Java@RequestParam注解和@RequestBody注解接收参数

目录 Java后端接收数据 第一章、后端不写任何注解情况下接收参数1.1&#xff09;后端不写注解postman发出get请求1.2&#xff09;后端不写注解postman发出post请求 第二章、后端写RequestParam注解接收参数2.1&#xff09;postman发出post请求2.2&#xff09;postman发出get请求…

docker-compose 安装Sonar并集成gitlab

文章目录 1. 前置条件2. 编写docker-compose-sonar.yml文件3. 集成 gitlab4. Sonar Login with GitLab 1. 前置条件 安装docker-compose 安装docker 创建容器运行的特有网络 创建挂载目录 2. 编写docker-compose-sonar.yml文件 version: "3" services:sonar-postgre…

【计算机网络】网络层——IP协议

目录 一. 基本概念 二. 协议报文格式 三. 网段划分 1. 第一次划分 2. CIDR方案 3. 特殊的IP地址 四. IP地址不足 1. 私有IP和公网IP 2. DHCP协议 3. 路由器 4. NAT技术 内网穿透(NAT穿透) 五. 路由转发 路由表生成算法 结束语 一. 基本概念 IP指网络互连协议…

android内存管理机制概览

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、相关概念3.1 垃圾回收3.2 应用内存的分配与回…

Linux与Bash 编程——Linux文件处理命令-L1

目录&#xff1a; linux系统与shell环境准备 Linux系统简介操作系统简史Linux的发行版&#xff1a;Linux与Windows比较&#xff1a;Linux安装安装包下载Linux的访问方式远程登录方式远程登录软件&#xff1a;mobaxterm的使用&#xff1a;使用电脑命令行连接&#xff1a;sshd的…

一篇讲透:箭头函数、普通函数有什么区别

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 什么是箭头函数 箭头函数和普通函数的区别 更简洁的语法 箭头函数…

10-让Java性能提升的JIT深度剖析

文章目录 JVM的语言无关性解释执行与JITC1、C2与Graal编译器C1编译器C2编译器 分层编译(了解即可)热点代码热点探测方法调用计数器回边计数器 编译优化技术方法内联锁消除标量替换逃逸分析技术逃逸分析的原理逃逸分析 JVM的语言无关性 跨语言&#xff08;语言无关性&#xff0…

OpenHarmony之内核层解析~

OpenHarmony简介 技术架构 OpenHarmony整体遵从分层设计&#xff0c;从下向上依次为&#xff1a;内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 组件”逐级展开&#xff0c;在多设备部署场景下&#xff0c;支持根据实际需求裁剪某些非必要的组件…