请求命令的执行过程

客户端给服务器发送一个命令请求的过程如下:

1、客户端向服务器发送命令请求

2、服务器接受并处理客户端请求,在数据库中进行设置操作,并产生回复。

3、服务器将命令回复发送给客户端

4、客户端收到回复命令并显示给用户。

发送命令请求

客户端输入请求后,客户端会将这个请求命令转换成协议格式,然后通过连接到服务器的套接字,将协议格式发送给服务器。

读取命令请求

读取命令时,服务器会执行一下操作:

1、读取协议格式的命令,将其保存到客户端状态的输入缓冲区中。

2、对输入缓冲区的内容进行解析,提取其中包含的命令,以及命令的个数,保存到客户端状态的argv和argc的属性当中。

3、调用命令执行器,执行命令。

具体结构如下:

image-20230328152639921

命令执行器(1):查找命令实现

执行器要做的第一件事就是根据客户端状态的argv[0]参数,在命令表中查找指定的命令,并将命令保存在客户端状态的cmd属性中。

一个命令表的结构如下:

image-20230328155212121

设置客户端状态如下:

image-20230328155247458

命令执行器(2):执行预备操作

到这里,服务器已经将客户端执行命令所需要的函数,参数以及参数个数都收集到了。但是在真正执行命令时,还需要一些预备操作:

1、检查客户端状态的cmd属性是否为null,如果为null说明输入命令错误,则不继续执行,返回错误。

2、根据cmd指向的redisCommand结构的arity属性,检查命令请求的参数个数是否正确,不正确直接返回。

3、检查客户端是否通过了身份验证。

4、如果服务器打开了maxmemory属性,那么会先检查服务器内存占用情况,并且在必要时进行内存回收。

5、如果上一次执行bgsave命令出错,并且开启了一个配置,这次命令又是一个写命令,会被直接拒绝。

6、如果客户端当前正在用SUBSCRIBE命令订阅频道,或者正在用PSUBSCRIBE命令订阅模式,那么服务器只会执行客户端发来的SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE四个命令,其他命令都会被服务器拒绝。
7、如果服务器正在进行数据载入,那么客户端发送的命令必须带有1标识(比如INFO、SHUTDOWN、PUBLISH等等)才会被服务器执行,其他命令都会被服务器拒绝。
8、如果服务器因为执行Lua脚本而超时并进人阻塞状态,那么服务器只会执行客户端发来的SHUTDOWN nosave命令和SCRIPT KILL命令,其他命令都会被服务器拒绝。口如果客户端正在执行事务,那么服务器只会执行客户端发来的EXEC、DISCARD、MULTI、WATCH四个命令,其他命令都会被放进事务队列中。
9、如果服务器打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器。当完成了以上预备操作之后,服务器就可以开始真正执行命令了。

以上只针对单机redis,如果是集群,则还要多一点。

命令执行器(3):调用命令函数实现

因为命令参数,以及个数,以及要执行的命令都已经保存,所以调用时只需要给具体函数传送一个redisClient结构体即可。

image-20230328161130267

函数会执行具体的操作,然后将结果保存在客户端状态的输入缓冲区里面。

将命令回复发送给客户端

当客户端的套接字变为可写状态时,服务器就会执行命令回复处理器,将保存到客户端输出缓冲区的命令回复发送给客户端。

serverCron函数

redis的serverCron每隔100ms就会执行一次,这个函数负责管理服务器资源,并保持服务器自身运行状态良好。

更新服务器时间缓存

因为redis很多操作都会涉及到时间,而每次获取时间都需要执行一次系统调用。为了减少系统调用的次数,服务器中的unixtime和mstime被当作时间缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct redisServer {
// 一个数组,保存着服务器中所有的数据库
redisDb *db;

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

// 记录了保存条件的数组
struct saveparam *saveparam;

// 修改计数器
long long dirty;

// 上一次执行保存的时间
time_t lastsave;

// AOF缓冲区
sds aof_buf;

// 一个链表,保存了所有客户端状态
list *clients;

// 秒级的unix时间戳
time_t unixtime;

// 毫秒级的unix时间戳
long long mstime;
};

该函数会更新服务器的时间缓存,由于每100ms更新一次,所以并不是特别准确。

更新LRU时钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct redisServer {
// 一个数组,保存着服务器中所有的数据库
redisDb *db;

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

// 记录了保存条件的数组
struct saveparam *saveparam;

// 修改计数器
long long dirty;

// 上一次执行保存的时间
time_t lastsave;

// AOF缓冲区
sds aof_buf;

// 一个链表,保存了所有客户端状态
list *clients;

// 秒级的unix时间戳
time_t unixtime;

// 毫秒级的unix时间戳
long long mstime;

// 默认每10秒更新一次时钟缓存,用于计算键的空转时间
unsigned lruclock:22;
};

检查持久化操作运行状态

image-20230328164717831

参考

《Redis设计与实现》