Redis数据库的实现

1、数据库

1.1、服务器中的数据库

1
2
3
4
5
6
7
8
9
10
struct redisServer {
// ...
// 一个数组,保存着服务器中的所有数据库
redisDb *db;

// 服务器的数据库数量
int dbnum;

// ...
}

数据库的数量由dbnum属性决定,默认情况下,Redis会创建16个数据库。

1.2、切换数据库

默认情况下,redis客户端的目标数据库为0号数据库,可以通过SELECT命令来切换目标数据库。

1
2
3
4
5
6
typde struct redisClient {

// ...
// 记录当前客户端正在使用的数据库
redisDb *db
} redisClient;

redis-cli客户端会在输入符旁边提示当前所使用的数据库:
redis[1] > SELECT 2
但是其他语言客户端却没有,为了避免操作错误数据库,执行Redis命令(如FLUSHDB)之前,最好先执行以下SELECT命令。

1.3、数据库键空间

1
2
3
4
5
6
7
typedef struct redisDb {
// ...
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;

// ..
}

数据库键操作

添加新键
redis> SET data “2016.07.31”
redis> RPUSH list “a” “b” “c”
redis> HSET hashTable name “test”

删除键
redis> DEL book

更新键
redis> SET data “2016.08.01”

对键取值
redis> GET str
redis> LRANGE list 0 -1

读写键空间时的维护操作

  • 记录命中和没命中次数(INFO stat的keyspace_hits和keyspace_missess属性);
  • 更新键的LRU时间(OBJECT idletime 查看闲置时间);
  • WATCH监视的键有改动,键会被标为脏,让事务程序注意到这个键已经被修改了;
  • 服务器每修改一个键之后,都会对脏键计数值加1,这个计数器会触发服务器的持久化和复制操作;
  • 数据库通知功能相关处理。

1.4、设置键的生存时间或过期时间

EXPIRE和PEXPIRE命令

redis> EXPIRE KEY 5 // 缓存5秒
redis> EXPIREAT key 1377252100

TTL和PTTL命令

返回距离这个键被服务器自动删除还有多长时间。

redisDb中的expires字典保存了所有键的过期时间。

PERSIST

在过期字典中查找给定的键,并解除键和值在过期字典的关联。

1.5、过期键删除策略

定时删除

会对服务器的响应时间和吞吐量造成影响。

惰性删除

如果一个过期的键一直没被访问到,那么永远也不会被删除。

定期删除

每个一段时间进行一次删除操作,限制删除操作执行的时长和频率减少删除操作对CPU时间的影响。

1.6、Redis的过期键删除策略

Redis使用惰性和定期删除两种策略。

1.7、AOF、RDB和复制功能对过期键的处理

执行SAVE或者BGSAVE命令创建一个新的RDB文件时,程序会对键进行检查,已过期的键不会被保存到新创建的RDB文件中。

载入的过程中,如果是主服务器,只会载入未过期的键;如果是从服务器模式,所有键都会被载入,不过在进行数据同步的时候,从服务器的数据库就会被清空,所以过期键对RDB文件的从服务器不会造成影响。

如果以AOF模式持久化数据,当过期键被惰性或者定期删除的时候,会向AOF文件追加一条DEL命令。

复制

主服务器删除一个过期键之后,会显示地向所有从服务器发送一个DEL命令。

从服务器存在过期的键,客户端从该从服务器继续获取该键,仍可以获取到;但是从主服务器获取,主服务器发现该键已过期,向客户端返回空回复,并向从服务器发送DEL message命令。

1.8、数据通知

键空间通知

获取0号数据库中针对message键执行的所有命令:
redis> SUBSCRIBE _ keyspace@0 :message
获取0号数据库中所有执行DEL命令的键:
redis> SUBSCRIBE
keyevent@0 _:del

服务器配置的notify-keyspace-events选项决定了服务器所发送通知的类型

发送通知

void notifyKeyspaceEvent(int type, char event robj key, int dbid)

程序会根据type这个值来判断通知是否就是服务器配置notify-keyspace-events选项所选定的通知类型,如果是。

例如:notifyKeyspaceEvent(REDIS_NOTIFY_SET, “sadd”, c->argv[1], c->db->id);

当SADD命令向集合成功的添加元素,命令就会发送。

发送通知的实现

2、RDB持久化

2.1、RDB文件的创建与载入

生成RDB文件的命令:

  • SAVE:会阻塞服务器进程,直到RDB文件创建完为止,阻塞期间,服务器不能处理任何请求命令;
  • BGSAVE:会派生子进程负责创建RDB文件

实际由rdb.c/rdbSave函数完成

RDB文件的载入是在服务器启动时自动执行的,期间服务器一直会处于阻塞状态。Redis没有专门用于载入RDB文件的命令。

