在 RedisDB 利用两个 dict 分别来记录 key-value 和 key-TTL。
对应的源码:
typedef struct redisDb { dict *dict; // 存放 key-value,也被称为 keyspace dict *expires; // 存放 key-TTL dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ int id; /* Database ID, 0~15 */ long long avg_ttl; // 记录平均 TTL 时长 unsigned long expires_cursor; // expire 检查时在 dict 中抽样的索引位置 list *defrag_later; // 等待碎片整理的 key 列表 } redisDb;
1、过期删除
当一个 key 设置过期时间时,Reids 会把 key 和过期时间 ttl 存储到过期字典expires
中.
设置 key 的过期时间的命令
# 设置 key 的过期时间 expire key seconds pexpire key milliseconds # 查看 key 的过期时间 ttl key pttl key # 对象空转时长 object idletime key
当查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中
- 若不在,正常读取键值对
- 若存在,获取 key 的过期时间,然后与当前系统时间比较,若比当前系统时间小,则判断 key 已过期。
当 key 过期后,需要有相应的机制将已过期的键值对删除。
1.1、过期删除策略
惰性删除
不主动删除过期 key,每次访问 key 时,都检测 key 是否过期,若过期则删除 key。
惰性删除的特点
- 优点:将检查 key 是否过期的操作分布在每一个命令操作时,占用很少的系统资源,对 cpu 时间友好
- 缺点:未访问的过期 key 长时间保留在内存中不会释放,造成内存空间的浪费
定时删除
定期从数据库中随机抽取一定数量的 key 检查是否过期,并删除其中的过期 key
- 从过期字典中随机抽取 20 个 key;
- 检查这 20 个 key 是否过期,并删除已过期的 key;
- 如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。
同时为了保证定期删除不会出现循环过度,导致线程卡死现象。为此增加了定期删除循环流程的时间上限,默认不会超过 25ms。
定期删除的特点
- 优点:通过限制删除操作执行的时长和频率,减少删除操作对 cpu 的影响,同时也能删除一部分过期数据,减少了过期 key 对空间的无效占用
- 缺点:难以确定删除操作执行的时长和频率。执行频繁对 cpu 不友好,执行太少,无法及时释放过期 key 占用的内存。
2、内存淘汰
当 Redis 内存使用达到阈值 maxmemory
时,主动挑选部分 key 删除以释放更多的内存
配置 redis.config
maxmemory # 访问内存上限,通常设置最大内存的一半, maxmemory-policy # 内存淘汰策略
2.1、内存淘汰策略
过期 key 范围内淘汰
- volatile-lru:最长时间没有使用
- volatile-lfu:最少次数使用
- volatile-ttl:最近要过期
- volatile-radom:随机
淘汰所有 key 范围内淘汰
- allkeys-lru
- allkeys-lfu
- allkeys-radom
禁止淘汰
- no-evicition:达到最大内存,增加数据,报错