欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > Redis 篇-深入了解 Redis 中的 RESP 通信协议与内存回收(过期 key 处理、内存淘汰策略)

Redis 篇-深入了解 Redis 中的 RESP 通信协议与内存回收(过期 key 处理、内存淘汰策略)

2024/10/25 18:24:57 来源:https://blog.csdn.net/Tingfeng__/article/details/142599111  浏览:    关键词:Redis 篇-深入了解 Redis 中的 RESP 通信协议与内存回收(过期 key 处理、内存淘汰策略)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 Redis 通信协议 - RESP 协议

        2.0 Redis 内存回收

        2.1 Redis 内存回收 - 过期 key 处理

        2.1.1 Redis 是如何知道一个 Key 是否过期呢?

        2.1.2 是不是 TTL 到期就立即删除了呢?

        2.2 Redis 内存回收 - 内存淘汰策略

        2.2.1 Redis 八种不同删除 key 的策略

        2.2.2 Redis 内存淘汰的流程图


        1.0 Redis 通信协议 - RESP 协议

        简单来说,通信协议就是对于发送命令或者接收命令格式的规范。

        Redis 是一个 CS 架构的软件,通信一般分两步:

        1)客户端向服务端发送一条命令

        2)服务端解析并执行命令,返回响应结果给客户端。

        1.1 RESP 协议 - 数据类型

        在 RESP 中,通过首字节的字符来区分不同数据类型,常用的数据类型包括 5 种:

        1)单行字符串:首字节是 "+",后面跟上单行字符串,以 CRLF("\r\n") 结尾。

举个例子:

        比如说要发送命令的格式:"+OK\r\n"。但需要注意的是,使用单行字符串发送命令,可能会导致二进制不安全问题发生。

        2)错误(Errors):首字节是 "-",与单行字符串格式一样,只是字符串是异常信息。

举个例子:

        服务端发送错误信息的格式:"-Error message\r\n" 。

        3)数值:首字节是 ":",后面跟上数字格式的字符串,以 CRLF 结尾。

举个例子:

        给服务端发送数值信息的格式:":10\r\n" 。

        4)多行字符串:首字节是 "$",表示二进制安全的字符串,最大支持 512 MB。

举个例子:

        给服务端发送多行字符串的格式:"$5\r\nhello\r\n"

需要注意的是: 

                如果大小为 0,则代表空字符串:"$0\r\n\r\n";

                如果大小为 -1,则代表不存在:"$-1\r\n";

        5)数组:首字节是 "*",后面跟上数组元素个数,再跟上元素,元素数据类型不限。

举个例子:

        给 Redis 服务端发送 "set name 童童" 的命令的格式:

        *3\r\n

        $3\r\nset\r\n

        $4\r\nname\r\n

        $6\r\n童童\r\n

字段解析:

        *3\r\n:表示的数组中有三个元素;

        $3\r\nset\r\n:数组的第一个元素是多行字符串类型,值为 set ,字节数为 3 个字节;

        $4\r\nname\r\n:数组的第二个元素也是多行字符串类型,值为 name ,字节数为 4 个字节;

        $6\r\n童童\r\n:数组的第三个元素也是多行字符串类型,值为 童童,字节数为 6 个字节;

        2.0 Redis 内存回收

        Redis 之所以性能强,最主要的原因就是基于内存存储。然而单节点的 Redis 其内存大小不宜过大,会影响持久化或主从同步性能。

        可以通过修改配置文件来设置 Redis 的最大内存:

        当内存使用达到上限时,就无法存储更多的数据了。因此,需要采用某些手段来解决内存达到上限的问题。

        一般可以通过内存回收来解决:过期 key 处理、内存淘汰机制。

        2.1 Redis 内存回收 - 过期 key 处理

        通过两个具体的问题来了解过期 Key 处理机制来解决内存不足:

        2.1.1 Redis 是如何知道一个 Key 是否过期呢?

        Redis 本身是一个典型的 key-value 内存存储数据库,因此所有的 key、value 都保存在 Dict 结构中。不过在 database 结构体中,有两个 Dict:一个用来记录 key-value;另一个用来记录 key-TTL 。

相关源码如下:

        解析其中最重要的两个字段:

        1)dict *dict:存放所有 key 及 value 的地方,也被称为 keyspace。

        2)dict *expires:存放每一个 key 及其对应的 TTL 存活时间,只包含设置 TTL 的 Key 。

相关的结构如下:

        所以,回到之前的问题:Redis 是如何知道一个 key 是否过期的?

        答案是利用两个 Dict 分别记录 Key-Value 以及 Key-TTL ,通过查询两个 Dict 来判断当前 key 是否过期的。

        2.1.2 是不是 TTL 到期就立即删除了呢?

        具体来说,Redis 处理过期 key 的方式一般来说有两种:惰性删除、周期删除。

        1)对于惰性删除来说:

        当你尝试访问一个 key 时,Redis 会检查该 key 是否已经过期。如果过期了,Redis 会将其删除并返回空值。这种方式被称为惰性删除。

