Redis缓存何以一枝独秀?(2) —— 聊聊Redis的数据过期、数据淘汰以及数据持久化的实现机制

news2024/10/6 2:33:31

大家好,又见面了。


本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。


上一篇文章中呢,我们简单的介绍了下Redis的整体情况。作为集中式缓存的优秀代表,Redis可以帮助我们在项目中完成很多特定的功能。Redis准确的说是一个非关系型数据库,但是由于其超高的并发处理性能,及其对于缓存场景所提供的一系列能力构建,使其成为了分布式系统中的集中缓存的绝佳选择。

Redis对于缓存能力场景的支持,除了基础的缓存增删改查,还支持对记录的过期时间设定,支持多种不同的数据淘汰策略等等。此外为了解决内存型组件数据可靠性问题,还提供了一系列的数据持久化方案。

本篇文章中,我们就一起聊一聊这方面内容。

数据过期能力

为了节约内存的使用量,保证有限的内存空间能够被更有价值的数据使用,所以很多内存缓存组件都会支持数据过期能力。之前我们提过的本地缓存组件Guava Cache、Caffeine等支持基于缓存容器对象级别设置统一的过期时间,而Redis则支持对每条记录设定单独的过期时间。

创建时设定过期时间

可以在创建记录的时候指定过期时间,redis提供了setex命令可以实现插入的时候同步指定过期时间。比如:

setex key1 5 value1

上述命令实现了往redis中写入一个key1记录,并同时设定了5s后过期。如果在JAVA SpringBoot项目中可以直接使用相关API接口来实现:

stringRedisTemplate.opsForValue().set("key1", "value1", 5, TimeUnit.SECONDS);

这样缓存写入5s之后,缓存记录就会过期失效。描述到这里可以看出,这是一种基于创建时间来判定是否过期的机制,也即常规上说的TTL策略,当设定了过期时间之后不管有没有被使用都会到期被强制清理掉。但有很多场景下也会期望数据能够按照TTI(指定时间未使用再过期)的方式来过期清理,如用户鉴权场景:

假设用户登录系统后生成token并存储到Redis中,指定token有效期30分钟,那么如果用户一直在使用系统的时候突然时间到了然后退出要求重新登录,这个体验感就会很差。正确的预期应该是用户连续操作的时候就不要退出登录,只有连续30分钟没有操作的时候才过期处理。

略有遗憾的是,Redis并不支持按照TTI机制来做数据过期处理。但是作为补偿,Redis提供了一个重新设定某个key值过期时间的方法,可以通过expire方法来实现指定key的续期操作,以一种曲线救国的方式满足诉求。

实现缓存的续期

通过expire命令,可以对已有的记录重新设定过期时间,如果此前已经有设定了过期时间,则覆盖原先的过期时间。

expire key1 30

执行上述命令,可以将key1的过期时间给重新设定为30s,不管此前是否有过期时间。同样地,在代码中也可以方便的实现这一命令:

stringRedisTemplate.expire("key1", 30, TimeUnit.SECONDS);

对于上面说的用户token续期的诉求,可以这样来操作:

用户首次登录成功后,会生成一个token令牌,然后将令牌与用户信息存储到redis中,设定30分钟有效期。
每次请求接口中携带token来鉴权,每次get请求的时候,就重新通过expire操作将token的过期时间重新设定为30分钟。
持续30分钟无请求后,此条token缓存信息过期失效。

同样实现了TTI的效果。

实现指定时刻过期

Redis的过期时间设定,是基于当前命令执行时刻开始的相对过期时间,只能设定距离当前多久后失效,如果想要实现在固定时刻失效,还需要调用端执行一点小小的换算处理来实现。

public void test() {
    LocalDateTime dateTime = LocalDateTime.parse("2022-11-23 22:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd " +
            "HH:mm:ss"));
    Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    long expireTimeLong = date.getTime() - System.currentTimeMillis();
    stringRedisTemplate.expire("key1", expireTimeLong, TimeUnit.MILLISECONDS);
}

