1、数据库
struct redisServer{
// 一个数组,保存服务器中所有数据库
redisDb *db;
// 服务器的数据库数量,默认16
int dbnum;
};
typedef struct redisClient{
/** 记录客户端正在使用的数据库
* 指向redisServer.db数组的其中一个元素*/
redisDb *db;
} redisClient;
typedef struct redisDb{
// 数据库键空间,保存所有键值对
dict *dict;
// 过期字典,保存键的过期时间
dict *expires;
} redisDb;
默认端口6379;
数据库的键空间用字典实现;
redisDb结构的expires字典保存了数据库中所有键的过期时间,过期字典的值是一个long long类型的整数,是一个毫秒精度的UNIX时间戳;
过期键删除策略:【1】定时删除,要创建大量定时器,不现实【2】惰性删除,取出键时才对键进行过期检查,会浪费内存【3】定期删除,定期执行删除函数,从一定数量的数据库中,取一定数量的随机键,删除过期键;Redis使用惰性删除和定期删除两种策略;
如果是从服务器不会检查键是否过期;
如果使用AOF,当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一条DEL命令;AOF重写时忽略过期键;
从服务器的过期键由主服务器控制,从服务器不会删除过期的message键,而是继续将message键返回给客户端,直到主服务器像它发送DEL message命令;
2、持久化
内存数据库,需要持久化。RDB执行周期长,可能会丢失几分钟的数据,不适合防止数据丢失,但是适合备份数据;AOF可以设置不同的同步策略,丢失数据少,但文件较大;
服务器优先使用AOF文件还原数据。
用户可以设置服务器配置的save选项,每隔一段时间自动BGSAVE,默认配置如下,如果服务器在900s内改动1次,或者300s内改动10次,或者60s内改动10000次,会执行BGSAVE,保存条件的数组保存在redisServer中。
save 900 1
save 300 10
save 60 10000
struct redisServer{
// 记录了保存条件的数组
struct saveparam *saveparams;
// 修改计数器
long long dirty;
// 上一次保存的时间
time_t lastsave;
};
struct saveparam{
// 秒数
time_t seconds;
// 修改数
int changes;
};
redisServer还维护dirty计数器,表示上一次成功执行SAVE或BGSAVE之后的修改计数,lastsave表示上次保存时间;
Redis服务器的周期性函数ServerCron每100 ms执行一次,其中一项工作就是检查dirty是否满足saveparams的条件;
RDB文件保存的是二进制数据,结构如下,后面两张分别是数据库部分和key_value_pairs部分;
(2)AOF:RDB通过保存键值对来记录数据库状态,AOF保存Redis服务器执行的写命令,分为命令追加、文件写入、文件同步三个步骤。
struct redisServer{
// AOF缓冲区
sds aof_buf;
};
always、everysec、no三种同步策略。
Redis提供了AOF重写功能解决AOF体积膨胀的问题,适合放到子进程执行,子进程带有服务器进程的数据副本。执行BGREWRITEAOF命令时,服务器维护一个重写缓冲区,在子进程创建AOF文件期间记录服务器的写命令,最后再将缓冲区追加到新AOF文件的末尾;
3、事件
Redis是单线程的,对文件事件和事件时间的处理都是同步、有序、原子的。
使用IO多路复用程序同时监听多个套接字的accept、read、write、close操作。多个文件事件会并发出现,IO多路复用程序将产生事件的套接字放入队列。IO多路复用程序的功能通过包装常见的select、epoll、evport、kqueue这些IO多路复用函数库实现,套接字既可读又可写时先读后写。
事件处理器中最常用的是连接应答处理器(处理accept)、命令请求处理器(read)、命令回复处理器(write);
(2)时间事件,有定时事件和周期性事件两种,目前Redis只有周期性事件,主要是serverCron,所有的时间事件都放在一个无序链表中,时间事件执行器运行时要遍历链表,但链表中通常只有serverCron一项。serverCron每100 ms执行一次,主要工作包括【1】更新服务器的各类统计信息,时间、内存占用、数据库占用情况等【2】清理过期键【3】关闭和清理连接失效的客户端【4】尝试进行AOF或RDB持久化【5】如果是主服务器,定期对从服务器进行同步【6】如果处于集群模式,进行定期同步和连接测试;
4、客户端
struct redisServer{
// 一个链表,保存了所有客户端状态,即List<redisClient>
list *clients;
};
typedef struct redisClient{
// 套接字描述符,是伪客户端时为-1,即AOF文件或Lua脚本的情况,不需要套接字连接
int fd;
// 客户端名字,可以用CLIENT setname命令设置
robj *name;
// 标志,表示客户端的角色和状态,比如是否主从、阻塞、在执行事务、处于集群下等等
int flags;
// 输入缓冲区,保存客户端的命令请求
sds querybuf;
// 命令的数组,例如{"set","key","value"}
robj **argv;
// argv数组长度
int argc;
// 指向要执行的命令,服务器查找命令表获得command,命令不区分大小写
struct redisCommand *cmd;
// 固定大小的输出缓冲区,保存命令回复
char buf[REDIS_REPLY_CHUNK_BYTES];
// buf数组已使用字节数量
int bufpos;
// 可变缓冲区,链表实现
list *reply;
// 是否通过身份认证
int authenticated;
// 创建客户端的时间
time_t ctime;
// 与服务器最后一次互动时间
time_t lastinteraction;
// 客户端空转时间
time_t obuf_soft_limit_reached_time;
} redisClient;
伪客户端有载入AOF文件和Lua脚本两种情况,无需套接字;
5、服务器
根据argv[0]在命令表里查找指定命令,保存在redisClient的cmd属性里,命令名字的大小写不影响命令表的查找结果。
serverCron管理服务器资源,如下:
(1)更新服务器时间缓存,更新redisServer的time_t unixtime(秒级精度时间戳)和long long mstime(毫秒级精度时间戳)属性;
(2)更新LRU时钟,即redisServer里的unsigned lruclock,默认每10s更新一次,用于计算键的空转时长;
(3)更新服务器每秒执行命令次数;
(4)更新服务器内存峰值记录;
(5)处理SIGTERM信号,是关闭服务器的信号;
(6)管理客户端资源;
(7)管理数据库资源,过期检查、扩容、收缩等;
(8)执行被延迟的BGREWRITEAOF,执行BGSAVE期间的BGREWRITEAOF命令会被延迟执行;
(9)检查持久化操作的运行状态;
(10)将AOF缓冲区的内容写入AOF文件;
(11)关闭异步客户端,关闭输出缓冲区超出限制的客户端;
(12)增加cronloops计数,表示serverCron函数的执行次数;