如何选择载入方式?

如果开启了AOF持久化功能,服务器会优先使用AOF文件还原数据;
AOF持久化功能处于关闭的情况下,服务器使用RDB文件来还原数据库状态;
相关函数:rdb.c/rdbLoad

SAVE,BGSAVE和BGREWRITEAOF有什么区别?

  • SAVE命令执行会阻塞服务器,客户端所有命令都会被拒绝;
  • BGSAVE命令有子进程执行,创建RDB文件过程中仍然可以继续处理客户端命令请求,但是此时,客户端发送的SAVE命令会被服务器拒绝,避免父进程和子进程同事执行两个rdbSave调用,防止产生竞争条件。同事,客户端再发送BGSAVE命令也会被拒绝。
  • BGREWRITEAOFhe BGSAVE命令不能同时执行,执行BGSAVE期间发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。执行BGREWRITEAOF期间,BGSAVE命令会被服务器拒绝。(BGSAVE和BGREWRITEAOF虽然都是在子进程里面执行的,不能同时执行是出于服务器性能考虑的)

2.2、自动间隔性保存

Redis运行用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令:

save 900 1
save 300 10
save 60 10000 # 服务器在60秒内,对数据库进行了至少10000次修改,则执行BGSAVE命令

2.3、RDB文件结构

固定REDIS开头,0006是版本号,最后是文件校验和。

key_value_pairs保存了数据库中所有键值对数据。

key_value_pairs具体是如何存储键值对的?

可分为不带过期和带过期时间的键值对:


其中type表示编码类型,key是一个字符串,value因type的不同结构也会不一样。

key_value_pairs中的value是如何选择编码的

字符串对象:如果是REDIS_ENCODING_RAW,又可根据长度是否大于20字节分为压缩和不压缩两种方式。(需要打开RDB文件压缩功能:redis.conf的rdbcompression),压缩格式如下:

列表对象:

REDIS_ENCODING_LINKEDLIST:

每一个item又是一个字符串对象。

集合对象:

REDIS_ENCODING_HT:

有序集合对象:

REDIS_ENCODING_SKIPLIST:

哈希表对象:

REDIS_ENCODING_HT:

ZIPLIT编码的列表、哈希表或者有序集合

将压缩列表转换成一个字符串对象,保存到RDB文件中。

2.4、分析RDB文件

可以通过od命令来分析RDB文件

redis> od -c dump.rdb

3、AOF持久化

AOF即:Append Only File。

RDB持久化与AOF持久化有什么不同?

RDB通过保存数据库中的键值对来记录数据库状态的不同,AOF持久化通过保存Redis服务器所执行的命令来记录数据库状态。

3.1、实现

AOF持久化实现分为:命令追加(追加到aof_buf),文件写入,文件同步。

Redis是如何实现AOF文件的写入和同步的?

Redis在一个事件循环里,每次都做判断,根据服务器的appendfsync配置选择对应的策略来进行AOF文件的写入和同步。

3.2、AOF文件的载入与数据还原

  • 创建fake client
  • 从AOF文件分析并取出一条写命令
  • 使用伪客户端执行被读出的命令
  • 循环上面三步

3.3、AOF重写

什么是AOF重写,为什么要执行AOF重写?

为了节省AOF文件的空间,去除冗余的命令。

通过调用aof_rewrite函数进行重写,在子进程中进行重写,重写过程新发送的命令按如下处理:

4、事件

Redis是一个事件驱动程序,服务器处理文件事件和时间时间两类事件。

4.1、文件事件

基于Reactor模式开发的网络事件处理器。使用IO多路复用同时监听多个套接字,根据套接字执行的任务为套接字关联不同的事件处理器。

IO多路复用程序所有功能都是通过封装常见的select、epoll、evport、kqueue这些I/O多路复用函数库来实现的。

常用的文件事件处理器?

连接应答处理器:对连接服务器监听套接字的客户端进行应答,Redis服务器进行初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来,引发连接应答处理器执行,并执行相应的套接字应答操作。

命令请求处理器:从套接字中读入客户端发送的命令请求内容,当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来,当客户端向服务器发送命令请求的时候,套接字就会产生AE_READABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作。

命令回复处理器:当服务器有命令回复需要传送给客户端的时候,服务器会将客户端套接字的AE_WRITEABLE事件和命令回复处理器关联起来,当客户端准备好接受服务器传回的命令回复时,就会产生AE_WRITEABLE事件,引发命令回复处理器执行,并执行相应的套接字写入操作。

请简述下一次完整的客户端与服务器连接事件

4.2、时间事件

4.3、事件的调度与执行

5、客户端

5.1、客户端属性

5.2、客户端的创建与关闭

6、服务器

6.1、命令请求的执行过程

6.2、serverCron函数

6.3、初始化服务器