Redis缓存设计之常见问题及解决方案
背景:缓存的常见问题及对应的解决方案进行了整理,给大家分享一下。
1.缓存穿透
缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储
层查不到数据则不写入缓存层。
缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。
造成缓存穿透的基本原因有两个:
第一, 自身业务代码或者数据出现问题。
第二, 一些恶意攻击、 爬虫等造成大量空命中。
缓存穿透问题解决方案:
1.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, 60 * 5);}return storageValue;} else {// 缓存非空return cacheValue;}}
1.2 布隆过滤器
对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不
存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可
能不存在;当它说不存在时,那就肯定不存在。

布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得
比较均匀。
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度
进行取模运算得到一个位置,每个 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>
示例伪代码:
package com.redisson;import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;public class RedissonBloomFilter {public static void main(String[] args) {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379");//构造RedissonRedissonClient redisson = Redisson.create(config);RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");//初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小bloomFilter.tryInit(100000000L,0.03);//将zhuge插入到布隆过滤器中bloomFilter.add("tom");//判断下面号码是否在布隆过滤器中System.out.println(bloomFilter.contains("jerry"));//falseSystem.out.println(bloomFilter.contains("john"));//falseSystem.out.println(bloomFilter.contains("tom"));//true}}
注意:布隆过滤器不能删除数据,如果要删除得重新初始化数据。
2.缓存失效(击穿)
由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大
甚至挂掉,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同
时间。
示例伪代码:
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;}}
3.缓存雪崩
缓存雪崩指的是缓存层支撑不住或宕掉后, 流量会像奔逃的野牛一样, 打向后端存储层。
由于缓存层承载着大量请求, 有效地保护了存储层, 但是如果缓存层由于某些原因不能提供服务(比如超大并
发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下
降), 于是大量请求都会打到存储层, 存储层的调用量会暴增, 造成存储层也会级联宕机的情况。
预防和解决缓存雪崩问题, 可以从以下三个方面进行着手。
- 保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster。
- 依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商
品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是
错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,
也可以继续通过数据库读取。 - 提前演练。 在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基
础上做一些预案设定。
4.热点缓存key重建优化
开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满
足绝大部分需求。 但是有两个问题如果同时出现, 可能就会对应用造成致命的危害:
- 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
- 重建缓存不能在短时间完成, 可能是一个复杂计算, 例如复杂的SQL、 多次IO、 多个依赖等。
在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃。
要解决这个问题主要就是要避免大量线程同时重建缓存。
我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从
缓存获取数据即可。
示例伪代码:
String get(String key) {
// 从Redis中获取数据
String value = redis.get(key);
// 如果value为空, 则开始重构缓存
if (value == null) {
// 只允许一个线程重建缓存, 使用nx, 并设置过期时间ex
String mutexKey = "mutext:key:" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
// 从数据源获取数据value = db.get(key);// 回写Redis, 并设置过期时间redis.setex(key, timeout, value);// 删除key_mutexredis.delete(mutexKey);}// 其他线程休息50毫秒后重试else {Thread.sleep(50);get(key);}}return value;}
5.缓存与数据库双写不一致
在大并发下,同时操作数据库与缓存会存在数据不一致性问题
5.1 双写不一致情况

5.2 读写并发不一致

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

6.总结:
以上我们针对的都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那
就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切
记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性!
相关文章:
Redis缓存设计之常见问题及解决方案
背景:缓存的常见问题及对应的解决方案进行了整理,给大家分享一下。 1.缓存穿透 缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储 层查不到数据则不写入缓存层。 缓…...
简单的线程池示例
线程池可以有效地管理和重用线程资源,避免频繁创建和销毁线程带来的开销。以下是一个简单的线程池示例。 cpp #include <iostream> #include <vector> #include <thread> #include <queue> #include <mutex> #include <condition…...
IT入门知识第三部分《软件开发》(3/10)
目录 IT入门知识大纲第三部分《软件开发》 1. 软件开发生命周期(SDLC) 1.1 需求分析 1.2 软件设计 1.3 程序编码 1.4 软件测试 1.5 项目部署 1.6 运行维护 2. 软件开发方法论 2.1 瀑布模型 2.2 敏捷开发 2.2.1 Scrum 2.2.2 Kanban 2.3 Dev…...
卫星通讯助力船舶可视化监控:EasyCVR视频汇聚系统新应用
一、背景 随着科技的不断进步和社会治安的日益严峻,视频监控系统已经成为维护公共安全和提升管理效率的重要工具。传统的视频监控主要依赖于有线传输,但受到地域限制、布线成本高等因素的影响,其应用范围和效果受到一定限制。而卫星通讯传输…...
gcn+tcn+transformer入侵检测
gcn gcn_out self.gcn(A_hat, D_hat, X) 的公式实际上是图卷积网络(GCN)层的核心操作。具体来说,这一步的计算基于图卷积的基本公式: H ( l 1 ) σ ( D ^ − 1 / 2 A ^ D ^ − 1 / 2 H ( l ) W ( l ) ) H^{(l1)} \sigma\left…...
【Python】 了解二分类:机器学习中的基础任务
我已经从你的 全世界路过 像一颗流星 划过命运 的天空 很多话忍住了 不能说出口 珍藏在 我的心中 只留下一些回忆 🎵 牛奶咖啡《从你的全世界路过》 在机器学习和数据科学领域,分类问题是最常见的任务之一。分类问题可以分为多类分…...
搭建PHP开发环境:Linux篇
目录 一、引言 二、环境准备 三、安装Web服务器(Apache) Ubuntu/Debian系统: CentOS/Red Hat系统: 四、安装PHP解释器 Ubuntu/Debian系统: CentOS/Red Hat系统: 五、配置Apache以支持PHP Ubuntu/…...
ROS 自动驾驶多点巡航
ROS 自动驾驶多点巡航: 1、首先创建工作空间: 基于我们的artca_ws; 2、创建功能包: 进入src目录,输入命令: catkin_create_pkg point_pkg std_msgs rospy roscpptest_pkg 为功能包名,后面两个是依赖&a…...
SQL学习,大厂面试真题(1):观看各个视频的平均完播率
各个视频的平均完播率 1、视频信息表 IDAuthorNameCategoryAgeStart Time1张三影视302024-01-01 7:00:002李四美食602024-01-01 7:00:003王麻子旅游902024-01-01 7:00:00 (video_id-视频ID, AuthorName-创作者, tag-类别标签, duration-视频时长(秒&…...
2023年全国大学生数学建模竞赛C题蔬菜类商品的自动定价与补货决策(含word论文和源代码资源)
文章目录 一、题目二、word版实验报告和源代码(两种获取方式) 一、题目 2023高教社杯全国大学生数学建模竞赛题目 C题 蔬菜类商品的自动定价与补货决策 在生鲜商超中,一般蔬菜类商品的保鲜期都比较短,且品相随销售时间的增加而…...
inpaint下载安装2024-inpaint软件安装包下载v5.0.6官网最新版附加详细安装步骤
Inpaint软件最新版是一款功能强大的图片去水印软件,这款软件拥有强大的智能算法,能够根据照片的背景为用户去除照片中的各种水印,并修补好去除水印后的图片。并且软件操作简单、界面清爽,即使是修图新手也能够轻松上手,…...
分享三个仓库
Hello , 我是恒。大概有半个月没有发文章了,都写在文档里了 今天分享三个我开源的项目,比较小巧但是有用 主页 文档导航 Github地址: https://github.com/lmliheng/document 在线访问:http://document.liheng.work/ 里面有各种作者书写的文档ÿ…...
MacOS - 启动台多了个『卸载 Adobe Photoshop』
问题描述 今天安装好了 Adobe Ps,但是发现启动台多了个『卸载 Adobe Photoshop』强迫症又犯了,想把它干掉! 解决方案 打开访达 - 前往 - 资源库,搜索要卸载的名字就可以看到,然后移除到垃圾筐...
PHP 日期处理完全指南
PHP 日期处理完全指南 引言 在PHP开发中,日期和时间处理是一个常见且重要的任务。PHP提供了丰富的内置函数来处理日期和时间,包括日期的格式化、计算、解析等。本文将详细介绍PHP中日期处理的相关知识,帮助读者全面理解和掌握这一技能。 1. PHP日期函数基础 1.1 date()函…...
KVB:怎么样选择最优交易周期?
摘要 在金融交易中,周期的选择是影响交易成败的重要因素之一。不同的交易周期对应不同的市场环境和交易策略,选择合适的周期可以提高交易的成功率。本文将详细探讨交易中如何选择最优周期,包括短周期、中周期和长周期的特点及适用情况&#…...
前端面试题日常练-day69 【面试题】
题目 希望这些选择题能够帮助您进行前端面试的准备,答案在文末 TypeScript中,以下哪个关键字用于声明一个变量的类型为联合类型? a) union b) any c) all d) | 在TypeScript中,以下哪个符号用于声明一个变量的类型为对象类型&am…...
Java 解析xml文件-工具类
Java 解析xml文件-工具类 简述 Java解析xml文件,对应的Javabean是根据xml中的节点来创建,如SeexmlZbomord、SeexmlIdoc等等 工具类代码 import cn.hutool.core.io.FileUtil; import com.alibaba.cloud.commons.io.IOUtils; import com.seexml.bom.Se…...
PyQt5学习系列之新项目创建并使用widget
PyQt5学习系列之新项目创建并使用widget 前言报错新建项目程序完整程序总结 前言 新建项目,再使用ui转py,无论怎么样都打不开py文件,直接报错。 报错 Connected to pydev debugger (build 233.11799.298)新建项目程序 # Press ShiftF10 to…...
mtk8675 安卓端assert函数的坑
8675 安卓端, assert(pthread_mutex_init(&mutex_data_, &mattr) 0);用这行代码发现pthread_mutex_init函数没有被调用,反汇编发现不光没调用assert,pthread_mutex_init也没调用。直接pthread_mutex_init(&mutex_data_, &ma…...
编程入门笔记:从基础到进阶的探索之旅
编程入门笔记:从基础到进阶的探索之旅 编程,作为现代科技的基石,正日益渗透到我们生活的方方面面。对于初学者来说,掌握编程技能不仅有助于提升解决问题的能力,还能开启通往创新世界的大门。本篇文章将从四个方面、五…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
