Redis核心技术与实战【学习笔记】 - 18.Redis 缓存被污染了,如何选择淘汰策略

news2024/12/24 8:56:48

前言

在一些常见下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据请求后,还继续留存在缓存中的话,只会白白占用缓存的空间。这种情况就是缓存污染

当缓存污染不严重时,只有少量数据占据缓存空间,此时对缓存系统的影响不大。但是,缓存污染一旦变得验证后,就会有大量不在访问的数据滞留在缓存中。如果这时数据占满了缓存空间,我们再往缓存中写入新数据时,就需要先把这些数据逐步淘汰出缓存,这就会引入额外的操作时间开销,进而会影响应应用的性能


1.如何解决缓存污染问题

要解决缓存污染,就得把不会在被访问的数据筛选出来并淘汰掉。这样就不用等到缓存被写满后,再逐一淘汰旧数据后,才能写入新数据了。而哪些数据能留在缓存中,是由缓存的淘汰策略决定的。

之前在《15.Redis 缓存的淘汰策略》讲过8种数据淘汰策略,分别是noevictionvloatile-randomvloatile-ttlvloatile-lruvloatile-lfuallkeys-randomallkeys-lrruallkeys-lfu

在这 8 种策略中,noeviction 策略是不会淘汰数据的。其他的 7 种策略,都会按照一定的规则来淘汰数据。

因 LRU 算法是被广泛应用的缓存数据淘汰算法,所以我们先分析其他策略,然后在单独分析淘汰策略使用 LRU 算法的情况,最后再学习下 LFU 算法用于淘汰策略时对缓存污染的应对措施。

首先,看下 vloatile-randomallkeys-random 这两种策略。他们都是采用随机挑选数据的方式,来筛选即将被淘汰的数据。

随机挑选是不会根据数据的访问情况来筛选数据。如果被淘汰的数据又被访问了,就会发生缓存缺失。也就是说,应用需要到后端数据库访问这些数据,降低了应用的请求响应速度。所以 vloatile-random 和 allkeys-random 策略,在避免缓存污染这个问题上的效果非常有限。

假设我们配置 Redis 缓存使用 allkeys-random 淘汰策略,当缓存写满时, allkeys-random 策略随机选择了数据 20 进行淘汰。不巧的是,数据 20 紧接着又被访问了,此时 Redis 就会发生了缓存缺失。
在这里插入图片描述

vloatile-ttl 针对的是设置了过期时间的数据,把这些剩余存活时间最短的筛选出来并淘汰掉。但是剩余存活时间并不能直接反映数据再次访问的情况。所以,按照 vloatile-ttl 策略淘汰数据,和按随机方式淘汰数据类型,避免缓存污染的效果有限。

你可能会想到一种例外情况:业务应用在给数据设置过期时间时,明确知道数据再次被访问的情况,并根据访问情况设置过期时间。此时,Redis 按照数据的剩余最短存活时间筛选,是可以把不会再被访问的数据筛选出来的,进而避免缓存污染。例如,业务部门知道数据被访问的时长是一个小时,并把数据的过期时间设置为一个小时。这样一来,被淘汰的数据是不会再被访问了。

先小结下。除了在明确知道数据被再次访问的情况下,vloatile-ttl 可以有效避免缓存污染。其他情况下, vloatile-randomallkeys-randomvloatile-ttl 这三种策略并不能应对缓存污染问题。

2.LRU 缓存策略

LRU 策略会按照数据访问的时效性,来筛选即将被淘汰的数据,应用非常广泛。在《15.Redis 缓存的淘汰策略》已经学校了 Redis 是如何实现 LRU 策略的,所以我们就重点看下它在解决缓存污染问题上的效果。

LRU 策略的核心思想:如果一个数据刚刚被访问,那么这个数据肯定是热数据,还会被再次访问。

