- 服务器
- RedisServer
- RedisClient
- 数据库
- RedisDb(KV键值对 + 过期字典)
- 读写时的其他维护操作
- 过期时间
- 命令
- 两种过期策略
- 淘汰策略
- 持久化对过期键的处理
- 写入RDB,载入RDB,写入AOF,重写AOF,复制
1.服务器
和服务器有关的数据结构存在redisServer中,客户端存在redisClient中
struct redisServer{
//一个数组保存服务起中所有数据库
redisDb *Db;
//记录数据库大小,默认16
int dbnum;
}
struct redisClient{
//客户端指向当前在使用的数据库
redisDb *Db;
}
2.数据库
数据库格式,即db,如下图所示:分为键空间和过期字典
typedef struct redisDb{
//其他
...
//数据库键空间
dict *dict;
//过期字典,保存着过期时间
dict *expires;
}redisDb;
数据库键空间中存储着所有key(字符串对象)和value(这里的value可以理解为redisObject)
过期字典中,保存key要过期的时间戳。
注:实际上dict和expires指向的key是同一块地址,该图是为了直观展示
除了对key进行常规的增删改之外,服务器还会进行额外的维护操作:
1.更新键空间命中(hit)和键空间不命中(miss)次数。
2.更新RedisObject中的lru,用于计算key的闲置时间
3.读取时候如果发现这个key已经过期,先删除再操作
4.如果使用WATCH命令监视了这个key,那么服务器在对被watch的key修改之后,会把这个key标记为dirty,从而让事务程序注意到这个key已经被修改过
5.服务器每修改一个键后,会对脏键计数器+1,主要用于触发持久化BGSAVE和复制操作
6.如果服务器开启了数据库通知功能,在对key进行修改之后,服务器将按配置发送相应的数据库通知
3.过期时间
(1)与过期时间有关的命令
expire / pExpire:设置时间段后过期,秒/毫秒 | expire key ttl eg. expire apple 1000 |
expireAt / pExpireAt:设置时间点后过期,秒/毫秒 | expire key timestamp eg. expire apple 1377333070 |
TTL/ PTTL:返回这个键生存时间 ,单位分别为 秒/毫秒 | ttl key ——>返回剩余时间 |
persist:取消key的过期时间 | persist key |
setEx:原理一样,只适用字符串类型 | |
TIME:获取当前时间戳 |
本质上expire, pExpire, expireAt的底层都是调用pExpireAt完成的
(2)过期删除策略
- 传统过期策略:
- 定时策略:为每个有过期时间的key创建定时器,到期就立即删除。缺点:立即删除影响CPU,定时器太多不好管理。
- 惰性策略:放着不管,下次用到发现了再删除。缺点:如果没访问则一直占用内存,容易内存泄漏。
- 定期策略:每隔一段时间,检查所有的过期key并删除,前两者的折中。缺点:不好控制删除频率。
- Redis的过期策略实现:Redis服务器实际采用惰性+定期策略
- 惰性:对Redis所有命令在执行前都会判断是否过期
- 定期:
- Redis会每100ms执行函数serverCron中的activeExpireCycle函数,会在规定时间分多次遍历服务器的每个数据库,从数据库的expires字段中随机检查一部分键的过期时间。
- 全局变量current_db会记录进度,下次开启时恢复上次的进度。eg. 如果有16个数据库,从0记录到15,再恢复到0重头再来。
- 具体实现:
- 从过期字典中随机 20 个 key
- 删除这 20 个 key 中已经过期的 key
- 如果过期的 key 比率超过 1/4,那就重复步骤 1
(3)淘汰策略
- 解决的问题:尽管有过期策略,但这都不是精准的删除,就还是会存在key没有被删除掉的场景,所以就需要内存淘汰策略进行补充。
- 概述:当内存不足时,redis会根据配置的策略淘汰部分keys,保证写入成功。当无淘汰策略时或没有找到适合淘汰的key时,Redis直接返回out of memory错误。
- 策略(6+2种)
策略(触发条件:内存不足以容纳新写入数据时) | 范围 | 移除策略 | 评价 |
noeviction(默认策略) | / | 不移除,直接报错,DEL请求和部分特殊请求除外 | 一般不用 |
allkeys-lru | 所有键空间 | 最久没有使用的 | 最常用。 |
volatile-lru | expires字典中 | 最久没有使用的 | |
allkeys-random | 所有键空间 | 随机移除 | 所有数据访问概率大致相等时,可以选择 |
volatile-random | expires字典中 | 随即移除 | |
volatile-ttl | expires字典中 | 更早过期时间的key / 将要过期的key | |
allkeys-lfu | 所有键空间 | 使用频率(次数)最少 | redis4.0之后出现 |
volatile-lfu | expires字典中 | 使用频率(次数)最少 |
-
redis的lru策略:常规LRU会准确的淘汰掉队头的元素,但是Redis的LRU并不维护队列,只是根据配置的策略要么从所有的key中随机选择N个(N可以配置)要么从所有的设置了过期时间的key中选出N个键,然后再从这N个键中选出最久没有使用的一个key进行淘汰。
4. RDF,AOF和复制对过期键的处理
1. 写入RDB:不会写入过期数据
2. 载入RDB:如果以主服务器运行,过期键不会被载入;
如果以从服务器运行,过期键会被载入;
3. 写入AOF:如果键已经过期但未被删除,AOF不会记录;
如果键已经过期并被删除,则程序会向AOF添加DEL命令。
eg. 加入msg已经过期,但还未删除。系统执行 GET msg指令
(1)数据库发现msg过期,于是惰性删除;(2)数据库向AOF添加DEL msg指令;(3)返回空
4. 重写AOF:不会写入过期数据
5. 复制:主服务器为保证数据一致,每次删除过期key时,会向从服务器发送DEL指令。
如果主服务器上的key没有被发现删除,则从服务器即使碰到该key也不会主动删除。