🔥博客主页: 【小扳_-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 进行删除,循环往复,直到内存充足为止。