按照这个核心思想,Redis 中的 LRU 策略,会在每个数据对应的 RedisObject 结构体中设置一个 lru 字段,用来记录数据的访问时间戳。在进行数据淘汰时,LRU 策略会在候选数据集中淘汰lru字段最小的数据(也就是访问时间最久的数据)。

所以,在数据被频繁访问的业务场景中,LRU 策略的确能有效留存访问时间最近的数据。而且,因为留存的这些数据还会被再次访问,所以又可以提升业务应用的访问速度。

但是,也正是因只看数据的访问时间,使用 LRU 策略在处理扫描式单词查询操作时,无法解决缓存污染问题

扫描式单词查询操作,就是指应对大量的数据进行一次全体读取,每个数据都会被读取,而且只会被读取一次。此时,因为这些被查询的数据刚刚被访问过,所以 RedisObject 的 lru 字段值都很大。

在使用 LRU 策略淘汰数据时,这些数据会留存在缓存中很长一段时间,造成缓存污染。如果查询的数据量很大,这些数据占满了缓存空间,却又不会服务新的缓存请求,此时再有新数据要写入缓存的话,还是需要先把旧数据替换出缓存才行,这会影响性能。

如下图所示,数据 6 被访问后,被写入 Redis 缓存。但是,在此之后,数据 6 一致没有被再次访问,这就导致数据 6 滞留在缓存中,造成了污染。

在这里插入图片描述
所以,对于采用了 LRU 策略的 Redis 缓存来说,扫描式单次查询会造成缓存污染。为了应对这类缓存污染问题,Redis 从 4.0 版本开始增加了 LFU 淘汰策略。

与 LRU 策略相比,LFU 策略中会从两个维度筛选并淘汰数据:

  • 一是数据访问的时效性(访问时间离当前时间的远近)
  • 二是数据被访问的次数。

3.LFU 缓存策略的优化

LFU 缓存策略是在 LRU 策略的基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较两个数据的访问时效,把距离上一次访问时间更久的数据淘汰。

和那些被频繁访问的数据相比,扫描式单次查询的数据因为不会再被访问,所以它们的访问次数不会再增加。因此 LFU 策略会优先把这些访问次数低的数据淘汰出缓存。这样,LFU 策略就可以避免这些数据对缓存的污染了。

我们再复习下 LRU 策略的实现:为避免操作链表的开销,Redis 在实现 LRU 策略时使用了两个近似的方法:

  • Redis 是用 RedisObject 结构来保存数据的,RedisObject 结构中设置了一个 lru 字段,用来记录数据的访问时间戳。
  • Redis 并没有为所有数据维护一个全局的链表,而是通过随机采样的方式,选取一定数量的数据(例如 10 个)的数据放入候选集合,后续在候选集合中根据 lru 字段值的大小进行筛选。

在此基础上,Redis 在实现 LFU 策略的时候,只是把原来 24 bit 大小的 lru 字段,又进一步进行了拆分成了两部分

  1. ldt 值:lru 字段的前 16 bit,表示数据的访问时间戳。
  2. counter 值:lru 字段的后 8 bit,表示数据的访问次数。

当 LFU 策略筛选数据时,Redis 会在候选集合中,根据 lru 字段的后 8bit 选择访问次数最小的数据进行淘汰。当访问次数相同,再根据 lru 字段的前 16bit 值大小,选择访问时间最久的数据进行淘汰。

Redis 只用 8bit 记录数据的访问次数,而 8 bit 记录的最大值是 255,这样可以吗?

在实际应用中,一个数据可能会被访问成千上万次。如果每次被访问,counter 就加 1 的话,那么只要访问 255 次,数据的 counter 值就一样了。在进行数据淘汰的时候,LFU 策略就无法很好的区分并筛选这些数据,反而还可能把不怎么访问的数据留存在了缓存中。

