(1)结构
是否伪客户端int fd;记录客户端正在使用的socket描述符fd=-1:伪客户端,AOF载入或Lua脚本 fd>-1:普通客户端client自身属性robj *name;客户端名字CLIENT setname命令可以更改nameint flags;属性标志记录客户端的role(状态)eg. REDIS_MASTER | REDIS_MULTI:该客户端是一个主服务器并且正在执行事务time_t ctime; time_t lastinteraction; time_t obuf_soft_limit_reached_time;记录了创建客户端的时间; 记录了客户端与服务器最后一次互动时间; 记录了输出缓冲区第一次到达软性限制的时间用于计算age:连接了多少秒 用于计算空转时间 用于缓冲区大小限制与Server交互相关sds querybuf;输入缓冲区,用于保存客户端发送的命令请求必须<1GB,不然将自动关闭客户端 eg.如果客户端向服务器发送SET key value。则会缓冲区将包含这个内容robj **argv; int argc;服务器对缓冲区内容分析之后, 会将命令参数及对应个数存在argv和argc中eg. SET key value argv[0]="SET" argv[1]="key" argv[2]="value" argc=3 共三个参数struct redisCommand *cmd;服务器根据argv和argc查找对应的实现函数,保存在redisCommand命令表中将cmd指向命令表,根据这个查找对应的实现函数char buf[REDIS_REPLY_CHUNK_BYTES]; int bufpos;固定大小的输出缓冲区用于保存长度比较小的回复,比如OK,整数值,回复错误等默认REDIS_REPLY_CHUNK_BYTES = 16KB bufpos用于记录已经使用的字节数量list *reply;可变大小的缓冲区用于保存长度比较大的回复, 比如长字符串,集合等链表形式,每个链接节点存储一个StringObject身份验证struct redisCommand *cmd;身份验证,=0表示未通过身份验证,=1表示验证成功如果开启身份验证:=0时,所有命令都会被拒绝。未开启身份验证:该属性无作用 AUTH用于验证命令 身份信息可以通过conf中requirepass配置 typedef struct redisClient{ //记录客户端正在使用的socket描述符 int fd; //客户端名字 robj *name; //属性标志记录客户端的角色 int flags; //输入缓冲区,用于保存客户端发送的命令请求 sds querybuf; //服务器对缓冲区内容分析之后,会将命令参数及对应个数存在argv和argc中 robj **argv; int argc; //服务器根据argv和argc查找对应的实现函数,保存在redisCommand命令表中 struct redisCommand *cmd; //两个输出缓冲区 //固定大小的缓冲区用于保存长度比较小的回复,比如OK,整数值,回复错误等 char buf[REDIS_REPLY_CHUNK_BYTES]; int bufpos; //可变大小的缓冲区用于保存长度比较大的回复,比如长字符串,集合等 //链表形使,每个链接节点存储一个StringObject list *reply; //身份验证,=0表示未通过身份验证,=1表示验证成功 int authenticated; //记录了创建客户端的时间,用于计算age:连接了多少秒 time_t ctime; //记录了客户端与服务器最后一次互动时间,用于计算空转时间 time_t lastinteraction; //记录了输出缓冲区第一次到达软性限制的时间,用于缓冲区大小限制 time_t obuf_soft_limit_reached_time; }redisClient;(2)创建与关闭
创建:新Client添加到RedisServer的链表末尾 关闭:有多种原因都会引起Client关闭 客户端退出或者被杀死客户端向服务端发送了不符合协议格式的命令timeout:如果配置了timeout属性,空转时间>timeout。(当然也有例外,主从模式,订阅等情况)客户端发送的命令请求超过输入缓冲区大小(默认1GB)客户端接受的回复超过输出缓冲区大小(3)伪客户端
Lua脚本和AOF文件载入都会创造伪客户端
(1)结构
与redisDb相关redisDb *Db;一个数组保存服务起中所有数据库链表形式int dbnum;记录数据库大小,默认16 与redisClient相关list *clients;数组保存所有Client客户端链表形式,新加入的客户端加入链表尾自动RDB相关struct saveparam *saveparams;记录了保存条件的数组与conf配置中的save相关long long dirty;修改计数器记录距离上次RDB到现在一共更改了多少次time_t lastsave;上一次执行保存的时间 AOF相关sds aof_buf;AOF缓冲区 int aof_rewrite_scheduled;记录服务器中 BGREWRITEAOF 命令执行是否被延迟当=1时,会将 BGREWRITEAOF 命令延迟到 BGSAVE 命令执行成功后再执行BGSAVE和BG重写pid_t rdb_child_pid;
pid_t aof_child_pid;
记录执行 BGSAVE的子进程ID
记录执行BGREWRITEAOF的子进程ID
都=-1:表示目前没有执行相关持久化
有一个!=-1:表示后台在持久化,需要实时检测是否完成和执行后续操作(比如替换原有RDB文件,AOF重写追加等)
当前时间戳time_t unixtime;秒级精度的系统当前UNIX时间戳 long long mstime;毫秒级精度的系统当前UNIX时间戳 内存size_t stat_peak_memory;更新内存峰值记录从开始到现在的最大峰值,每次serverCron执行都会更新空转时长unsigned lruclock:22;记录了服务器的LRU时钟。serverCron 以每 10 秒一次的频率更新 lruclock 属性的值。
LRU 时钟不是实时的,它只是一个模糊的估计值。
拿该值减去redisObject的lru属性,就是空转时间serverCron执行次数int cronloops;记录serverCron函数执行的次数唯一作用是:在复制模块中实现“每执行serverCron函数N次就执行执行代码”
if(cronloops % N == 0){代码....}
struct redisServer{ //AOF相关 //AOF缓冲区 sds aof_buf; //RDB相关 //记录了保存条件的数组 struct saveparam *saveparams; //修改计数器 long long dirty; //上一次执行保存的时间 time_t lastsave; //一个链表,保存了所有客户端状态 list *clients; //秒级精度的系统当前UNIX时间戳 time_t unixtime; //毫秒级精度的系统当前UNIX时间戳 long long mstime; //默认每10s更新一次的时钟缓存 //用于计算key的空转时长 unsigned lruclock:22; //已使用内存峰值 size_t stat_peak_memory; //用于延迟执行重写aof int aof_rewrite_scheduled; }(2)初始化
初始化的工作由initServerConfig函数完成
初始化状态结构:设置运行ID,运行频率,conf路径,默认端口号,是否RDB/AOF,LRU时钟,创建命令表载入配置:更改配置初始化其他 维护clients链表,db数组,频道订阅,慢查询日志等设置信号处理器,创建共享对象(如1到10000的字符串对象),运行serverCron函数,初始化I/O模块根据RDB或者AOF还原数据库执行事件循环(loop)以客服端执行 set key value指令为例,描述整个交互流程
客户端发送命令 这个命令将被转换成协议发送给服务器,格式如下服务器读取命令 根据socket和协议,将其保存到客户端中的querybuf输入缓冲区分析querybuf中的命令,解析完成后保存在客户端中的argv和argc准备执行该指令命令执行器1:查找命令实现 根据argv[0]的参数(eg. SET),在命令表中查找命令,并保存到客户端的cmd属性中命令执行器2:执行预备校验 目前为止,服务器准备工作已经完成(cmd记录了实现函数地址,argv保存了参数,argc保存了参数个数)执行前需要进行检查:cmd指向的实现函数是否为null,参数个数是否正确,身份验证是否正确等...命令执行器3:调用实现函数 服务器执行实现函数。将回复保存到redisClient的输出缓冲区中(buf和reply)命令执行器4:执行后续工作 如果执行太慢,假如慢查询日志;如果开启了AOF,则写入AOF缓冲;如果其他服务器正在复制,则发送到其他服务器将结果返回给客户端 客户端接受回复命令并打印,服务器清空客户端的输出缓冲区该函数每100毫秒执行一次,执行一次需要做12件事
更新服务器时间缓存:unixtime和mstime 这两个属性用于对精度要求不高的功能:如 打印日志,更新服务器LRU时间,决定是否持久化,计算服务器上线时间对于过期时间,添加慢查询等要求高的功能,服务器会额外执行系统调用保证精准时间更新lruclock 该属性记录了服务器的LRU时间(上次被使用的时间戳),默认每10秒更新一次,是一个模糊的估算值计算redisObject空转时间时就用 redisServer.lruclock属性 - redisObject.lru属性 得出空转时间更新服务器内存峰值:stat_peak_memory属性(最大使用情况) 每次serverCron函数执行时,程序都会查看服务器当前内存使用情况,如果>stat_peak_memory,则替换更新服务器每秒执行命令数 serverCron中的trackOperationsPerSecond函数每100毫秒执行一次,通过抽样方式来估算 服务器在最近一秒内处理的命令数量处理sigterm信号管理客户端资源 serverCron每次都对一定数量的客户端进行检查客户端很长时间没有和服务器响应(>timeout),服务器认为该客户端超时,则会断开和该客户端的连接。当客户端在上一次执行命令请求后,输入缓冲区超过规定的长度,程序会释放输入缓冲区,并创建一个默认大小的缓冲区,防止缓冲区过分消耗。关闭输出缓冲区超出大小限制的客户端。管理数据库资源 主要是检查键是否过期,并且按照配置的策略,删除过期的键。如懒惰删除、定期删除等。执行被延迟的bgrewriteaof命令 属性aof_rewrite_scheduled记录是否有延迟的bgrewriteaof命令。 当执行bgsave命令期间,如果接收到bgrewriteaof命令,不会立即执行该命令,而是会将属性aof_rewrite_scheduled置成1。每次执行serverCron函数执行时,发现属性aof_rewrite_scheduled是1,会检查当前是否在执行bgsave命令或bgrewriteaof命令,如果没有在执行这两个命令,则会执行bgrewriteaof命令检查持久化操作的运行状态 检查rbd_child_pid和aof_child_pid的值都为-1:目前没有执行持久化 (1)如果存在BGREWRITEAOF延迟,执行BGREWRITEAOF(2)如果save配置中自动保存RDB满足,执行BGSAVE(3)AOF重写的条件是否满足?执行BGREWRITEAOF:不做动作将aof缓冲区内容写入osBuffer 如果开启aof,redis每次写命令都会记录到aof缓冲区中serverCron每100毫秒都会将缓冲区中的内容写入osbuffer中每次loop会根据appendfsync的策略将osbuffer写入到文件中关闭异步客户端 关闭输出缓冲区超出限制的客户端cronloops++ redis用属性cronloops保存serverCron函数执行的次数。当执行一次serverCron,则会将属性值加1。这个值目前的唯一作用,在复制模块中实现“每执行serverCron函数N次就执行执行代码” if(cronloops % N == 0){自定义代码....}