通过计算出目标时刻与当前时刻的时间差值,作为过期时间设定到记录上,即可。

数据淘汰策略

前面强调过,Redis是一个基于内存的缓存数据库,而内存的容量通常是有限的。虽然Reids有提供数据过期处理逻辑,但是当数据量特别多的时候就需要数据淘汰机制来兜底了。

这里数据淘汰策略与数据过期两个概念的差异要先弄清楚:

  • 数据过期,是符合业务预期的一种数据删除机制,为记录设定过期时间,过期后从缓存中移除。

  • 数据淘汰,是一种“有损自保”的降级策略,是业务预期之外的一种数据删除手段。指的是所存储的数据没达到过期时间,但缓存空间满了,对于新的数据想要加入内存时,为了避免OOM而需要执行的一种应对策略。

试想下,把Redis当做一个容器,容器已满的情况下继续往里面放东西,应对之法其实就两种:

  1. 直接拒绝放入。

  2. 扔掉容器中部分已有内容,腾出空间接纳新内容放入。

遵循上述认知,Redis提供了6种不同的数据淘汰机制,供使用方按需选择,将有限的空间仅用来存储热点数据,实现缓存的价值最大化。如下:

对几种策略具体含义梳理归纳如下表所示:

数据淘汰策略具体含义说明
noeviction淘汰新进入的数据,即拒绝新内容写入缓存,直到缓存有新的空间。
allkeys-lru将内存中已有的key内容按照LRU策略将最久没有使用的记录淘汰掉,然后腾出空间用来存放新的记录。
volatile-lru从设置了过期时间的key里面按照LRU策略,淘汰掉最久没有使用的记录。与allkeys-lru相比,这种方式仅会在设定了过期时间的key里面进行淘汰。
allkeys-random从已有的所有key里面随机剔除部分,腾出空间容纳新数据。
volatile-random从已有的设定了过期时间的key里面随机剔除部分,腾出空间容纳新的数据
volatile-ttl从已有的设定了过期时间的key里面,将最近将要过期的数据提前剔除掉,与volatile-lru的区别在于排序逻辑不一样,一个基于ttl规则排序,一个基于lru策略排序。

从上述策略里面可以看出,根据LRURandom两种操作的范围不同,各自又细分了两种不同的执行策略。

  • 从设定过期时间的key里进行淘汰

相对来说,设定了过期时间的数据,说明业务层面已经默许了其可以被删除,所以即使被提前淘汰了,对业务层面的影响也是比较小的。

系统中缓存最近30分钟的用户浏览历史记录,即使这些数据被删除淘汰,对系统主体功能而言,不会受损。

  • 从全量key里面执行淘汰

从全量数据里面执行淘汰,就有可能淘汰掉没有设置过期时间的key记录。未设置过期时间的数据如果数据被淘汰掉,很有可能会影响业务的运行逻辑逻辑正确性。

缓存中存储了系统内的黑名单用户列表,用户鉴权的时候,会判断用户是否在黑名单中,如果在黑名单中则禁止登录。这个黑名单是永久的,不会自己解封。如果由于被动淘汰策略触发删除部分黑名单,那原先的黑名单用户就会不受限制而进入到系统中,导致预期之外的情况发生。

不得不说,Redis的这一细分处理原则,还是很贴心的。具体实践中,可以根据自身系统内存储的数据体量以及存储的数据内容性质,选择合适的数据淘汰策略。

数据持久化方案

除了容量有限之外,存储在内存中的数据最大的风险点是什么?数据丢失!

因为内存中的数据是非持久化存储的,一旦断电或者出现系统异常等情况,很容易导致内存数据丢失。所以大部分的系统里面都只是将内存型缓存用作数据库的辅助扛压,最终的数据存储在DB等可以持久化存储容器中,同步一份数据到缓存中用于并发场景下的业务使用。