假如,第一个数据 A 的累计访问次数是 256 ,访问的时间戳是 20240101100909,所以它的 counter 为 255,而第二个数据 B 的累计访问次数是 1024,访问时间戳为 20240101090909.如果 counter 只能记录到 255,那么数据 B 的 counter 也是255。
此时,缓存写满了,Redis 使用 LFU 策略进行淘汰。数据 A 和 B 的 counter 都是 255,LFU 再比较 A 和 B 访问的时间戳,发现数据 B 上一次访问时间早于 A,就会把 B 淘汰。但其实数据 B 的访问次数远大于数据 A,很可能会再次被访问。这样一来,使用 LFU 策略来淘汰数据就不合适了。

的确,Redis 也注意到了这个问题。因此,在实现 LFU 策略时,Redis 并没有采用数据每次被访问,就给对应的 counter 加 1的计数规则,而是采用了一个更优化的技术规则

LFU 策略的技术规则是:每次当数据被访问一次时,首先用计数器当前值乘以配置项 lfu_log_factor 再加 1,再取其倒数,得到一个 P 值;然后,把这个 P 值和一个取值范围在(0,1)间的随机数 r 值比大小,只有 p 值大于 r 值,计数器才加 1。

下面这段 Redis 的部分源码,显示了 LFU 策略增加计数器值的计算逻辑。其中,baseval 是计数器当前值。计数器的初始值默认是 5(有代码的 LFU_INIT_VAL 常量设置),而不是 0,这样可以避免数据刚被写入缓存,就因访问次数少而被立即淘汰。

double r = (double) rand()/RAND_MAX;
...
double p = 1.0/(baseval *  server.lfu_log_factor + 1);
if(p>r) counter++;

使用了这个规则后,我们可以通过设置不同的 lfu_log_factor 配置项,来控制数据值的增加速度,避免 counter 值很快就到 255 了。

下表是 Redis 官网提供的一张表,它记录了当 lfu_log_factor 取不同值时,在不同的实际访问次数情况下,计数器的值是如何变化的。

lfu_log_factor100 hits1k hits100k hits1M hits10M hits
0104255255255255
11849255255255
101018142255255
10081149143255

可以看到,当 lfu_log_factor 取值为 1 时,实际访问次数为 100 K 后,counter 值就达到 255 了,无法再区分访问次数更多的数据了。而当 lfu_log_factor 取值为 100 时,实际访问次数为 10MB,counter 值才达到 255,此时,实际访问次数小于 10M 的不同数据都可以通过 counter 值区分出来。

正是因为适用了非线性地增的计数方法,及时缓存数据的访问次数成千上万,LFU 策略也可以有效地区分不同的访问次数,从而进行合理的数据筛选。从上面的表中,可以看到,当 lfu_log_factor 取值为 10 时,百、千、十万几倍的访问次数对应的 counter 值已经有明显的区分了,所以,我们在应用 LFU 策略时,一般可以将 lfu_log_factor 取值为 10

前面我们也提到了,应用负载的情况是很复杂的。在一些场景下,有些数据在短时间内被大量访问后,就不会再被访问了。那么按照访问次数来筛选的话,这些数据会被留存在缓存中,但不会提升缓存命中率。为此,Redis 在实现 LFU 策略时,还设计了一个 counter 值的衰减机制。

LFU 策略使用衰减因子配置项 lfu_decay_time 来控制访问次数的衰减。LFU 策略会计算当前时间和数据最近一次访问时间的差值,并把这个差值换算成以分钟为单位。然后,LFU 策略再把这个差值除以 lfu_decay_time 值,所得的结果就是数据 counter 要衰减的值

假设 lfu_decay_time 取值为 1,如果数据在 N 分钟内没有被访问,那么它的访问次数就要减 N。如果 lfu_decay_time 取值更大,那么相应的衰减值会变小,衰减效果也会减弱。所以,如果业务应用中有短时高频访问的数据的话,建议把 lfu_decay_time 设置为 1,这样一来,LFU 策略在它们不在被访问后,会较快的衰减它们的访问次数,尽早把它们从缓存中淘汰出去,避免缓存污染。

