Redis中的缓存设计

缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常处于容错的考虑,如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。造成缓存穿透的基本原因有两个:

  • 1.自身业务或者数据出现问题
  • 2.一些恶意攻击、爬虫等造成大量空命中。

解决方案

1.缓存空对象

String get(String key) {// 从缓存中获取数据String cacheValue = cache.get(key);// 缓存为空if (StringUtils.isBlank(cacheValue)) {// 从存储中获取String storageValue = storage.get(key);cache.set(key, storageValue);// 如果存储数据为空,需要设置一个过期时间(300秒)if (storageValue == null) {cache.expire(key, 64 * 5);}return storageValue;} else {// 缓存非空return cacheValue;}
}

2.布隆过滤器

对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在
时,这个值可能不存在;当它说不存在时,那就肯定不存在.布隆过滤器就是一个大型的位数组和几个不一样的无偏hash函数。所谓无偏就是能够把元素的hash值算的比较均匀。向布隆过滤器中添加key时,会使用多个hash函数都会算得一个不同的位置。再把位数组的这几个位置都置为1,就完成了add操作。向布隆过滤器询问key是否存在时,跟add一样,也会把hash的几个位置都算出来,看看位数组中这几个位置是否都为1,只要有一个位为0,那么说明布隆过滤器中这个key不存在。如果都是1,这并不能说明这个key就一定存在,只是极有可能存在,因为这些位置为1可能是因为其他的key存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会很低。这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景,
代码维护较为复杂,但是缓存空间占用很少
在这里插入图片描述

示例

布隆过滤器使用示例,需要引入Redisson依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.5</version></dependency>
// 初始化布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
// 初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L, 0.03);// 把所有数据存入布隆过滤器
void init() {for (String key : keys) {bloomFilter.put(key);}
}String get(String key) {// 从布隆过滤器这一级缓存判断下key是否存在Boolean exist = bloomFilter.contains(key);if (!exist) {return "";}// 从缓存中获取数据String cacheValue = cache.get(key);// 缓存为空if (StringUtils.isBlank(cacheValue)) {// 从存储中获取String storageValue = storage.get(key);cache.set(key, storageValue);// 如果存储数据为空,需要设置一个过期时间(300秒)if (storageValue == null) {cache.expire(key, 64 * 5);}return storageValue;} else {// 缓存非空return cacheValue;}
}

使用布隆过滤器需要把所有数据提前放入布隆过滤器,并且在增加数据时也要往布隆过滤器里放,布隆过滤器缓存过滤伪代码:
注意:布隆过滤器不能删除数据,如果要删除得重新初始化数据

缓存失效(击穿)

由于大批量缓存存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同时间。

String get(String key) {// 从缓存中获取数据String cacheValue = cache.get(key);// 缓存为空if (StringUtils.isBlank(cacheValue)) {// 从存储中获取String storageValue = storage.get(key);cache.set(key, storageValue);// 设置一个过期时间(300~600之间的一个随机数)int expireTime = new Random().nextInt(300) + 300;if (storageValue == null) {cache.expire(key, expireTime);}return storageValue;} else {// 缓存非空return cacheValue;}
}

缓存雪崩

缓存雪崩指的是缓存曾支撑不住或宕掉后,流量会像奔逃的野牛一样,打向后端存储层。由于缓存层承载着大量请求,有效地保护了存储层,到那时如果缓存层由于某些原因不能提供服务(比如超大并发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降),于是大量请求都会打到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。

解决方案

预防和解决缓存雪崩问题,可以从三个方面着手:

  • 1.保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster
  • 2.依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、控制或是错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取
  • 3.提前演练。在项目上线前,演练缓存层宕机后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定

热点缓存key重建优化

开发人员使用"缓存+过期时间"的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:

  • 1.当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大
  • 2.重建缓存不能在短时间内完成,可能是一个复杂计算。例如复杂的SQL、多次IO、多个依赖等

在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。要解决这个问题主要就是要避免大量线程同时重建缓存。我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

