- 前言:在使用Redis过程中大多数人其实多多少少就应该对Redis的实现有了一个模糊的猜测。Redis代表一个服务器,其中会默认有16个数据库,每次操作时都要提前select切换数据库,每个库里又有很多自定义的键值对数据,其实每个库都是一个大的字典,key都是String类型,value就根据需要选择5种基本类型之一,到这里这章内容已经概括一半了,后半章主要写数据过期和过期数据删除的问题,下面看一下具体的实现细节。
9.1 服务器中的数据库
- Redis的所有数据库都保存在redis.h/redisServer的db数组中,数组的每一项都是一个redis.h/redisDb结构,每个redisDb对应一个数据库:
struct redisServer {
//...
// 一个数组,保存着服务器中所有的数据库
redisDb *db;
// 决定数据库的数量,默认16
int dbnum;
//...
}

9-1.jpg
9.2 切换数据库
- 默认选择0号数据库,通过select命令切换。
- 内部通过客户端redisClient结构的属性db记录当前目标库,db是一个redisDb结构的指针,其实就是指向上一节redisServer中db数组里的某一个元素,通过修改redisClient中db指向切换目标数据库:
struct redisClient {
//...
// 记录当前客户端正在使用的数据库
redisDb *db;
//...
}
9.3 数据库键空间
- Redis是一个键值对数据库服务器,服务器中每个数据库都有一个redis.h/redisDb结构表示,其中dict字典保存了数据库中所有的键值对,这个字典称为键空间。
struct redisDb {
//...
// 键空间,保存数据库中所有的键值对
dict *dict;
//...
}
- 每个key是字符串对象,value可以使任意一种基础类型的对象。对键值对的crud操作其实都是操作dict字典。
9.3.5 读写键空间时的维护操作
- 9.3的前几个小结都是对键空间的crud和一些附图直接跳过。
- 读写对象时的操作:
- 读取键后,会根据命中结果记录次数,在INFO的stats命令的keyspace_hits属性和keyspace_misses属性中查看。
- 读取键后,会更新LRU(最后一次使用)时间,清理功能会用到这个属性。
- 如果读取的键已经过期,会先删除键。
- 如果客户端watch了某个键,服务端修改这个键后会标记为脏(ditry)。
...
9.4 设置键的生存时间或过期时间
- expire或pexpire:设置经过指定秒或毫秒后删除键。
- expireat或pexpireat:根据秒或毫秒时间戳设置键的过期时间。
- ttl和pttl:返回过期时间秒和毫秒。
9.4.1 设置过期时间
- 上面四种都可以设置过期,使用方式都是“指令 <key> <value>”。
- 实际上其他三种命令都会转化成pexpireat,具体代码不粘了,就是时间戳和方法的转换,最后都转化成pexpireat。
9.4.2 保存过期时间
- 上面提到了redis.h/redisDb,里面存了所有键的"键空间",过期时间保存在了另一个dict里叫"过期字典"
- "过期字典"里的键指向"键空间"里的键对象
- 值是long类型的毫秒时间戳
- 知道"过期字典"的结构,大概也就知道pexpireat是怎么设置过期的了:
- 如果键不存在return,如果存在,找到并设置"过期字典"里key的超时value,返回。
9.4.3 移除过期时间
- persist命令可以移除键的过期时间,命令和pexpireat相反:
- 如果键不存在return,如果存在,找到并删除"过期字典"里key的超时value,返回。
9.4.4 计算并返回剩余生存时间
- ttl和pttl通过计算超时时间和当前时间差,得到剩余生存时间:
- 如果键不存在return,如果存在,如果有过期时间不存在return,如果存在和当前时间相减,返回。
9.4.5 过期键的判定
- is_expired(key)方法和ttl类似,检查键是否存在于"过期字典",如果存在,检查当前时间是否大于过期时间。
9.5 过期键删除策略
- 过期删除有三种可能的策略:
- 定时删除:创建过期同时创建一个定时器,定时器到期删除key
- 惰性删除:到期不管,用到的时候提前判断
- 定期删除:每隔一段时间扫描并且删除,一般不会一次全扫根据算法调整。
9.5.1 定时删除
- 内存友好(可以准时删除),CPU不友好(键太多时任务很吃CPU),而且redis创建定时器的时间事件用到了无序列表,查找时间复杂度O(n)低效。
9.5.2 惰性删除
- 内存不友好(时间到了如果不用就会一直存在),CPU友好(只有在取时候会做判断,没什么压力),问题主要是如果过期Key一直不用,就一直不会删越堆越多。
9.5.3 定期删除
- 这种方式主要问题是执行的时长和频率,扫描的数量和频率设定不当都会影响性能。
9.6 Redis的过期键删除策略
9.6.1 惰性删除策略的实现
- 每次读写数据前调用expireIfNeeded()检查是否过期,过期就删除键并且按不存在执行,否则正常执行读写。
9.6.2 定期删除策略的实现
- Redis中有一个周期执行的函数redis.c/serverCron,执行serverCron时会调用activeExpireCycle函数,分多次遍历服务器中的各个数据库:
- 每次运行从数据库去除一定数量的键检查,过期就删除
- current_db记录activeExpireCycle函数检查的进度,下次执行时按current_db记录继续检查。
- 所有数据库执行完成后current_db重置为0
9.7 AOF、RDB和复制功能对过期键的处理
9.7.1 生成RDB文件
- 执行save和bgsave创建新RDB文件时,过期键不会保存到新RDB文件中
9.7.2 载入RDB文件
- 如果启动了RDB功能在Redis启动时:
- 如果是主服务器:检查是否过期,过期的键会被忽略
- 如果是从服务器:全部导入,不过在主从同步时会被清空。
9.7.3 AOF文件写入
- 如果键已经过期,但是没被惰性删除或定期删除扫描到,就没有变化。触发删除后会在AOF追加一条DEL命令
9.7.4 AOF重写
- AOF文件重写时,过期键不会保存到重写的AOF文件中
9.7.5 复制
- 复制模式时,从服务器过期键删除由主服务器控制:
- 主服务器删除超时键后,向从服务器发出DEL命令
- 从服务器在读写时即使键过期也不会删除
- 从服务器只有在接到主服务器DEL命令时才会删除
9.8 数据库通知
- 通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。
网友评论