这种组网场景下,Redis的数据其实是没有持久化的诉求的,因为Redis中数据仅仅是一份副本,最终数据在DB中都有。即使系统异常或者掉电重启,也可以基于数据库的数据进行缓存重建 —— 最多就是数据量特别巨大的时候,重建缓存的耗时会比较长。

另外一种场景,业务里面会有有些写操作会比较频繁、强依赖Redis特性来实现的功能,这部分数据不能丢、但又没有重要到必须每次更新都需要存入DB的地步。比如博客系统中的文章阅读量数据,文章每次被读取都需要更新阅读数,写操作非常频繁,如果阅读量存储到DB中,会导致DB压力较大,这种情况就希望可以将数据存储在内存中,然后内存数据可以持久化保存。

Redis提供了多种持久化方案,可以实现将内存数据定期存储到磁盘上,重启时候可以从磁盘加载到内存中,以此来避免数据的丢失。

下面一起看下。

RDB全量持久化模式

全量模式很好理解,就是定时将当前内存里面所有的key-value键值对内容,全部导出一份快照数据存储到磁盘上。这样下次如果需要使用的时候,就可以从磁盘上加载快照文件,实现内存数据的恢复。

RDB全量模式持久化将数据写入磁盘的动作可以分为SAVEBGSAVE两种。所谓BGSAVE就是background-save,也就是后台异步save,区别点在于SAVE是由Redis的命令执行线程按照普通命令的方式去执行操作,而BGSAVE是通过fork出一个新的进程,在新的独立进程里面去执行save操作。

还记得前面文章中说的么?Redis的请求命令执行是通过单线程的方式执行的,所以要尽量避免耗时操作,而save动作需要将内存全部数据写入到磁盘上,对于redis而言,这一操作是非常耗时的,会阻塞住全部正常业务请求,所以save操作的触发只有两个场景:

  1. 客户端手动发送save命令执行
  2. Redis在shutdown的时候自动执行

从数据保存完备性方面看,这两种方式都起不到自动持久化备份的能力,如果出现一些机器掉电等情况,是不会触发redis shutdown操作的,将面临数据丢失的风险。

相比而言,bgsave的杀伤力要小一些、适用度也更好一些,它可以保证在持久化期间Redis主进程可以继续处理业务请求。bgsave增加了过程中自动持久化操作的机制,触发条件更加的“智能”:

  1. 客户端手动命令触发bgsave操作
  2. Redis配置定时任务触发(支持间隔时间+变更数据量双重维度综合判断,达到任一条件则触发)

此外,在master-slave主从部署的场景中还支持仅由slave节点触发bgsave操作,来降低对master节点的影响。值得注意的是,在fork子进程的时候需要将redis主进程中内存所有数据都复制一份到子进程中,所以bgsave操作实际上是将子进程内存中的数据快照导出到磁盘上,在执行期间对机器的剩余内存有较高要求,如果机器剩余内存不足,则可能导致fork的时候两份内存数据量超过机器物理内存大小,导致系统启用虚拟内存,拷贝速度大打折扣(虚拟内存本质上就是把磁盘当内存用,操作速度相比物理内存大大降低),会阻塞住Redis主进程的命令执行。

如果开启了RDB的bgsave定时触发执行机制,在出现异常掉电等情况,可能会丢失最后一部分尚未来及持久化的内容。在恢复的时候,Redis启动之后会先去读取RDB文件然后将其写入内存中恢复此前的缓存数据,数据恢复期间不受理外部业务请求。

AOF增量同步方式

RDB全量模式简单粗暴,直接将内存全量数据存储为快照序列化到本地。AOF(Append Only File)与RDB的思路不同,AOF更像是记录住Redis的每一次写请求执行命令,将每次执行的写操作命令记录存储到磁盘上,然后通过一种类似命令重放执行的方式,来实现数据的恢复。

