Redis设计与实现-RDB与AOF

上一篇文章主要是关于Redis的底层数据结构的实现,本篇是redis设计与实现的第二部分,内容主要是一个单机redis数据库的主要特性特征,本文主要节选redis的RDB持久化和AOF功能。

数据库

数据结构

redis服务器将所有服务器信息封装在redis.h/redisServer中,其中所有的数据库信息都保存在由redisDB结构体元素构成的db数组中,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct redisServer {
...
redisDb *db;//数组,保存服务器中所有的数据库
int dbnum; //数据库的数量
struct saveparam *saveparams; //指向save选项条件
long long dirty; //上一次修改时间
time_t lastsave; //上一次执行保存的时间
sds aof_buf; //AOF缓冲区
}
struct saveparam {
//秒数
time_t seconds;
//修改数
int changes;
};

另外redis将所有关于客户端信息封装在了redis.h/redisClient中:

1
2
3
4
typedef struct redisClient {
...
redisDb *db;//记录客户端正在使用的数据库
}redisClient;

服务器中每个数据库都由一个redis.h/redisDB结构表示,结构体如下:

1
2
3
4
5
6
7
8
9
10
typedef struct redisDb {
dict *dict; //数据库键空间,保存着数据库所有的键值对
dict *expires; //过期字典,保存着所有键的过期时间
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
int id; //数据库ID
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;

重点总结

  1. Redis 服务器的所有数据库都保存在 redisServer.db 数组中, 而数据库的数量则由 redisServer.dbnum 属性保存。
  2. 客户端通过修改目标数据库指针, 让它指向 redisServer.db 数组中的不同元素来切换不同的数据库。
  3. 数据库主要由 dict 和 expires 两个字典构成, 其中 dict 字典负责保存键值对, 而 expires 字典则负责保存键的过期时间。
  4. 因为数据库由字典构成, 所以对数据库的操作都是建立在字典操作之上的。
  5. 数据库的键总是一个字符串对象, 而值则可以是任意一种Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象和有序集合对象,分别对应字符串键、哈希表键、集合键、列表键和有序集合键。
  6. expires 字典的键指向数据库中的某个键(此处不会存在内存空间浪费,因为过期字典中和数据库的键指向同一个字符串对象),而值则记录了数据库键的过期时间, 过期时间是一个以毫秒为单位的 UNIX 时间戳。
  7. Redis使用惰性删除和定期删除两种策略来删除过期的键:惰性删除策略只在碰到过期键时才进行删除操作, 定期删除策略则每隔一段时间, 主动查找并删除过期键。
  8. 执行 SAVE 命令或者 BGSAVE 命令所产生的新 RDB 文件不会包含已经过期的键。
  9. 执行 BGREWRITEAOF 命令所产生的重写AOF文件不会包含已经过期的键。只有当一个过期键被删除之后,服务器会追加一条 DEL 命令到现有 AOF 文件的末尾, 显式地删除过期键。
  10. 当主服务器删除一个过期键之后, 它会向所有从服务器发送一条 DEL 命令, 显式地删除过期键。从服务器即使发现过期键,也不会自作主张地删除它,而是等待主节点发来DEL命令,这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。

RDB持久化

重点总结

  • RDB 文件用于保存和还原 Redis 服务器所有数据库中的所有键值对数据。
  • SAVE 命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。
    备注:只有在服务器执行完SAVE命令、重新开始接受新的请求之后,客户端发送的命令才会被处理。此命令会阻塞所有的命令包括BGSAVE命令。

  • BGSAVE 命令由子进程执行保存操作,所以该命令不会阻塞服务器。
    备注:BGSAVE执行期间,SAVE和BGSAVE都会被拒绝,前者是为了防止二者同时调用rdbSave,后者是因为不能同时执行两个BGSAVE。

  • 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行 BGSAVE 命令。
    备注:具体数据结构可以参照第一节中redisServer结构体。

  • RDB 文件是一个经过压缩的二进制文件,由多个部分组成。
    备注:一个完整的RDB文件组成为:REDIS|db_version|database|EOF|check_sum,其中,REDIS存储“REDIS”五个字符;db_version长度为4个字节,是一个字符串表示的整数,记录了rdb文件的版本号;database包含零个或若干个数据库,以及数据库中的键值对;EOF为一个字节表示RDB正文文件内容的结束;check_sum是一个八字节长的无符号整数,保存前面四部分内容的校验和。

AOF持久化

AOF(Append Only File),感觉就是个日志文件,记录redis服务器的写命令。与RDB同样提供了持久化的功能,二者的实现方法却不相同,RDB是记录Redis服务器的状态来进行持久化,AOF却是保存Redis的写命令来记录数据库的状态。

重点总结

  • AOF 文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
  • AOF 文件中的所有命令都以 Redis 命令请求协议的格式保存。
  • 命令请求会先保存到 AOF 缓冲区里面, 之后再定期写入并同步到 AOF 文件。
    备注:当AOF持久化功能处于打开时候,服务器执行完写命令之后,会按照协议格式将被执行的写命令追加到服务器状态的aof_buf末尾,属性结构参照第一节。

  • appendfsync 选项的不同值对 AOF 持久化功能的安全性、以及 Redis 服务器的性能有很大的影响。
    备注:Redis服务器配置appendfsync直接决定了AOF持久化功能的效率和安全性。appendfsync配置的不同主要决定了AOF文件同步的频率,因为操作系统,当用户调用write函数将一些数据写入到文件时,并不是立即写入,而是暂时保存在一个内存缓冲区中,等到缓冲区填满或者超过了指定的时限之后,才真正将数据写入到磁盘中。appendfsync有三个选项:always、everysec、no。always是每次写入AOF时,都会同步AOF文件,everysec是每隔一秒同步,no则是依赖操作系统,由操作系统决定。

  • 服务器只要载入并重新执行保存在 AOF文件中的命令,就可以还原数据库本来的状态。

  • AOF 重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样, 但体积更小。
  • AOF 重写是一个有歧义的名字, 该功能是通过读取数据库中的键值对来实现的, 程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
  • 在执行 BGREWRITEAOF 命令时, Redis 服务器会维护一个 AOF 重写缓冲区, 该缓冲区会在子进程创建新AOF文件的期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾, 使得新旧两个 AOF 文件所保存的数据库状态一致。 最后, 服务器用新的 AOF 文件替换旧的 AOF 文件, 以此来完成 AOF 文件重写操作。
    备注:AOF 重写缓冲区,是转为AOF重写设置的,在重写期间暂时保存新增的写命令。

-EOF-