总体
- 该部分包括数据库的结构、过期键的删除策略、数据库通知
1. 服务器中的数据库
- redis服务器的数据库结构都是redis.h/redisDb结构
struct redisServer{
...
//一个数组,保存这服务器中的所有数据库
redisDb *db
//服务器的数据库数量
int dbnum;
...
}
- 数据库的个数由dbnum属性决定,该值是由服务器配置的database选项决定,该值默认为16。
- 结构图
2. 切换数据库
- Redis默认的数据库为0号数据库,客户端可以通过select语句来切换数据库
- 客户端状态redisClient结构如下,其中db属性记录了客户端当前的目标数据库
typedef struct redisClient{
...
//记录客户端当前正在使用的数据库
redisDb *db;
...
}
- redisClient->select 2
3. 数据库键空间
- 服务其中的每个数据库都是由redisDb数据结构表示,而其中,dict字典保存了数据库中所有的键值对,称dict字典为键空间。
typedef struct redisDb{
...
//数据库键空间,保存着数据库中的所有键值对
dict *dict;
//过期字典
dict *expires;
...
}
- 键空间的键值
- 键空间中的键就是数据库的键,每个键都是一个字符串对象
- 键空间中的值就是数据库的值,对应着不同的redis对象。
- 图中展示了 list(alphabet)、hash(book)、string(message)
- 键空间的增加、删除、更新、取值
- 增加-》SET date “2013.12.1”
- 删除-》DEL book
- 更新-》SET message “blah blah”
- 取值-》GET message
4. 设置键的过期时间
- redis中,过期时间是一个UNIX的时间戳,过期时间来临,服务器会自动删除这个键。
- SETEX 命令可以为字符串键设置过期时间,但是不可以对其他redis对象设置过期时间。但是底层原理和EXPIRE原理一致。
- 设置过期时间的通用命令,虽然命令存在四种,但是最后1,2,3都会转换为第四种,将具体的过期时间转换成毫秒保存到redis中,即使1,2传的是秒数和毫秒数,redis内部会转换成当前时间+秒/毫秒之后对应的毫秒,然后作为key的过期时间。
- EXPIRE key ttl
- PEXPIRE key ttl
- EXPIREAT key timestamp
- PEXPIREAT key timestamp
- redis中通过过期字典expires,保存了数据库中所有键的过期时间
- 过期字典的结构
- 过期字典的键是一个指针,这个指正指向键空间中的某个键对象(也即是某个数据库键)
- 过期字典的值是一个Long类型的整数,保存了键所指向的数据库键的过期时间(一个毫秒精度的UNIX时间戳)
- 示意图,其中为了表示方便,写了两次alphabet和book实质,在过期字典中是指向键空间中的键对象的指针,而非键对象,图中也表示出来
- 过期时间的操作,新增、删除、获取
- 新增:PEXPIREAT message 1391234400000
- 删除:PERSIST message
- 获取:TTL
- 过期键的判定
- 检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间
- 检查当前UNIX时间戳是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。
5. 过期键的删除策略
- 过期删除策略存在三种:
- 定时删除:创建一个定时器,在键的过期时间来临时,立即执行键的删除工作
- 惰性删除:暂时放任键过期不管,但是每次从键空间获取键时,都检查取得的键是否过期,如果过期就删除,没有则返回该键
- 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。
5.1 定时删除
- 优点:对内存友好,可以保证键可以尽快的删除掉,不会长时间的堆积在内存中,造成内存的浪费
- 缺点:对CPU不友好,在过期键比较多的情况下,占用相当一部分的CPU资源,影响服务器的吞吐量和响应时间。除此之外,需要用到redis服务器中的时间事件,当前时间事件的实现方式–无序链表,查找的时间复杂度O(N)
5.2 惰性删除
- 优点:对CPU友好的,程序只在取出键时进行过期检查,删除的目标也仅限于当前处理的键,不会在其他的键上耗费时间
- 缺点:对内存不友好,导致,很多无用的垃圾占用大量的内存
5.3 定期删除
- 优点:
- 通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响
- 通过定期删除,可以减少因为过期键太多导致的内存浪费
- 确定:
- 定期删除操作的时长和频率的确定
- 如果频率太高,势必会加重CPU的耗费时间
- 如果频率太少,或者执行时长太短,则会和惰性删除一样导致,内存的浪费
6 Redis过期键删除策略
6.1 惰性删除的实现
- 惰性删除策略是由db.c/expireIfNeed函数实现,所有的读写数据库的Redis命令在执行之前都会调用expireIfNeed函数进行检查
- 如果输入键已经过期,那么expireIfNeed将输入键从数据库中删除
- 如果输入建未过期,那么不做任何动作
- expireIfNeed函数会对键进行删除,所以每个命令的实现都应该包括键存在和键不存在两个处理方式
6.2 定期删除策略实现
- 由redis.c/activeExpireCycle函数实现
- 该函数会在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。
- 执行过程
- 函数每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除过期的。
- 在函数中会自己上次执行到哪个数据库,使用current_db来记录,在下次执行定期删除时,从current_db之后的数据库进行删除工作
- 随着activeExpireCycle函数的执行,势必服务器中有限的数据库都会被检查一遍,这个时间current_db会重置为一开始的0数据库,进行下一次的执行。
7 AOF RDB 和复制功能对过期键的处理
7.1 RDB
- 生成RDB文件
- 在执行SAVE或者BGSAVE命令时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。
- 数据库中包含过期键不会对新的RDB文件造成影响。
- 载入RDB文件
- 在服务器启动的时候,在启动RDB文件的情况下,服务器对RDB文件进行载入
- 服务器是主服务器,在载入RDB文件时,会对键进行过期检查,未过期才会记录到数据库中,过期会被忽略,所以过期键对主数据库不会有影响
- 服务器是从服务器,在载入RDB文件时,文件保存所有的键,不论是否过期,都会被载入数据库中。不过在主数据库进行数据同步的时候,从服务器的数据库会被清空。所以一般来讲,过期键对载入RDB文件的从服务器不会造成影响。
7.2 AOF
- AOF文件写入
- 当服务器以AOF持久化模式运行时,如果数据库中的某个键过期了,但还没有执行惰性删除和定期删除,那么AOF文件不会因为这个过期键而发生任何影响
- 当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一条DEL命令,来显式记录该键被删除
- AOF重写
- 执行AOF重写的过成中,程序会对数据库中的过期键进行检查,已经过期的不会放到重新的AOF文件中。数据库中包含过期键不会对AOF重写造成影响。此时可能存在数据库中包含过期键,而AOF中没有。
7.2 复制
- 当服务器运行在复制模式下,从服务器的过期键删除动作是由主服务器控制的
- 主服务器在删除一个过期键后,会显示的向所有的从服务器发送一条DEL命令,告知从服务器删除这个过期键
- 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键。
- 从服务器只有在主服务器发来的DEL命令之后,才会删除过期键
- 通过由主服务器来控制从服务器统一删除过期键,可以保证主从服务器数据的一致性。
8 数据库通知
8.1 发送通知
- 发送通知是由notify.c/notifyKeyspaceEvent函数实现
void notifyKeyspaceEvent(int type,char *event,robj *key,int dbid)
8.2 发送通知的实现
- server.notify_keyspace_events属性就是服务器配置notify-keyspace-events选项锁设置的值,如果给定的通知类型type不是服务器允许的通知类型,那么函数直接返回不做任何操作
- 如果给定的通知是服务器允许发送的通知,那么下一步函数会检测服务器是否允许发送键空间通知,如果允许的话,程序就会构建并发送事件通知
- 最后,函数检测服务器是否允许发送键事件通知,如果允许的话,程序就会构建并发送事件通知。