AOF具体实现的时候,包含几种不同的策略:

  • always

可以简单的理解为每一条redis写请求执行的时候会触发一次磁盘写入操作,且只有在磁盘写入完成之后,请求的响应才会返回。这种方式可以保证AOF记录的准确性,但是会严重影响Redis的并发吞吐量。

  • every sec

异步执行,任务执行线程执行命令后将命令写入任务放入队列中,由子线程异步方式每秒一次将执行命令分批写入文件中,相比always方式在异常情况下可能会丢失最后1s的执行记录,但可以大大降低对redis命令执行效率的影响。

  • no

redis不控制落盘时间,由操作系统去决定什么时候该往磁盘flush,这种情况一般不推荐使用,无法准确掌控是否落盘,可靠性不够。

AOF的方式落盘持久化的时候,每次仅写入增量的部分,所以对系统整体运行期的影响较小,但随着系统在线运行时长的累加,AOF中存储的命令也越来越多,这样问题也随着出现:

  1. AOF写入的方式类似与日志打印,将请求追加写入到磁盘文件中,文本文件未经过压缩,时间久了之后会占据大量磁盘空间,易造成磁盘满的问题。
  2. 在需要从AOF文件回放重新构建缓存内容时,可能会耗时较久(相当于要将长期累积下来的写操作命令逐个重新执行一下)。

RDB与AOF混合使用

从前面的介绍中可以看出:

  • RDB在过程中每次写磁盘的时候对Redis业务处理的性能影响较大,但是从磁盘加载到内存重建缓存的时候效率很高。

  • AOF通过增量的方式降低了运行过程中对Redis业务处理的影响,但是命令回放重建缓存的时候效率较差。

如果将两者结合起来使用,是否可以取长补短呢?事实似乎的确如此。从4.0版本开始,Redis支持了RDB + AOF的混合持久化方式,通过rewrite机制来实现。需要在redis的配置文件中开启对应开关:

aof-use-rdb-preamble yes

开启之后,redis在每次执行aof操作的时候会判断下是否达到了触发rewrite的条件,如果达到,则fork出一个新的子进程进行RDB操作将当前时刻全量内存数据生成RDB数据然后写入到AOF文件中,而后续的写操作命令则继续append方式追加记录到AOF文件中。这样一来AOF文件实际上由两部分内容组成。如下图所示:

通过RDB + AOF混合的策略,很好的实现了两者的优势互补:

  1. 先通过AOF的方式记录命令,达到门槛的时候才执行rewrite操作生成RDB,最大限度降低了RDB执行频率,降低了对redis业务命令处理过程的影响。
  2. 通过RDB的方式替代了前期大量的AOF命令存储,有效的降低了磁盘占用。
  3. 通过RDB + AOF的方式,系统重建缓存的时候,先加载RDB文件完成主体数据的重建,然后在此基础上重放AOF增量命令,大大降低了启动时AOF重放的耗时。

小结回顾

好啦,关于Redis的数据过期设定、数据淘汰机制以及数据持久化策略等方面的问题,就讨论到这里了。那么你对Redis是否有了新的了解呢?你觉得Redis的哪个方面特性最打动了你呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。

📣 补充说明

本文属于《深入理解缓存原理与实战设计》系列专栏的内容之一。该专栏围绕缓存这个宏大命题进行展开阐述,全方位、系统性地深度剖析各种缓存实现策略与原理、以及缓存的各种用法、各种问题应对策略,并一起探讨下缓存设计的哲学。

如果有兴趣,也欢迎关注此专栏。

我是悟道,聊技术、又不仅仅聊技术~

如果觉得有用,请点赞 + 关注让我感受到您的支持。全网同名,欢迎关注,获取更及时的更新。

期待与你一起探讨,一起成长为更好的自己。

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

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

相关文章

Spring Security 表单配置(二)

