数据库结构
redis的数据库是一个保存在redis服务器redisService
结构体中的redisDb
数组,redis客户端redisClient
通过一个redisDb
指针指向服务器内数据库数组内的一个元素。
键空间 字典
而redisDb
内部,是由一个字典组成,这个字典叫做键空间(key space)。所有对数据库的增删改查,都是对redisDb
内字典的操作,字典的键总是字符串对象,字典的值可以是redis的五种对象的任意一种。
过期字典
除了保存所有数据的字典之外,redis维护过期键值对会使用一个过期字典,过期字典内保存的是键–过期时间(long long 类型时间戳,单位ms)的键值对。
过期键的处理策略包括:懒删除和定期删除,懒删除即只有当要访问指定键值对的时候,才查询该键值对是否过期;定期删除是指,指定一个时间间隔,每经过一个时间间隔则从过期字典中选出一部分键值对,判断是否过期。
设置键的过期时间有四种命令,底层都是使用的一种命令PEXPIREAT实现的:
* PEXPIREAT key timestamp
: 指定键在某个时间戳之后过期,单位是ms
* EXPIRE key timestamp
:指定键在某个时间戳之后过期,单位s,底层使用PEXIREAT
实现
* PEXPIRE key ttl
:指定键在ttl时间后过期,单位ms,底层使用PEXPIREAT
计算后实现
* EXPIRE key ttl
:指定键在ttl时间后过期,单位s,底层使用PEXPIRE
实现
redis备份和复制机制中过期键值对的行为
- RDB备份:备份时,过期键不会加入备份文件;还原文件时,如果还原到主服务器,过期键不会还原到内存,如果还原到从服务器,则还原到内存,之后通过于主服务器同步数据,来判断过期
- AOF备份:以AOF持久化模式运行redis时,过期键的删除操作会产生一条删除记录到AOF文件,过期键本身会备份到文件;而当进行AOF重写的时候,过期键不会保存到重写后的AOF文件中
- 复制:以复制模式运行redis时,过期键的删除由主服务器说了算,主服务器删除过期键的时候会给从服务器发送删除命令。也就是说如果从服务器没有接到删除命令,而有请求向从服务器读过期键,从服务器会返回对应的数据而不是返回过期错误。
RDB持久化
RDB持久化的保存
手动持久化
redis可以使用命令SAVE
阻塞式的保存数据库数据,或者使用BGSAVE
fork子进程保存数据库数据,BGSAVE
不会使数据库阻塞
在redis服务器阻塞的时候,客户端的所有命令都会拒绝,当使用BGSAVE
的时候,redis会对SAVE
、BGSAVE
拒绝,而对BGREWRITEAOF
命令延迟到BGSAVE
命令执行结束后。对其他的命令正常执行。
自动持久化
可以设置redis服务器自动使用BGSAVE
命令持久化。
redisService
的结构体内有saveparam
的结构体数组:
struct saveparam{
time_t seconds;//秒数,表示一段时间内
int changes;//修改数,表示seconds这段时间内需要满足的修改次数
}
当使用命令save seconds changes
时,就会向saveparam数组内存储一个元素,表示如果在seconds秒内,数据库进行了至少changes次修改,则使用BGSAVE
持久化一次数据库,save seconds changes
命令可以运行多次,会产生多个条件
为了满足记录修改数,自动持久化的要求,redisService
结构体内还有另外两个字段dirty
和lastsave
来表明距离上一次成功执行SAVE
命令或者BGSAVE
命令的时间,和距那个时间以来的修改次数。
redis的周期函数serverCron
会默认每隔100ms执行一次,这个函数的职责之一就是检测saveparam
的条件是否满足,如果满足就执行BGSAVE
。
最后,redisServer关于定期保存的结构如下:
struct redisServer{
// 和自动保存相关的属性
long long dirty;//距上一次执行持久化以来数据库的修改次数
time_t lastsave;//上一次持久化的时间,时间戳,单位s
struct saveparam *saveparam;//自动保存的条件
}
RDB持久化的恢复
恢复是自动的,redis服务器在启动时自动检测RDB文件,发现即会使用,并且在恢复过程中阻塞,拒绝其他命令。
RDB文件的格式
RDB文件是二进制文件,包含四部分数据:
* REDIS字符串:5字节,表明这是个RDB文件
* db_version字段:4字节,表明RDB文件的版本
* databases:数据库内容,保存所有非空数据库内容
* EOF:1字节,文件结束符
* check_sum:8字节,无符号整数,校验和
databases结构:
- SELECTDB:1字节,表明一个数据库的开始
- db-number:1字节,2字节或者5字节,一个数据库的编号
- key-value_pairs:数据库的键值对,如果有过期时间,则也包含在其中
key_value_paires结构:
- EXPIRETIME_MS:1字节,常量,当存在过期时间时才有的标志位
- ms:8字节,带符号整型,过期时间戳,如果没有过期时间,则没有该字段
- TYPE:1字节,表明value类型的字段
- key:总是字符串对象
- value:值,各种对象
关于各种对象如何持久化以及如何反持久化,见书P128,在此不记录
AOF持久化
AOF持久化的实现:命令追加、文件写入、文件同步
命令追加
redisServer结构体内有一个sds的缓冲区,新的写命令在执行之后,会将命令内容追加到这个缓冲区中
文件写入与同步
redis服务器使用一个事件循环处理文件事件和时间事件,文件事件诸如客户端的请求和响应的返回,时间事件诸如定时任务
AOF文件的写入是在每一次事件循环之后,写入到文件并判断是否要同步。有alaway,everysec,no三个同步选项。 默认位everysec,即每秒同步一次AOF文件
AOF文件的载入
redis服务器将会模拟客户端,执行一遍AOF文件内的所有命令,因为redis命令只能在客户端上下文运行, 所以redis服务器会创建一个没有网络连接的伪客户端。
AOF文件重写
- AOF记录每一条写命令,随着时间变化,这个AOF文件会越来越大,为了减少AOF文件的体积,使用AOF重写。
- 原理是将旧AOF文件对键值对的多条写命令改为 对键的一条写操作,这并不需要对旧的AOF文件的读取分析,而只需对数据库当前的键值对读,然后写文件即可完成。
- 另外,如果是对列表,哈希表等包含多个键值对的集合操作的话,为了防止写操作造成缓存区溢出,AOF重写会将一次写改为多次写。
- AOF重写会造成当前线程阻塞,所以redis将AOF重写操作放到子进程来做。
- 重写完成后,子进程会向父进程发送信号量,父进程原子性的用新文件将旧文件覆盖掉。
AOF文件重写的不一致问题
可能在重写过程中,数据库存入了新的键值,但这不会记入重写的文件中
解决方法是:当在子进程使用AOF重写函数的时候,redis服务器设置了一个AOF重写缓冲区,每个写命令,不仅发送给AOF缓冲区,还发送给AOF重写缓冲区。