相关的源码:

        2)对于周期删除来说:

        顾名思义,是通过一个定时任务,周期性的抽样部分过期的 key,然后执行删除。执行周期有两种:

        第一种:Redis 会设置一个定时任务 serverCron(),按照 server.hz 的频率来执行过期 key 清理,模式为 SLOW 。

SLOW 模式规则:

        执行频率受 server.hz 的影响,默认为 10,即每秒执行 10 次,每个执行周期 100ms 。

        执行清理耗时不超过一次执行周期的 25% 。

        逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 Key 判断是否过期。

        如果没达到时间上限(25 ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束。

        第二种:Redis 的每个事件循环前会调用 beforeSlepp() 函数,执行过期 key 清理,模式为 FAST 。

FAST 模式规则:

        执行频率受 beforeSleep() 调用频率影响,但两次 FAST 模式间隔不低于 2 ms 。

        执行清理耗时不超过 1ms 。

        逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 Key 判断是否过期。

        如果没达到时间上限(1 ms)并且过期 Key 比例大于 10%,再进行一次抽样,否则结束。

小结:

        1)RedisKey 的 TTL 记录方式:

        在 RedisDB 中通过一个 Dict 记录每个 key 的 TTL 时间。

        2)过期 key 的删除策略:

        惰性删除:每次查找 key 是判断是否过期,如果过期则删除;

        定期删除:定期抽样部分 key,判断是否过期,如果过期则删除。

        3)定期清理的两种模式:

        SLOW模式执行频率默认为每秒十次,每次不超过 25 ms 。

        FAST 模式执行频率不固定,但两次间隔不低于 2 ms,每次耗时不超过 1 ms 。

        2.2 Redis 内存回收 - 内存淘汰策略

        内存淘汰就是当 Redis 内存使用达到设置的阈值时,Redis 主动挑选部分 Key 删除从而来释放更多内存的流程。

        Redis 会在处理客户端命令的方法之前,先执行 processCommand() 中尝试做内存淘汰。

相关的源码:

        2.2.1 Redis 八种不同删除 key 的策略

        在配置文件中进行配置不同删除 key 的策略:

Redis 支持 8 种不同策略来选择要删除的 key:

        1)noeviction:不淘汰任何 key,但是内存满时不允许写入新数据,默认就是这种策略。

        2)volatile-ttl:对设置了 TTL 的 key,比较 key 的剩余 TTL 值,TTL 越小越先被淘汰。

        3)allkeys-random:对全体 key,随机进行淘汰。也就是直接从 db->dict 中随机挑选。

        4)volatile-random:对设置了 TTL 的 key,随机进行淘汰。也就是从 db->expires 中随机挑选。

        5)allkeys-lru:对全体 key,基于 LRU 算法进行淘汰。

        6)volatile-lru:对设置了 TTL 的 key,基于 LRU 算法进行淘汰。

        7)allkeys-lfu:对全体 key,基于 LFU 算法进行淘汰。

        8)volatile-lfu:对设置了 TTL 的 key,基于 LFU 算法进行淘汰。

LRU 算法:

        最少最近使用,用当前时间减去最后一次访问的时间,这个值越大则淘汰优先级越高。以秒为单位记录最近一次访问的时间,长度为 24 bit 。

LFU 算法:

        最少频率使用,会统计每个 key 的访问频率,值越小淘汰优先级越高。高 16 位以分钟为单位记录最近一次访问时间,低 8 位记录逻辑访问次数。

        2.2.2 Redis 内存淘汰的流程图

具体流程分析:

        在执行客户端发送过来的命令之前,先判断当前内存是否充足,如果内存还是很充足,那么不需要去执行删除 key 的操作。

        如果当前内存不充足时,继续判断内存策略是否为默认 noeviction 策略,也就是即使内存满了,也不会执行删除 key 的操作,会发出错误信息。

        假设不是默认策略,接着继续判断是从 dict 字典还是从 entries 字典中删除 key,该两者的区别就是有无设置 TTL。

        再紧接着,选择删除策略,假设选择了 random 策略,也就是随机选择 key 来删除。删除完之后,再判断内存是否充足,如果还是不足,那么会接着循环来删除 key,如果达到充足的内存,则直接退出删除 key 的操作。

        若选择的删除策略为:TTL、LRU、LFU 这些策略,先创建一个池,用来存放准备删除的 key,在 Redis 数据库中从 0 直到 15 的数据库进行遍历抽样选择要放入池中的 key,默认每次抽样 5 个 key。

        再接下来,根据具体选择的策略来进行将抽样 5 个 key 放入池中,每一个放入池中的 key 都以升序的方式进行排序。

        最后,将池中要确定删除的 key 进行删除,循环往复,直到内存充足为止。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com