缓存穿透,缓存雪崩的四种解决方案

2023-11-04

前言

设计一个缓存系统,不得不要考虑的问题就是:缓存穿透、缓存击穿与失效时的雪崩效应。

缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

我们的目标是:尽量少的线程构建缓存(甚至是一个) + 数据一致性 + 较少的潜在危险,下面会介绍四种方法来解决这个问题:

1、使用互斥锁(mutex key): 这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了(如下图)

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

如果是单机,可以用synchronized或者lock来处理,如果是分布式环境可以用分布式锁就可以了(分布式锁,可以用memcache的add, redis的setnx, zookeeper的添加节点操作)。

下面是Tim yang博客的代码,是memcache的伪代码实现

 
  1. if (memcache.get(key) == null) {  

  2.    // 3 min timeout to avoid mutex holder crash  

  3.    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  

  4.        value = db.get(key);  

  5.        memcache.set(key, value);  

  6.        memcache.delete(key_mutex);  

  7.    } else {  

  8.        sleep(50);  

  9.        retry();  

  10.    }  

  11. }  

如果换成redis,就是:

 
  1. String get(String key) {  

  2.   String value = redis.get(key);  

  3.   if (value  == null) {  

  4.    if (redis.setnx(key_mutex, "1")) {  

  5.        // 3 min timeout to avoid mutex holder crash  

  6.        redis.expire(key_mutex, 3 * 60)  

  7.        value = db.get(key);  

  8.        redis.set(key, value);  

  9.        redis.delete(key_mutex);  

  10.    } else {  

  11.        //其他线程休息50毫秒后重试  

  12.        Thread.sleep(50);  

  13.        get(key);  

  14.    }  

  15.  }  

  16. }  

2、"提前"使用互斥锁(mutex key):

在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下:

 
  1. v = memcache.get(key);  

  2. if (v == null) {  

  3.    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  

  4.        value = db.get(key);  

  5.        memcache.set(key, value);  

  6.        memcache.delete(key_mutex);  

  7.    } else {  

  8.        sleep(50);  

  9.        retry();  

  10.    }  

  11. } else {  

  12.    if (v.timeout <= now()) {  

  13.        if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  

  14.            // extend the timeout for other threads  

  15.            v.timeout += 3 * 60 * 1000;  

  16.            memcache.set(key, v, KEY_TIMEOUT * 2);  

  17.            // load the latest value from db  

  18.            v = db.get(key);  

  19.            v.timeout = KEY_TIMEOUT;  

  20.            memcache.set(key, value, KEY_TIMEOUT * 2);  

  21.            memcache.delete(key_mutex);  

  22.        } else {  

  23.            sleep(50);  

  24.            retry();  

  25.        }  

  26.    }  

  27. }  

3、"永远不过期":

这里的“永远不过期”包含两层意思:

1、从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

2、从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

 
  1. String get(final String key) {  

  2.        V v = redis.get(key);  

  3.        String value = v.getValue();  

  4.        long timeout = v.getTimeout();  

  5.        if (v.timeout <= System.currentTimeMillis()) {  

  6.            // 异步更新后台异常执行  

  7.            threadPool.execute(new Runnable() {  

  8.                public void run() {  

  9.                    String keyMutex = "mutex:" + key;  

  10.                    if (redis.setnx(keyMutex, "1")) {  

  11.                        // 3 min timeout to avoid mutex holder crash  

  12.                        redis.expire(keyMutex, 3 * 60);  

  13.                        String dbValue = db.get(key);  

  14.                        redis.set(key, dbValue);  

  15.                        redis.delete(keyMutex);  

  16.                    }  

  17.                }  

  18.            });  

  19.        }  

  20.        return value;  

  21.    }  

4、资源保护:

之前在缓存雪崩那篇文章提到了netflix的hystrix,可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

三、四种方案对比:

作为一个并发量较大的互联网应用,我们的目标有3个: 1、加快用户访问速度,提高用户体验。 2、降低后端负载,保证系统平稳。 3、保证数据“尽可能”及时更新(要不要完全一致,取决于业务,而不是技术。)

所以第二节中提到的四种方法,可以做如下比较,还是那就话:没有最好,只有最合适。

解决方案

一、简单分布式锁(Tim yang)

