本文主要介绍Redis客户端实现,包括客户端状态,与服务器的交互等内容。
Redis是一对多服务器程序,通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式处理命令请求,并与多个客户端进行网络通信。
客户端状态由RedisClient结构保存,服务器都为每个客户端维持一个这样的结构,其中包括的信息有:
· 客户端的套接字描述符;
· 客户端的名字;
· 指向客户端正在使用的数据库的指针,以及该数据库的号码;
· 客户端当前要执行的命令、命令的参数、以及执行命令实现函数的指针;
· 客户端的输入缓冲区与输出缓冲区;
· 客户端的复制状态信息,以及进行复制所需的数据结构;
· 客户端执行BRPOP
,BLPOP
等列表阻塞命令时使用的数据结构;
· 客户端的事务状态,以及执行watch
命令时使用的数据结构;
· 客户端执行发布与订阅功能时使用的数据结构;
· 客户端的身份验证标志;
· 客户端的创建时间,与服务器最后一次通信时间,以及客户端的输出缓冲区大小超出软性限制的时间。
Redis服务器状态结构的clients属性是一个链表,这个链表保存了所有与服务器连接的客户端的状态结构,对客户端执行批量操作,或者查找某个指定的客户端,都可以通过遍历client链表完成。
struct redisServer {
// ...
list *clients;
// ...
};
下图展示了这种结构:
I、客户端属性
1.1 套接字描述符
客户端状态的fd属性记录了客户端正在使用的套接字描述符:
typedef struct redisClient {
//...
int fd;
//...
} redisClient;
伪客户端的fd属性为-1,这包括载入AOF的伪客户端以及用于执行Lua脚本的伪客户端。
普通客户端的fd为大于-1 的整数。
执行CLIENT list
命令可以列出目前所有连接到服务器的普通客户端。
1.2 名字
可以使用CLIENT setname
命令为客户端设置一个名字,名字保存在RedisClient的 那么属性中。
1.3 标志
客户端的标志属性flags基类客户端的角色,以及客户端目前所处的状态,存储在redisClient的flags属性中:
flags属性可以是单个标志:flags =
, 也可以是二进制多个标志: flags =
具体标志所表示的含义可以参见Redis文档。
1.4 输入缓冲区
客户端状态的输入缓冲区用于保存客户端发送的命令请求:
typedef struct redisClient {
//...
sds querybuf;
//...
} redisClient;
1.5 命令与命令参数
在服务器将客户端发送的命令请求保存到客户端的querybuf属性之后,服务器将对命令请求的内容进行分析,并将得出的命令参数以及命令参数的个数分别保存到客户端状态的argv属性与argc属性:
typedef struct redisClient {
//...
robj **argv;
int argc;
//...
} redisClient;
1.6 命令的实现函数
当服务器从协议内容中分析并得出argv和argc属性之后,服务器会根据argv[0]的值,在命令表中查找所对应的命令实现函数。
下图为一个命令表示例:
当程序在命令表中成功找到argv[0]所对应的RedisCommand结构时,会将客户端状态的cmd指针指向这个结构。
之后,服务器使用cmd属相所指向的命令结构,以及argv和argc中保存的信息,完成客户端命令。
1.7 输出缓冲区
执行命令所得到的回复会被保存到客户端状态的输出缓冲区中,每个客户端都有两个输出缓冲区,一个缓冲区的大小是固定的,另一个是可变的:
· 固定大小的缓冲区用于保存长度较小的回复,如OK等,其以数组形式存储;
· 可变大小的缓冲区用于保存那些长度比较大的回复,如很多元素的集合,以链表形式存储;
typedef struct redisClient {
//...
char buf[REDIS_REPLY_CHUNK_BYTES];
int bufpos;
list *reply;
//...
} redisClient;
buf是一个固定大小缓冲区的数组,bufpos属性记录了buf数组目前使用的字节数量。
reply链表是可变缓冲区。
II、客户端的创建与关闭
2.1 创建普通客户端
前面已经说过,服务器通过client链表来维护客户端,每次创建的新客户端都会添加到链表末尾。
2.2 关闭客户端
服务器会因为多种原因关闭客户端,这里重点说明服务器为限制客户端输出缓冲区大小而采取的关闭客户端措施。
· 硬性限制:如果缓冲区大小超过了硬性限制所设置的大小,则服务器立即关闭客户端。
· 软性限制:如果输出缓冲区大小超过了软性限制所设置的大小,但还没超过影响限制,服务器使用一个time属性来记录客户端到达软性限制的时间,如果缓冲区的大小一直超出软性限制,并持续时间超过设定的时长,则会关闭客户端;如果在未到达时长之前,不再超出软性限制,客户端就不会关闭。
2.3 伪客户端
服务器中包含两个不需要socket的伪客户端,分别执行AOF load,与lua脚本
【参考】
[1] 《Redis设计与实现》
欢迎转载,转载请注明出处Redis之客户端