String get(String key) {// 从Redis中获取数据String value = redis.get(key);// 如果value为空,则开始重构缓存if (value == null) {String mutexKey = "mutex:key:" + key;if (redis.set(mutexKey, "1", "ex 180", "nx") {// 从数据源获取数据value = db.get(key);// 回写Redis,并设置过期时间redis.setex(key, timeout, value);} else {// 其他线程休息50毫秒重试Thread.sleep(50);return get(key);}  }return value;}

缓存与数据库双写不一致

在这里插入图片描述
在这里插入图片描述

解决方案

  • 1.对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
  • 2.就算并发很高,如果业务上能容忍短时间内的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求
  • 3.如果不能容忍缓存数据不一致,可以通过加分布式读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁
  • 4.也可以阿里开源的canal通过监听数据库的binlog日志即时地去修改缓存,但是引入了新地中间件,增加了系统地复杂度
    在这里插入图片描述

总结:

以上我们针对地都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那就没必要加缓存了,可以直接操作数据库。当然,如果数据库扛不住压力,还可以把缓存作为数据读写的主存储,异步将数据同步到数据库,数据库只是作为数据的备份。

放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性

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

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

相关文章

mysql5.7离线安装 windows

windows上离线安装mysql5.7 下载安装包 去官网下载对应版本的mysql官网 点击archives,接着选择自己要下载的版本&#xff0c;选择windows系统&#xff0c;并根据自己电脑的位数选择相应的版本【找到“此电脑”&#xff0c;鼠标右击&#xff0c;出来下拉框&#xff0c;选择“属性…

【django framework】ModelSerializer+GenericAPIView接口数据流

GenericAPIView数据从序列化到最终返回响应的数据流 // 以ModelSerializergenerics.CreateAPIView为例 程序终归是为了处理数据&#xff0c;怎么处理&#xff0c;以怎样的顺序和方法去处理&#xff0c;就涉及到了具体的业务流程。当我们是用了一个牛掰的框架&#xff0c;发现原…

考察c语言关键字

C语言——关键字 1.问题&#xff1a;简述goto语句的作用 答&#xff1a;无条件跳转 具体来说&#xff0c;其作用在于允许程序在执行时无条件地跳转到指定的标签位置&#xff0c;并从该标签位置继续执行。通过goto语句&#xff0c;可以实现程序流程的无条件转移&#xff0c;使得…

使用PWM实现呼吸灯功能

CC表示的意思位捕获比较&#xff0c;CCR表示的是捕获比较寄存器 占空比等效于PWM模拟出来的电压的多少&#xff0c;占空比越大等效出的模拟电压越趋近于高电平&#xff0c;占空比越小等效出来的模拟电压越趋近于低电平&#xff0c;分辨率表示的是占空比变化的精细程度&#xf…

离线安装docker、docker-compose、Mysql镜像

离线安装docker docker-compose mysql镜像 一、下载docker docker-compose mysql 镜像文件 1、首先下载docker镜像 博主所用文件版本号&#xff1a; docker-23.0.6.tgz 下载docker 地址 &#xff1a;https://blog.csdn.net/xiaohanshasha/article/details/135489623?spm1001…

【Hadoop大数据技术】——MapReduce经典案例实战(倒排索引、数据去重、TopN)

&#x1f4d6; 前言&#xff1a;MapReduce是一种分布式并行编程模型&#xff0c;是Hadoop核心子项目之一。实验前需确保搭建好Hadoop 3.3.5环境、安装好Eclipse IDE &#x1f50e; 【Hadoop大数据技术】——Hadoop概述与搭建环境&#xff08;学习笔记&#xff09; 目录 &#…

网络安全,硬防迪云

要减少被攻击的频率&#xff0c;游戏开发者可以采取以下措施&#xff1a; 1. 强化安全措施&#xff1a;确保游戏服务器和用户数据的安全性&#xff0c;加密网络传输&#xff0c;防止黑客攻击和数据泄露。 2. 更新和修复漏洞&#xff1a;定期检查游戏代码和服务器&#xff0c;…

Java学习笔记(14)

常用API Java已经写好的各种功能的java类 Math Final修饰&#xff0c;不能被继承 因为是静态static的&#xff0c;所以使用方法不用创建对象&#xff0c;使用里面的方法直接 math.方法名 就行 常用方法 Abs,ceil,floor,round,max,minm,pow,sqrt,cbrt,random Abs要注意参数的…

【汇编】#5 80x86指令系统其一(数据传送与算术)

文章目录 一、数据传送指令1. 通用数据传送指令1.1 MOV传送指令tips:MOV指令几条特殊规定 1.2 XCHG交换指令1.3 进栈指令PUSH1.4 出栈指令POP1.5 所有寄存器进出栈指令PUSHA/POPAtips:SP特别处理 2. 累加器专用传送指令2.1 输入指令IN2.2 OUT输出指令2.3 IO端口与8086CPU通讯关…

【Linux-网络编程】

Linux-网络编程 ■ 网络结构■ C/S结构■ B/S结构 ■ 网络模型■ OSI七层模型■ TCP/IP四层模型 ■ TCP■ TCP通信流程■ TCP三次握手■ TCP四次挥手 ■ 套接字&#xff1a;socket 主机IP 主机上的进程&#xff08;端口号&#xff09;■ TCP传输文件 ■ 网络结构 ■ C/S结构…

高亮页面任意元素,轻松完成用户引导 | 开源日报 No.201

kamranahmedse/driver.js Stars: 20.1k License: MIT driver.js 是一个轻量级、无依赖的纯 JavaScript 引擎&#xff0c;用于引导用户在页面上聚焦。该项目解决了如何在网页上引导用户关注核心要素的问题。 简单易用&#xff1a;没有任何外部依赖轻量级&#xff1a;仅有 5kb …

中间件 | RPC - [Dubbo]

INDEX 1 Dubbo 与 web 容器的关系2 注册发现流程3 服务配置3.1 注册方式 & 订阅方式3.2 服务导出3.3 配置参数 4 底层技术4.1 Dubbo 的 spi 机制4.2 Dubbo 的线程池4.3 Dubbo 的负载均衡策略4.3 Dubbo 的协议 1 Dubbo 与 web 容器的关系 dubbo 本质上是一个 RPC 框架&…

SpringCloud Stream 消息驱动

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第九篇&#xff0c;即介绍 Stream 消息驱动。 二、消息驱动概念 2.1 消息驱动是什么 官方定义 Spring …

学习通刷视频刷题脚本及安装使用过程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装插件二、复制脚本文件链接三、启动脚本四、登录学习通&#xff08;切记一倍速就行不然被封哦&#xff09;五、最好先把答题关掉先刷视频 前言 解决学习…

AI技术崛起:数据可视化之路更近

在当今AI技术蓬勃发展的时代&#xff0c;数据可视化作为信息传达的重要手段&#xff0c;其门槛逐渐降低。然而&#xff0c;这并不意味着我们可以忽视学习数据可视化的重要性。即使不需要深入专业技术&#xff0c;对数据可视化的基础知识的了解也是至关重要的。那么&#xff0c;…

02-Java变量和运算符

1. 基本数据类型转换&#xff08;Conversion&#xff09; 在Java程序中&#xff0c;不同的基本数据类型的值经常需要进行相互转换。Java语言所提供的七种数值类型之间可以相互转换&#xff0c;基本数据类型转换有两种转换方式&#xff1a;自动类型转换和强制类型转换。boolean…

Dubbo 的配置总线:抓住 URL,就理解了半个 Dubbo

概述 在互联网领域&#xff0c;每个信息资源都有统一的且在网上唯一的地址&#xff0c;该地址就叫 URL&#xff08;Uniform Resource Locator&#xff0c;统一资源定位符&#xff09;&#xff0c;它是互联网的统一资源定位标志&#xff0c;也就是指网络地址。 URL 本质上就是…

【C++从练气到飞升】03---构造函数和析构函数

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书。 目录 ⛳️推荐 一、类的6个默认成员函数 二、构造函数 1. 构造函数的概念 2. 构造函数的定义 3. 构造函数的特性 三、析构函…

MySQL MHA故障切换

目录 一、案例分析 1.1、案例概述 1.2、案例前置知识点 1&#xff09;什么是 MHA 2&#xff09;MHA 的组成 3&#xff09;MHA 的优势 4&#xff09;MHA 现状 1.3、案例环境 1&#xff09;本案例环境 ​编辑 2&#xff09;案例需求 3&#xff09;案例实现思路…

01——LenNet网络结构,图片识别

目录 1、model.py文件 &#xff08;预训练的模型&#xff09; 2、train.py文件&#xff08;会产生训练好的.th文件&#xff09; 3、predict.py文件&#xff08;预测文件&#xff09; 4、结果展示&#xff1a; 1、model.py文件 &#xff08;预训练的模型&#xff09; impor…