- redisClient
- 结构
- 创建与关闭
- 伪客户端
- redisServer
- 结构
- 初始化
- Client和Server交互过程
- serverCron时间事件函数(12件事)
1.redisClient
(1)结构
是否伪客户端 | int fd; | 记录客户端正在使用的socket描述符 | fd=-1:伪客户端,AOF载入或Lua脚本 fd>-1:普通客户端 |
client自身属性 | robj *name; | 客户端名字 | CLIENT setname命令可以更改name |
int 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文件载入都会创造伪客户端
2.redisServer
(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)
3.服务器与客户端的交互
以客服端执行 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缓冲;如果其他服务器正在复制,则发送到其他服务器
- 将结果返回给客户端
- 客户端接受回复命令并打印,服务器清空客户端的输出缓冲区
4.serverCron函数
该函数每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){自定义代码....}