你未必出类拔萃,但一定与众不同
Redis 服务器
命令请求的执行过程
一个命令请求从发送到获得回复的完整过程
127.0.0.1:6379> set userId 9527
OK
1.客户端发送命令set userId 9527 给服务器
2.服务器接收并且处理客户端命令请求set userId 9527,在数据库中进行操作,并且成功后产生命令回复OK
3.服务器将命令回复OK发送给客户端
4.客户端接收服务器命令回复OK,并且将回复打印给用户看
图解
发送命令请求
当用户输入一个命令请求时,客户端会将命令转化为协议格式,通过连接到服务器的套接字,将协议格式的命令发送给服务器
读取命令请求
客户端与服务器之间因为客户端的写入而可读时,服务器将调用命令请求处理器来执行以下操作
1.读取套接字中协议格式的命令请求,并且将命令请求保存到客户端状态的输入缓冲区
2.对输入缓冲区中的命令进行分析,提取出命令请求中的命令参数,以及参数个数,然后保存到客户端状态的argv属性(数组中的每个元素都是一个字符串对象)和argc属性(数组的长度)
3.调用命令执行器。执行客户端的命令
图解
然后分析程序就会对输入缓冲区中的协议进行分析,然后保存到客户端状态的argv属性(数组中的每个元素都是一个字符串对象)和argc属性(数组的长度)
命令执行器
(1):查找命令实现
第一件事就是根据客户端状态的argv[0]参数,在命令表里查找参数所指定的命令,并且将找到的命令保存到客户端状态的cmd属性里面
命令表是一个字典,字典的键就是一个一个命令名字
字典的值就是一个个redisCommand结构,每个redisCommand都记录一个redis命令的实现消息
redisCommand结构主要属性
属性名 | 类型 | 作用 |
---|---|---|
name | char * | 命令的名字 |
proc | redisCommandProc * | 函数指针,指向命令的实现函数,比如setCommand |
arity | int | 命令参数的个数,用于检查命令请求的格式是否正确。如果值为-N,表示参数的数量大于等于N(命令的名字本身也是一个参数) |
sflags | char * | 字符串形式的标识值。这个值记录了命令的属性,如读命令、写命令、是否允许在载入时或Lua脚本中使用 |
flags | int | 对sflags标识进行分析得出的二进制标识,由程序自动生成。服务器对命令标识进行检查使用该属性而不是sflags属性,因为对二进制标识的检查可以方便地通过& ^ ~ 等操作来完成 |
calls | long long | 服务器总共执行的命令个数 |
milliseconds | long long | 执行命令的总耗时 |
sflags标识
标识 | 意义 | 带有这个标识的命令 |
---|---|---|
w | 写入命令 | SET RPUSH DEL等 |
r | 只读命令 | GET STRLEN EXISTS等 |
m | 执行前检查服务器内存使用情况,内存紧缺就不执行 | SET APPEND RPUSH LPUSH SADD SINTERSTORE等 |
a | 管理命令 | SAVE BGSAVE SHUTDOWN等 |
p | 发布与订阅命令 | PUBLISH SUBSCRIBE PUBSUB等 |
s | 不可在lua脚本使用 | BRPOP BLPOP BRPOPLPUSH SPOP等 |
R | 随机命令 | SPOP SRANDMEMBER SSCAN RANDOMKEY等 |
S | 对结果执行排序 | SINTER SUNION SDIFF SMEMBERS KEYS等 |
l | 服务器载入数据时使用 | INFO SHUTDOWN PUBLISH等 |
t | 允许从服务器在带有过期数据时使用的命令 | SLAVEEOF PING INFO等 |
M | 在监视器模式下不会被自动传播 | EXEC |
以SET、GET命令示例redisCommand结构:
SET
实现函数为setCommand,命令参数为-3:表示命令接受3个或以上的参数,命令标识为wm:表示SET命令是一个写入命令,并且在执行这个命令之前,服务器应该对内存占用状况进行检查,因为这个命令可能会占用大量内存
GET
实现函数为getCommand,命令参数为2:表示该命令只接受2个参数,命令标识为r:标识是一个只读命令
命令执行器(2):执行预备操作
在真正执行命令之前,还需要一些准备操作
1.检查客户端状态的cmd指针是否指向NULL,是则说明用户输入的命令名字找不到相应的命令实现,剩下的步骤服务器就不执行
2.根据客户端的cmd属性指向的redisCommand结构的arity属性,检查命令请求所给定的参数个数是否正确,当参数个数不正确就不执行后续步骤
3.检查客户端是否通过身份验证,未通过的只能执行AUTH命令
4.如果服务器打开了maxmemory功能,就需要执行命令之前检查服务器的内存。
5.如果服务器上一次执行BGSAVE命令时出错,且服务器打开了stop-writes-on-bgsave-error功能,而服务器要执行的是一个写命令,那么服务器拒绝执行这个命令,并返回一个错误。
6.如果客户端正在用SUBSCRIBE命令订阅频道,或正在用PSUBSCRIBE命令订阅模式,那么服务器只会执行客户端发来的SUBSCRIB、PSUBSCRIBE、UNSUBSCRIB、PUNSUBSCRIBE四个命令,其他命令会被服务器拒绝。
7.如果服务器正在进行数据载入,那么客户端发送的命令必须带有 l 标识(比如INFO、SHUTDOWN、PUBLISH等)才会被服务器执行,其他命令都会被服务器拒绝。
8.如果服务器因为执行Lua脚本而超时并进入阻塞状态,那么服务器只会执行客户端发来的SHUTDOWN nosave命令和SCRIPT KILL命令,其他命令都会被服务器拒绝。
9.如果客户端正在执行事务,那么服务器只会执行客户端发来的EXEC、DISCARD、MULTI、WATCH四个命令,其他命令都会被放进事务队列中。
10.如果服务器打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器。
命令执行器(3):调用命令的实现函数
将要执行的命令会被保存到客户端状态的cmd属性里,执行命令时只要执行喜爱语句
client->cmd->proc(client)
被调用的命令实现函数会执行指定的操作,并且产生相应的命令回复,这些回复被保存在客户端状态的输出缓冲区里,之后实现函数会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。
命令执行器(4):执行后续工作
- 如果服务器执行了慢查询日志功能,那么慢查询日志模块会检查是否需要为刚刚执行完的命令请求添加一条新的慢查询日志
- 根据刚刚执行命令所耗费的时长,更新被执行命令的redisCommand结构的milliseconds属性,并且将命令的redisCommand结构的calls计数器的值加一
- 如果服务器开启了AOF持久化功能,那么AOF持久化模块会将刚刚执行的命令请求加入到AOF缓冲区中
- 如果有其他从服务器正在复制当前这个服务器,那么服务器会将刚刚执行的命令传播给其他所有服务器
以上操作执行完以后,服务器就会开始执行下一个命令
将命令回复发送给客户端
命令实现函数会将命令回复保存到客户端的输出缓冲区中,并且为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态,服务器就会执行命令回复处理器,将保存在客户端的输出缓冲区中的命令回复给客户端
当命令回复发送完毕用户,回复处理器会清空客户端状态的输出缓冲区,准备处理下一个请求
客户端接收并打印命令回复
当客户端接收到协议格式的命令会转化为人类可读的格式然后打印在客户端给用户看。