Spring Security 表单配置(二)架构认证过滤器认证成功认证失败架构 Spring Security的整体架构,官网文档有介绍:https://docs.spring.io/spring-security/reference/5.7/servlet/architecture.html 友情提示:可以使用…

极客时间学习笔记:03芯片分类

芯片与集成电路的区别? 芯片肯定不全是集成电路。芯片里面,大约只有 80% 属于集成电路,其余的都是光电器件、传感器和分立器件,行业内把这些器件称为 O-S-D(Optoelectronic, Sensor, Discrete)。 下面这张…

SpringBoot 2.7.7入门案例

SpringBoot技术 文章目录SpringBoot技术SpringBoot介绍SpringBoot入门总结SpringBoot介绍 SpringBoot是为了简化搭建Spring项目过程而和开发的框架,Spring本身也是简化开发的框架技术。 可以想想SpringMVC项目(整合SSM)的开发过程&#xff…

【国信长天蓝桥杯】CT117E-M4 嵌入式开发板准备篇 ①开发环境搭建,Keil及STM32CubeMX的下载安装

摘要 本文章基于国信长天 CT117E-M4 嵌入式开发板,讲解了竞赛开发环境的搭建,Keil及STM32CubeMX软件的安装方法,祝各位同学蓝桥杯电子比赛取得好成绩! 软件下载 在蓝桥杯的嵌入式比赛中,主要用到两个软件,分别是代…

易烊千玺小网站短信验证码(小行星编号)发送和验证的实现

每次进入小网站都能看到小小的变化,反观易程序员背后维护的艰辛哈哈哈哈哈哈从此就多了一个目标:one day做出和易烊千玺一样牛的小网站这里面多多的知识点都是我目前都没有学会的(明明都实训了。。页面设计 各种小图标动态效果 网站域名申请 …

【人工智能】观看人工智能 (AI) 入门课程,一起来看看都讲了什么

作者:小5聊 简介:一只喜欢全栈方向的程序员,欢迎咨询,尽绵薄之力答疑解惑 公众号:有趣小馆,一个有趣的关键词回复互动功能 1、课程介绍 1)讨论什么是 AI 及其重要性 2)简要介绍机器学…

MEmu Android Emulator

MEmu Android Emulator是一款专门用于游戏的软件模拟器。你可以从很多方面享受使用MEmu类软件的乐趣,让某人可以直接在计算机上安装它们。您不需要配置复杂的设置,只需安装它们即可。 您可以通过单击右侧的APK按钮轻松安装Andrew游戏。你想安装的APK游戏…

OPPO软件商店APP侵权投诉流程

目录一、官方指引二、侵权投诉流程1.侵权受理流程图2.受理渠道3.权利人侵权投诉通知邮件一、官方指引 https://open.oppomobile.com/new/developmentDoc/info?id10826 二、侵权投诉流程 1.侵权受理流程图 2.受理渠道 侵权处理邮箱:iprheytap.com 侵权处理抄送邮…

一,Spring入门

1 Spring简介 Spring是一个轻量级的JavaEE应用框架,对比EJB(Enterprise Java Beans)技术是官方制定的重量级的JavaEE解决方案。EJB的重的表现:编码必须实现EJB内置的组件、必须部署在支持EJB的服务器中才能运行测试。EJB有很强的侵入性&…

ansible作业五

1、jinjia2模板 hosts.j2,内容如下(主机名和ip地址使用变量): Welcome to 主机名 !(比如servera.lab.example.com) My ip is ip地址. 要求在所有受管主机生成文件:/etc/welcome.txt。 2、角色部分 根据下列…

【Java|golang】2283. 判断一个数的数字计数是否等于数位的值

给你一个下标从 0 开始长度为 n 的字符串 num &#xff0c;它只包含数字。 如果对于 每个 0 < i < n 的下标 i &#xff0c;都满足数位 i 在 num 中出现了 num[i]次&#xff0c;那么请你返回 true &#xff0c;否则返回 false 。 示例 1&#xff1a; 输入&#xff1a;…

