一、客户端的属性
typedef struct redisClient{ ...
... }redisClient;
1.套接字描述符
int fd;
标识客户端和伪客户端
伪客户端:fd = -1,两种情况,载入AOF文件并还原数据库状态;执行Lua脚本中包含的Redis命令。
客户端:fd > -1
2.名字
robj* name;
默认无名,指针指向空,通过【client list】看名字,通过【client setname】设置名字
3.标志
int flags;
标志客户端所处的状态,主从复制操作、
4.输入缓冲区
sds querybuf;
不可超过1G,否则关闭客户端,
5.命令和命令参数
robj** argv; int argc;
argv指向一个数组,每个项都是字符串对象,argc则表示数组长度
例如argv[0]="set",argv[1]="name",argv[2]="shoulinniao",argc=3;
6.命令的实现函数
struct redisCommand* cmd;
argv[0]是一个命令,在命令字典里找到,再将cmd指向这个命令的redisCommand,然后根据这个redisCommand以及argv、argc中的信息调用命令实现函数,执行命令。
7.输出缓冲区
执行命令后所得的回复会放在输出缓冲区,有2个输出缓冲区。
固定大小缓冲区:
char buf[ REDIS_REPLY_CHUNK_BYTES ];//16*1024=16KB
int bufpos;//保存已使用的字节数量
大小可变缓冲区:buf用完就启用这个
list* reply;//拉出一条有3个字符串对象的链表
8.身份验证
int authenticated;//0表示未通过验证,1表示通过验证
【auth ***】命令验证身份,状态为0时只能用这个命令。
9.时间
time_t ctime;//创建客户端的时间
time_t lastinteraction;//客户端和服务器的最后一次交互时间
time_t obuf_soft_limit_reached_time;//距离lastinteraction过了多少秒
二、客户端的创建与关闭
1.创建普通客户端
客户端使用connect函数连接到服务器时,服务器就会调用连接事件处理器,为客户端创建相应的客户端状态,并将这个客户端状态添加到服务器状态结构clients链表的末尾。
2.关闭普通客户端
- 客户端进程退出或者被杀死
- 客户端向服务器发送不符合协议格式的命令,被服务器关闭
- 设置timeout超时配置选项
- 客户端请求命令超过输入缓冲区的限制(1GB) 或 发给客户端的回复命令超过输出缓冲区大小(硬性限制:超过限制,马上被关闭;软性限制:超过软性限制但是不超过硬性限制,太久就被关闭,不久恢复就不会被关闭)
3.Lua脚本的伪客户端
struct redisServer{ //... redisClient* lua_client; //... };
服务器会在初始化时创建负责执行Lua脚本中包含Redis命令的伪客户端,并将这个伪客户端关联在服务器的lua_client属性中。
4.AOF文件的伪客户端
载入AOF文件时,服务器会创建伪客户端执行AOF文件中的Redis命令,载入完成后关闭。
三、命令请求的执行过程
1.发送命令请求
用户-----键入命令----->客户端-----将命令转换为协议格式----->服务器
2.读取命令请求
- 读取协议格式的命令请求,并保存到输入缓冲区
- 对输入缓冲区的命令进行分析,提取参数和个数保存到argv和argc里
- 调用命令执行器执行客户端指定的命令
3.命令执行器
(1)根据argv[0]参数在命令表(一个字典)里查找命令,并将找到的命令保存到cmd属性里(cmd指向一个redisCommand)
(2)执行前检查。cmd指向是否为null?不为null的话redisCommand参数个数是否正确?是否通过身份验证?还有一些内存占用、持久化、订阅、Lua脚本、监视器等情况。
(3)调用命令的实现函数。根据指向的redisCommand执行操作,将产生的回复 保存在 输出缓冲区里,再关联命令回复处理器将 命令回复 返回给客户端。
(4)执行后续操作。若开启慢日志查询功能则补充日志;根据执行时长更新redisCommand结构的milliseconds属性,calls+1;如果开启AOF持久化功能,补充命令到AOF缓冲区;如果有从服务器正在复制,则将刚刚执行的命令传给所有从服务器。
4.命令回复处理器将 命令回复 返回给客户端并清除输出缓冲区
5.客户端接收回复并打印
服务器-----协议格式的命令回复----->客户端-----解析成人类看得懂的格式并打印----->用户
四、服务器的serverCron函数
默认每100ms执行一次,看看它干了啥
1.更新服务器时间缓存
struct redisServer{ //.. time_t unixtime;//秒级精度当前时间戳 long long mstime;//毫秒级精度当前时间戳 //.. };
每100ms更新一次时间,精度不高。
- 对于打印日志、更新服务器LRU时钟、决定是否执行持久化任务、计算服务器上线时间这类对时间精度要求不高的操作,将就着用;
- 对于键过期、添加慢查询日志这类要求高精度时间的操作,服务器还是会再更新一次系统时间;
2.更新LRU时钟
3.更新服务器每秒执行命令次数
4.更新服务器内存峰值记录
5.处理SIGTERM信号,即判断是否关闭服务器,关闭前执行RDB持久化操作
6.管理客户端资源,释放连接超时的客户端,释放超过一定长度的输入缓冲区并重建
7.删除过期键,必要时收缩字典
8.执行被延迟的BGrewriteAOF,该命令是因为服务器正在执行BGsave操作才会被延迟,记录一个标识(int aof_rewrite_scheduled),通过判断标识看有没有被延迟的BGrewriteAOF需要执行。
9.检查持久化的运行状态
10.将AOF缓冲区写入AOF文件
11.记录serverCron函数执行次数的 cronloops变量+1,用于模N实现“每执行N次serverCron函数就执行一次指定代码”的功能。
五、初始化服务器
服务器从启动到能够接受客户端请求,有一系列的操作
1.初始化服务器状态结构,默认各种选项,如运行ID、默认运行频率、端口号、默认文件路径、持久化条件、命令表等
2.载入配置选项,改掉某些默认值
3.初始化服务器数据结构,目前只造了一张命令表,还有其他数据结构需要初始化。
- 保存客户端信息的server.clients链表
- 包含服务器所有数据库的server.db数组
- 保存频道订阅信息的server.pubsub_channels字典,保存模式订阅信息的server.pubsub_patterns链表
- 用于执行Lua脚本的server.lua环境
- 用于保存慢查询日志的server.slowlog属性
初始化这些数据结构需要先载入配置选项,可能有些数据结构被改掉了。
在对数据初始化的过程中,还干了一些其他设置操作,设置信号处理器、创建共享对象等
4.通过AOF或RDB文件回复数据库
5.执行事件循环,打印出日志【The server is now ready to accept connections on port 6379】即可以接受客户端的请求了。
参考&引用
《redis设计与实现》