- 服务器中的数据库
- Redis服务器将所有数据库都保存在redisServer.db数组中,而数据库的数量由redisServer.dbnum属性保存,默认为16(默认创建16个数据库)
- 切换数据库
- 默认情况下,Redis客户端的目标数据库为0号数据库,但可以通过SELECT命令切换数据库
- 数据库键空间
- 数据库主要由dict和expires两个字典构成,dict字典保存了数据库中的所有键值对,被称为键空间(key space),而expires字典负责保存键的过期时间
- 其他键空间操作:
- FLUSHDB命令:清空整个数据库(通过删除键空间所有键值对来实现)
- DBSIZE命令:返回数据库键数量(返回键空间所有键值对来实现)
- 设置键的生存时间或过期时间
- 通过EXPIRE/PEXPIRE命令,客户端可以以秒/毫秒为数据库中的某个键设置生存时间(Time To Live,TTL),时间耗尽时,自动删除
- 客户端可以通过EXPIREAT/PEXPIREAT命令,以秒/毫秒为数据库中的某个键设置过期时间,到期时,自动删除
- TTL/PTTL命令接受一个带有生存时间或者过期时间的键,返回这个键的剩余生存时间
最终EXPIRE、PEXPIRE、EXPIREAT都会转换为PEXPIREAT命令来执行
-
- 保存过期时间:expires字典的键指向数据库中的某个键,值记录了该键的过期时间
- 移除过期时间:PERSIST命令可以移除一个键的过期时间
- 过期键的判定:二步骤:
- 检查过期字典是否有给定键:如果有,则取过期时间
- 检查当前UNIX时间戳是否大于键的过期时间:如果是,那么键已过期;否则,键未过期
- 过期键删除策略*
- 定时删除(主动删除策略):在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
- 优点:对内存友好,通过定时器,可以让过期键尽快删除并释放内存
- 缺点:占用过多CPU时间,影响服务器的响应时间和吞吐量
- 惰性删除(被动删除策略):放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,就返回该键
- 优点:对CPU时间友好,不会再删除其它无关的过期键上花费任何CPU时间
- 缺点:浪费太多内存,有内存泄漏的危险
- 定期删除(主动删除策略):每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,要检查多少个数据库,由算法决定
- 优点(该策略是前两种策略的一种整合和折中):
- 通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响
- 通过定期删除过期键,有效减少了因为过期键而带来的内存浪费
- 难点(确定删除操作执行的时长和频率):
- 如果删除操作太频繁或执行时间过长,定期删除策略就会退化成定时删除,过多占用CPU时间
- 如果删除操作太少或执行时间太短,定期删除策略优惠和惰性删除一样,出现浪费内存的情况
- 优点(该策略是前两种策略的一种整合和折中):
- 定时删除(主动删除策略):在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
- Redis的过期键删除策略**
- Redis通过配合使用惰性删除和定期删除两种策略,更加合理的使用CPU时间和避免浪费内存空间之间取得平衡
- 惰性删除策略的实现:
- 由db.c/expireIfNeeded函数实现,所有读写数据库的redis命令在执行前都会调用expireIfNeeded函数对输入键进行检查
- 输入键已过期则删除键
- 未过期则执行实际的命令流程
- expireIfNeeded函数就像是一个过滤器,他可以在命令真正执行之前,过滤掉过期的输入键,从而避免命令接触过期键
- 因为每个被访问的键都可能因为过期而被expireIfNeeded函数,所以每个命令的实现函数都必须能同时处理键存在以及键不存在这两种情况
- 键存在时,命令按照键存在的情况执行
- 不存在或被expireIfNeeded函数删除时,按不存在情况执行
- 由db.c/expireIfNeeded函数实现,所有读写数据库的redis命令在执行前都会调用expireIfNeeded函数对输入键进行检查
-
- 定期删除策略的实现
- 过期键的定期删除策略有redis.c/activeExpireCycle函数实现,每当redis的服务器周期性操作redis.c/serverCron函数执行时,activeExpireCycle函数就会被调用,他在规定的时间内,分多次遍历服务器的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键
- 定期删除策略的实现
- AOF、RDB和复制功能对过期键的处理
- 生成RDB文件时:执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会保存到新建的RDB文件中
- 载入RDB文件时
- 以主服模式运行,载入RDB文件时,程序会检查文件中的键进,只有未过期的键会被载入数据库
- 以从服模式运行,载入RDB文件时,文件中所有的键都会载入到数据库中,不过,主从服务器数据同步时,从库会被清空
- AOF文件写入时
- 当过期键被删除后,程序会向AOF文件追加(append)一条DEL命令,来显示地记录该键已被删除
- AOF重写时
- 程序会对数据库中的键进行检查,过期键不会被保存到重写后的AOF文件中
- 复制:当服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:
- 主服删除一个过期键后,会显示地向所有从服发送一个DEL命令,告知从服删除这个过期键
- 从服在执行客户端发送的读命令时,即使碰到过期键也不会删,会被当做正常键处理
- 从服只有接到主服的DEL命令后,才会删除过期键
- 数据库通知
- Redis2.8版本新增功能,可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化/命令执行情况
- RDB持久化(RDB简介)*
- RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态存到一个RDB文件中,避免数据意外丢失
- RDB文件:
- 是一个经过压缩的二进制文件
- 只要文件在,就可以还原生成RDB文件时的数据库状态(即使服务器停机/进程退出)
- RDB文件的创建与载入(RDB原理之一**)
- 生成RDB文件的两个命令
- SAVE命令会阻塞服务器直到执行完成STW
- BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求;其中有几个命令跟平常不大一样
- SAVE命令被拒绝;避免两个rdbSave调用
- 禁止两个BGSAVE同时执行;避免产生竞争条件
- BGREWRITEAOF和BGSAVE命令不能同时执行:这两个命令都由子进程执行,并发出两个子进程,并且两个子进程都同时执行大量磁盘写入操作
- RDB的载入是在启动时检测到RDB文件的存在,就会自动载入RDB文件中(没有专门用于载入的命令);载入时,会一直处于阻塞状态,直到完成载入
- 创建RDB文件的实际工作由rdb.c/rdbSave函数完成;载入RDB文件的实际工作由rdb.c/rdbLoad函数完成;
- 生成RDB文件的两个命令
- 自动间隔性保存(RDB原理之二**)
- 因为BGSAVE命令可以在不阻塞服务器进程的情况下执行;所以Redis允许用户通过设置服务器配置的save选项,让服务器定期自动执行一次BGSAVE命令
- 举例:设置保存条件:服务器在90秒内,对数据库进行了至少一次修改;满足条件时,执行BGSAVE
- RDB文件结构(RDB原理之三**)
REDIS |
db_version |
databases |
EOF |
check_sum |
-
- RDB文件最开头是REDIS部分,长度5字节,保存“REDIS”五个字符;用于程序载入文件时,判断是否为RDB文件
- db_version长度为4字节,记录了RDB文件的版本号
- databases 包含数据库,以及库中键值对
- EOF常量长度为1字节(类似于SDS结尾的/0),标志RDB文件正文内容结束
- check_sum是一个8字节长的无符号整数,保存着一个校验和;这个校验和是程序通过前四部分的内容计算得出;载入RDB文件时,会将载入数据所计算出的校验和与check_sum所记录的校验和对比,检查RDB文件是否损坏/出错