4.小结

在实际业务中,LRU 和 LFU 两个策略都有应用。LRU 策略更加关注数据的时效性,而 LFU 策略更加关注数据的访问频率。

  • 通常情况下,实际应用的负载具有较好的时间局部性,LRU 策略的应用会更加广泛
  • 但是,在扫描式单次查询的应用场景中,LFU 策略就可以很好地应对缓存污染问题了,建议你优先使用

此外,如果业务应用中有短时高频访问的数据,除了 LFU 策略本身会对数据的访问次数进行自动衰减外,还可以使用 volatile-ttl 策略,并根据这些数据的访问时限,设置它们的过期时间,以免它们留存在缓存中造成污染。

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

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

相关文章

题目:有1,2,3,4共四个数字,能组成多少个不相同而且无重复数字的三位数有多少个,都是多少?lua

这是作者的思路, 创建三个表, 第一个数是从四个数遍历, 第二个是数剔除第一个数进行遍历 第三个是剔除第一第二个数遍历 脚本如下 local a{1,2, 3, 4} local b{} local c{} local d{} local function copy(tbl) local ctbl{} for k,v in…

Flink CEP(基本概念)

Flink CEP 在Flink的学习过程中,我们已经掌握了从基本原理和核心层的DataStream API到底层的处理函数,再到应用层的Table API和SQL的各种手段,可以应对实际应用开发的各种需求。然而,在实际应用中,还有一类更为复…

大数据 - Hadoop系列《三》- MapReduce(分布式计算引擎)概述

上一篇文章: 大数据 - Hadoop系列《三》- HDFS(分布式文件系统)概述-CSDN博客 目录 12.1 针对MapReduce的设计构思 1. 如何对付大数据处理场景 2. 构建抽象编程模型 3. 统一架构、隐藏底层细节 12.2 分布式计算概念 12.3 MapReduce定义…

wordpress怎么做产品展示站?推荐使用MOK主题和ent主题

大多数WordPress站点都是个人博客网站,主要以文章性质的图文为主。不过部分站长想要用WordPress搭建一个产品展示站,应该怎么做呢? 其实,WordPress可以用来建立各种各样的博客网站,包括个人博客、企业网站、商城、影视…

MySQL中去除重复(十一)

MySQL中去除重复(十一) 一、相同的行 我们要去除相同行要使用DISTINCT关键字 SELECT DISTINCT 列名 FROM 表名; distinct 是针对查询的结果集合进行去重而不是针对某一行或者某一列。 二、查询中的行选择 用 WHERE 子句限制从查询返回的行。一个 WHERE 子句包含一个 必须满…

Oracle 面试题 | 08.精选Oracle高频面试题

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Spring-mvc、Spring-boot中如何在调用同类方法时触发AOP

1. 问题描述 Spring-mvc和Spring-boot中aop可以实现代理的功能,我们可以借此实现事务和日志记录或者限流等多种操作。但是,如果你在一个方法中调用其同类下的其他方法的时候不会触发AOP。本文主要说明其原因及解决办法和实现原理。 2. 原因 AIOP的本质是…

使用VScode编译betaflight固件--基于ubuntu平台

使用VScode编译betaflight固件--基于ubuntu平台 1、使用git克隆betaflight的开源代码2、配置编译环境3、使用VScode编译代码 window平台的见上一篇文章 使用VScode编译betaflight固件–基于windows平台 本文主要介绍在linux系统 ubuntu平台下使用VScode编译betaflight固件的方法…

十分钟上手vue!

Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。 一 vue.js的导入及使用 vue安装…

Unity 读取指定目录所占内存大小