优点

1、思路简单 2、保证一致性

缺点

1、代码复杂度增大 2、存在死锁的风险 3、存在线程池阻塞的风险

二、加另外一个过期时间(Tim yang)

优点

1、保证一致性

缺点

1、代码复杂度增大 2、存在死锁的风险 3、存在线程池阻塞的风险

三、不过期(本文)

优点

1、异步构建缓存,不会阻塞线程池

缺点

1、不保证一致性。 2、代码复杂度增大(每个value都要维护一个timekey)。 3、占用一定的内存空间(每个value都要维护一个timekey)。

四、不过期(本文)

优点

1、hystrix技术成熟,有效保证后端。 2、hystrix监控强大。

缺点

1、部分访问存在降级策略。

总结

热点key + 过期时间 + 复杂的构建缓存过程 => mutex key问题 构建缓存一个线程做就可以了。 四种解决方案:没有最佳只有最合适。

转载于:https://my.oschina.net/liuyuantao/blog/2989589

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

缓存穿透,缓存雪崩的四种解决方案 的相关文章

  • BODMAS系统的加法和减法

    我一直在构建一个简单的公式计算器 但一直被加法和减法困扰 正如您应该知道的 在计算方程时 您遵循优先级算术规则 即括号 顺序 幂函数 除法 乘法 加法和减法 问题是加法和减法具有相同的优先级 因此您可以从左到右阅读 到目前为止 这是我的代码
  • Apache Camel - 路由中的事务

    我有一个关于 Apache Camel 的一般性问题 我无法找到聚合器是否已进行交易 如果是交易 交易是如何实现的 聚合的速度有多快 将消息发送到聚合器可以在事务中运行 您需要一个带有聚合器的持久存储来让传出消息充当事务 请参阅有关持久性的
  • 如何查找类路径中具有指定名称的所有资源?

    我想列出类路径中具有特定名称的所有文件 我预计会发生多次 因此Class getResource String 不管用 基本上 我必须识别类路径中任何位置具有特定名称 例如 xyz properties 的所有文件 然后累积读取其中的元数据
  • 在Linux中执行jar文件[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我创建了一个可执行的 Java jar 文件 也就是说 我将 java 程序正确打包到 jar 文件中 包括 META INF MANIFEST 文件
  • 为什么我在 Java 中收到有关“可能丢失精度”的警告?

    class Test public static void main String args short s 2 s1 200 s2 s2 s s1 error possible loss of precision System out p
  • 如何使用 Java Streams API 将 Map 列表与列表值合并?

    我怎样才能减少Map
  • 使用 Python-VLC 的 PyInstaller:无属性“media_player_new”错误

    我使用 Python VLC 创建视频播放器 并使用 PyInstaller 在 Windows 10 计算机上生成可执行文件 最初 它给了我错误 Import Error Failed to load dynlib dll libvlc
  • CXF 增加连接池大小而不更改 http.maxConnections

    最近我被要求将 CXF 配置为与我们旧的 XFire 服务相同的参数 这些参数之一是Keep Alive timeout 60 max 20 然而 我做了一些研究 看来 CXF 使用 JVMHttpURLConnection引擎盖下的对象
  • 使用具有可变数量索引的 numpy mggrid

    如何将 numpy mgrid 与可变数量的索引一起使用 我在 github 上找不到任何人将其与硬编码值以外的任何内容一起使用的示例 import numpy as np np mgrid 1 10 1 10 this works fin
  • 避免加密和编码的 URL 字符串中的换行符

    我正在尝试实现一个简单的字符串编码器来混淆 URL 字符串的某些部分 以防止它们被用户弄乱 我使用的代码几乎与示例中的相同JCA指南 http docs oracle com javase 6 docs technotes guides s
  • 如何在 Windows 上使用 Java Hotspot JVM 禁用小型转储 (mdmp) 文件生成

    目前 我有一个已部署的可执行 jar 文件 该文件在崩溃时会创建大型 7 Gb 小型转储文件 我想要一个导致崩溃原因的文本表示 而不是 JVM 状态的二进制文件 我尝试使用中找到的信息这个 CodeRanch 帖子 http www cod
  • python 中的 F 字符串前缀给出语法错误[重复]

    这个问题在这里已经有答案了 我有一个名为 method 的变量 它的值是 POST 但是当我尝试运行时print f method method is used 它不断在最后一个双引号处给出语法错误 我找不到它这样做的原因 我正在使用 py
  • 带 Qt 的菜单栏/系统托盘应用程序

    我是 Qt PyQt 的新手 我正在尝试制作一个应用程序 其功能将从菜单栏 系统托盘执行 这里展示了一个完美的例子 我找不到关于如何做到这一点的好资源 有人可以建议吗 Thanks 我认为您正在寻找与QMenu and QMainWindo
  • 如何将 printStackTrace() 中的异常写入 Java 中的文本文件?

    我需要用 Java 捕获文本文件中的异常 例如 try File f new File catch FileNotFoundException f f printStackTrace instead of printing into con
  • 为数据集生成随机 JSON 结构排列

    我想生成 JSON 结构的许多不同排列作为同一数据集的表示 最好不需要对实现进行硬编码 例如 给定以下 JSON name smith occupation agent enemy humanity nemesis neo 应该产生许多不同
  • 如何创建简单的梯度下降算法

    我正在研究简单的机器学习算法 从简单的梯度下降开始 但在尝试用 python 实现它时遇到了一些麻烦 这是我试图重现的示例 我获得了有关房屋的数据 居住面积 以英尺为单位 和卧室数量 以及最终的价格 居住面积 英尺2 2104 卧室 3 价
  • 在android中测量不规则多边形的面积

    我正在开发一个应用程序 在其中我在地图上绘制多边形 并且我使用的地图不是谷歌 它的Mapsforge开源离线地图库 我可以通过将地理点转换为像素点来轻松在地图上绘制多边形 但在这里我想发现是不规则的多边形 为此我做了很多尝试 但它让我失败了
  • JSONDecodeError:额外数据:Python [重复]

    这个问题在这里已经有答案了 我使用以下代码从文件加载 json file file name obj list with open file as f for json obj in f obj list append loads json
  • 将函数按元素应用于两个 DataFrame

    如何应用函数z ij f x ij y ij 来自数据框X and Y相同大小并将结果保存到 DataFrameZ 这取决于你有什么样的功能 很多功能已经被矢量化为数据框 例如 等等 所以对于这些功能 你可以简单地做Z X Y or Z X
  • 项目级别的@PowerMockIgnore

    在 Maven 中运行时 我的 powermock 测试用例出现以下错误 java lang LinkageError loader constraint violation loader instance of org powermock

随机推荐

  • html提交表单 node,Nodejs之http的表单提交

    之前介绍了http模块的请求与响应的过程 也介绍了TCP协议的客户端与服务端的数据传输 http协议是TCP上层协议 这里创建了一个简单的web服务器 并对提交表单数据进行处理 根据了不起的Node js一书总结 POST方法提交表单数据
  • centso7 openssl 报错Verify return code: 20 (unable to get local issuer certificate)

    问题重现 由于centos7 默认的openssl的版本为1 1 0k 本人编译媒体服务时 需要openssl版本1 1 1以上 所有删除的之前的低版本openssl 手动编译了一个1 1 1k的版本 媒体服务正常运行 并且CA验证正常 结
  • hadoop完全分布式一键安装、启动、停止脚本

    hadoop完全分布式一键安装脚本 bin bash 配置HADOOP的安装目录 修改的地方1 脚本可以自己创建 在windows编写的代码可能运行有问题执行以下 1 gt vim redisshell sh 2 gt set ff uni
  • 1.使用SQL语句创建表

    1 创建表的语法 create table 表名 列1 数据类型 1 列2 数据类型 tablespace 表空间 SQL create table student ID NUMBER not null NAME VARCHAR2 20 表
  • 综合能力 ---- 1. 通信职业道德

    1 职业道德内涵 职业义务 职业良心 职业荣誉 职业信誉 职业尊严 职业纪律 2 记忆职业和职业道德概念 职业 人们在社会中所从事的专门业务和对社会所承担的特定职责 并以此作为重要生活来源的社会活动 职业道德 人们从事正当的社会职业 并在其
  • chrome.runtime.sendMessage 回调函数参数为undefined

    chrome runtime sendMessage 回调函数参数为undefined chrome runtime sendMessage的回调函数默认是同步的 而且超时后直接执行 返回undefined 如果要异步执行 必须在处理函数中
  • Vim,人类史上最好用的文本编辑器!从此以后你就是一个善良的极客!

    CSDN 的小伙伴们 大家好 我是沉默王二 写完 Shell 那篇后就想写 Vim 了 因为人类史上最好的文本编辑器就是 Vim 不赞同的请自觉持有保留意见 哈哈哈 Better Stronger Faster 用这三个单词来赞美 Vim
  • iOS(三)实现App底部TabBar的切换:二

    上一篇讲述了iOS自带的TabBar 但在我所见到的很多App源码中大多用了自己写的TabBar 惯例先上图 这只是一个最简单的TabBar 但重在原理 虽然是我懒 HomeViewController h HomeViewControll
  • day17-json和面向对象(总结)

    day17 json和面向对象 姚万里 1 json数据 1 json数据格式的作用 json和xml是两种通用的数据格式 几乎所有的高级编程语言都支持 json和xml数据的格式的存在 是为了让不同编程语言的程序可以进行有效的数据沟通 2
  • VSCode: PlatformIO主页一直显示loading解决方案

    VSCode PlatformIO主页一直显示loading解决方案 Github问题描述 Could not start PIO Home server Error timeout 205 在vscode中打开platformio点击进入
  • 海豚php上传音频方法(引用 layui的 js 与 css)

    1 html代码 div class layui upload div div div
  • 1033. 旧键盘打字(20)--Python

    之前的时候最后一个测试点一直没有通过 后来在网上搜寻了一下答案 发现自己写的逻辑实在是太混乱了 所以看了一下别人的思路 主要是 1 首先判断坏键盘中是否有 若是有的话 使用flag标记一下 2 然后可以循环的判断应该输出的字符串 边遍历边输
  • 遗传算法及Python代码实现、图解

    目录 前言 一 遗传算法 Genetic Algorithm GA 简介 二 遗传算法基本概念 二 1 目标函数 环境 二 2 一组解 最优解 种群 最适宜种群 二 3 解 编码 个体 基因型 二 4 解码 表现型 难点 二 5 交叉 变异
  • 【jdk1.8特性】之Function

    笔者日常 来吧 Function 相关声明 本文按照以下顺序进行说明并给出简单的使用示例 序号 接口 1 Function
  • 【Redis笔记】发布与订阅

    Redis发布与订阅功能由PUBLISH SUBSCRIBE PSUBSCRIBE等命令组成 SUBSCRIBE命令 客户端可以订阅一个或多个频道 从而成为这些频道的订阅者 subscriber 每当有其他客户端向被订阅的频道发送消息时 频
  • 未来十年互联网十大发展趋势

    世界已被互联网占领 互联网未来10年将如何变化与发展 1 互联网全球普及 根据国际电信联盟最近统计 全球互联网用户总数已经达到20亿人 而联合国公布的最新统计数字显示 世界人口在2011年底突破70亿大关 所以到2020年毫无疑问会有更多的
  • Ubuntu下各种压缩与解压的操作

    这篇文章主要给大家介绍了在Ubuntu系统下各种压缩与解压的操作 其中包括 tar gz bz2 bz Z tgz zip rar以及 lha等各个的解压与压缩方法 每一种都给出了示例代码 需要的朋友可以参考学习 一起来看看吧 一 tar
  • vue项目中高德地图根据城市名定位到城市中心位置,并在该位置做标记

    先看效果 首先记得引入高德地图 并且要带上 plugin AMap DistrictSearch 如下
  • 加密方式

    目录 MD5和RSA 1 MD5 a MD5简介 MD5一种不可逆的加密算法 什么意思呢 网站一般会保存用户密码 为了不让数据库管理员看到用户的密码 你输入的密码是这样的 12345 网站加密后的密码可能是这样的 E10ADC3949BA5
  • 缓存穿透,缓存雪崩的四种解决方案

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 前言 设计一个缓存系统 不得不要考虑的问题就是 缓存穿透 缓存击穿与失效时的雪崩效应 缓存穿透 缓存穿透是指查询一个一定不存在的数据 由于缓存是不命中时被动写的 并且出于容