EXCEL的几个取整函数对比,int() round() ceiling() ceiling.math()等

1目标 我们处理EXCEL数据经常要遇到以下的需求 取整取倍数按任意数取倍数2 简单取整函数 int() int()只能最简单取整&#xff0c;无任何参数3 round() 四舍五入取整函数 & 整数位取整美化 round() roundup() rounddown() roundup() 和 rounddown() 除了向上和向下取整…

【树莓派4B】搭建HomeAssistant服务端

前言 发挥树莓派的剩余价值&#xff0c;看到知乎有大神利用siri语音控制小米生态的智能家居&#xff0c;他就是利用HA实现的&#xff0c;HA打通不同品牌智能硬件的生态壁垒&#xff0c;而且还是开源&#xff0c;而我刚好手里有一块闲置的树莓派&#xff08;斜眼笑&#xff09;…

【Linux】Linux调试器——gdb的使用以及一些指令

gdb的使用1.背景2.使用3.相关指令1.背景 程序的发布方式有两种&#xff0c;debug模式和release模式 Linux gcc/g出来的二进制程序&#xff0c;默认是release模式 要使用gdb调试&#xff0c;必须在源代码生成二进制程序的时候, 加上 -g 选项 2.使用 使用前先确保自己的Linux上有…

MongoDB的行转列查询

项目组数据需求&#xff0c;需要将Mongo库中的列按日期分组转成行的格式进行显示。Mongo群里问了下&#xff0c;群里热心的大佬小徐 同学果断出手相助&#xff0c;顺利解决了数据问题。现将内容总结梳理如下&#xff0c;帮助有需要的其他同学 表结构 建表语句 db.class.inse…

OSCP_vulnhub digitalworld.local: DEVELOPMENT

DIGITALWORLD.LOCAL: DEVELOPMENT安装&环境下载Description攻击寻找受害主机及端口服务nmap就提示了ctrl u的内容&#xff0c;意思是有隐藏目录搜索slogin_lib.inc.php site:exploit-db.comubantu系统&#xff0c;4.15.0 查找版本漏洞第二种vim sudo提权第三种nano sudo提权…

【前端修炼场】— table 表格的构建

此文为【前端修炼场】第七篇&#xff0c;上一篇文章链接&#xff1a;超链接 文章目录前言一、table 表格的引入二、table 表格属性2.1 边框( border )2.2 宽度( width )2.3 高度( height )2.4 水平对齐( align"left 或 right 或 center )2.5 单元格间距( cellspacing)2.6 …

极客时间学习笔记:04芯片-设计之路

其实一颗芯片项目就是一个标准的产品项目&#xff0c;项目的起点是市场需求分析&#xff0c;接着是设计和制造&#xff0c;如果产品成功完成了商业落地&#xff0c;那么就可以开启下一代产品的迭代升级新周期了。 如果只看芯片设计&#xff0c;它主要包含需求分析、架构设计、逻…

基于Openl启智平台如何提交代码至远程仓库

基于Openl启智平台如何提交代码至远程仓库Openl启智简介快速创建项目克隆项目到本地提交和更新文件Openl启智简介 面向新一代人工智能开源共性技术&#xff0c;面向AI领域的一站式协同开发环境&#xff0c;提供集代码开发环境&#xff0c;数据管理、模型调试、推理和评测为一体…

【Linux】常用基本指令(始)

文章目录&#x1f3aa; Linux下基本指令1.1 &#x1f680; 登录相关指令1.2 &#x1f680; ls1.3 &#x1f680; pwd1.4 &#x1f680; cd1.5 &#x1f680; touch1.6 &#x1f680;mkdir1.7 &#x1f680;rmdir && rm1.8 &#x1f680;man1.9 &#x1f680;cp2.0 &…