public static class TxxTool{#region 读取文件大小private static List<string> DirList new List<string>();public static long GetFileSize(string path){DirList new List<string>();DirList.Add(path);GetAllDirecotries(path);long fileSize 0;for…

Web3行业研究逐步加强,“链上数据”缘何成为关注焦点?

据中国电子报报道&#xff0c;近日&#xff0c;由中关村区块链产业联盟指导&#xff0c;中国信息通信研究院牵头&#xff0c;欧科云链控股有限公司参与编写的《全球Web3产业全景与发展趋势研究报告&#xff08;2023年&#xff09;》正式发布。研究报告通过全面追踪国内外Web3产…

EF Core入门例子(以SqLite为数据库)

测试环境&#xff1a; visual studio 2017 .net core 2.1 具体步骤如下&#xff1a; 1 新增名称为EFCoreDemo的.net core控制台程序&#xff0c;版本选择.net core 2.1&#xff0c;项目不能放到带中文的目录下&#xff0c;不然到后面执行Add-Migration命令时会报如下的错误…

自然语言处理(NLP)—— Dialogflow ES聊天机器人

1. 背景介绍 这个实验室的目标是让你了解并使用Google的Dialogflow服务。Dialogflow是一个可以让你创建聊天机器人的服务&#xff0c;这个过程不需要或者只需要很少的编程技能。 1.1 账号的创建 为了完成这个实验室&#xff0c;你需要在以下网站上创建账号&#xff1a…

产品经理学习-产品运营《海报制作》

如何策划一款优秀的海报 海报是什么&#xff1f; 是一种将文字和图片结合的信息传递形式&#xff1b;其作用和目的是把想传递给用户的信息高效的传递出去&#xff0c;让用户在极短的时间内产生兴趣&#xff0c;进而产生收藏、分享等行为。 海报的类型&#xff1a; 类型 特点 …

HBuilder使用[微信小程序开发者工具] 显示 × initialize报错

解决办法 我们先要单独打开 微信开发者工具 点击设置里面的安全,把服务器端口打开 然后再回到我们的HBuilder使用重新打开打开 成功打开

Android Studio | sync时报错到Gradle,显示Connection timed out的解决方案

一、问题描述 拉了新项目代码&#xff0c;或者是本地新建项目&#xff0c;Sync阶段就挂掉&#xff0c;给的错误提示是Connection timed out。 二、解决方案 找到本地Gradle的目录位置 Settings – Build – Build Tools – Gradle 在mac上&#xff0c;可能你会发现发现我打码…

ubuntu20.04安装sumo

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 有问题&#xff0c;请大家指出&#xff0c;争取使方法更完善。这只是ubuntu安装sumo的一种方法。一、注意事项1、首先明确你的ubuntu的用户名是什么 二、sumo安装1.…

【Kafka专栏】windows搭建Kafka环境 详细教程(01)

文章目录 01 引言1.1 官网地址1.2 概述简介1.3 kafka与zookeeper 02 部署zookeeper2.1 下载组件包2.2 解压压缩包&#xff08;1&#xff09;解压到任意路径&#xff08;2&#xff09;解压后的目录创建数据目录data 2.3 修改zoo配置2.4 设置系统变量2.5 启动zookeepe服务&#x…

flask基于Python的期货交易模拟系统的django-afl61-vue

期货交易模拟系统是一个便于用户在线查看期货投资、取消投资、风险控制、账户资金、持仓资金等&#xff0c;管理员进行管理的平台。因此本文主要论述了系统开发的过程和实现的功能&#xff0c;结合Web技术来实现的期货交易模拟系统。本系统以软件工程理论为开发基础&#xff0c…

Unity_ShaderGraph示例项目启动

Unity_ShaderGraph示例项目启动 大前提不变:URP模板,Unity2023.1.19使用 Shader Graph - Unity 手册Shader Graph 是一个工具,能够让您直观地构建着色器。您可以在图形框架中创建并连接节点,而不必手写代码。Shader Graph 提供了能反映所作更改的即时反馈,对于不熟悉着色…