《Redis设计与实现》读书笔记(十八) ——Redis客户端属性设计与原理
(原创内容,转载请注明来源,谢谢)
一、概述
redis服务器是一对多服务器,多个客户端可以与一个服务器建立连接,并且分别发送请求,服务器接收请求并分别回复。通过使用I/O多路复用技术实现的文件事件处理器,redis服务器使用单线程单进程的方式来处理请求,并与多个客户端建立网络通信。
1、单个客户端状态结构存储
每个与服务器建立连接的客户端,服务器都为这些客户端建立相应的redis.h/redisClient结构,用于分别存储每个客户端的状态,这个结构保存客户端当前的信息,以及执行相关功能时候需要用到的数据结构,主要包括:
1)客户端信息
客户端套接字描述符,客户端名字,客户端标志值,客户端身份验证标志,客户端创建时间、最后一次与服务器通信时间、客户端输出缓冲区以及缓冲区大小超出服务器软性限制的时间。
2)客户端状态
客户端当前使用的数据库指针及数据库号码,客户端当前要执行的命令、命令参数、参数个数、实现命令的指针。
3)客户端用到的数据结构
客户端复制状态信息及复制所需的数据结构,客户端执行brpop、blpop等阻塞列表命令时用到的数据结构,客户端事务及watch用到的数据结构,客户端执行发布订阅功能用到的数据结构。
2、所有客户端存储
redis通过redisServer结构的clients属性,将所有的客户端结构进行存储,存储是采用链表的方式。如下:
struct redisServer{
//…其他内容
list *clients;
}
clients属性,以链表的形式,存储了所有的客户端状态,里面的每个元素都代表一个客户端,包含该客户端的诸如客户端信息、状态、使用的数据结构等全部信息。如下图所示:
二、客户端属性
客户端属性分为通用的和特定操作下的属性,特定操作如切换数据库、执行事务、watch等。这里重点学习通用属性。
主要用到的类型如下:
typedef struct redisClient{
//..其他内容
int fd;
robj *name;
int flags;
sds querybuf;
robj **argv;
int argc;
struct redisCommand*cmd;
char buf[REDIS_REPLY_TRUNK_BYTES];
int bufpos;
list *reply;
int authenticated;
time_t ctime;
time_tlastinteraction;
time_t obuf_soft_limit_reached_time;
}redisClient;
1、套接字描述符
在redisClient里面,用int的方式记录当前使用的套接字描述符,属性名称是fd。
fd的值是-1或者大于-1的整数。当客户端是来自AOF文件或者Lua脚本,需要生成的临时的伪客户端,则值是-1,表示其不需要网络;正常的客户端值是大于-1的整数,这个值是redis客户端和redis服务器进行通信的描述符。
2、名字
客户端的名字属性名称是name,类型是redis的字符串对象。
默认情况下客户端没有名字,可以通过client setname设置名字,通过client list命令查看当前所有客户端的信息,如下是设置名字之后的:
如果没有设置名字,name是指向null的指针,设置之后则是redis字符串对象。
3、标志
客户端的标志记录客户端的角色,和当前客户端所处的状态,用int类型存储,属性名是flags。标志可以是单个的也可以是多个的二进制“或”。如flags = flag1 或者flags = flag1 | flag2 |flag3。
每个标志是一个常量,主要的如下:
1)主从复制期间,主服务器会成为从服务器的客户端,REDIS_MASTER表示客户端是主服务器,REDIS_SLAVE表示客户端是从服务器。
2)REDIS_PRE_PSYNC表示客户端是redis版本低于2.8的从服务器,主服务器不能用psync命令才进行复制。
3)REDIS_LUA_CLIENT表示lua脚本创建的伪客户端。
4)还有其他标志,最重要的是REDIS_UNBLOCKED,这标志标记redis已经从阻塞状态脱离出来,只有这样的客户端才会接收到服务器发送的信息。
5)REDIS_FORCE_AOF,这个标志也很特殊,默认情况下只有“写”相关的命令才会计入aof文件,但是存在个别命令,虽然不会影响到数据库的数据,但是会产生副作用,则需要用此标记,强制将结果写入aof。例如pubsub命令,会给所有订阅者发送信息;script load命令,加载脚本。
flags例子如下:
4、输入缓冲区
输入缓冲区保存客户端发送的命令请求,采用redis的sds类型存储,名称是querybuf。sds是简单动态字符串,是redis用来存储字符串对象值的结构。
输入缓冲区会根据输入的内容动态扩大或减小,但是不能超过1gb,否则redis会自动关闭该客户端。
输入命令set key value,querybuf的结果(缩略)如下:
5、命令与命令参数
当发送请求道querybuf后,redis会对命令进行分析,将命令参数保存到**argv,命令个数保存到argc。
argv是一个数组,每一项都是一个redis字符串对象,其中argv[0]是要执行的命令,后面的下标是传入的参数。argc则是记录数组argv的长度。
输入命令set key value,argc和argv的结果如下:
6、命令实现函数
分析完querybuf,并生成argc和argv后,redis将根据argv[0]的内容,确定相应的要实现的函数。
redis保存着一个数据字典,该字典存储着每个命令与其对应的redisCommand结构,键是sds结构的命令,值是对应的redisCommand结构。该字典也成为redis的命令表。
客户端的cmd属性,即分析完argv[0]后,从上述redis命令表中找到对应的结构,并用指针指向该结构。命令表的键不区分大小写,因此redis的命令也是不区分大小写的。
7、输出缓冲区
执行命令得到的回复,会保存在输出缓冲区中,每个客户端都有两个缓冲区,一个是固定大小的,另一个可变大小的。
固定大小的缓冲区由buf和bufpos两个属性组成,buf是一个数组,用于存储固定大小的返回值,默认大小是16kb;bufpos记录目前buf已经使用的字节数。固定大小缓冲区通常用于记录长度比较短的元素,如OK、短的字符串返回值、整数、错误回复等。
可变大小的缓冲区由链表组成,属性名是reply,链表内是一个或多个字符串对象组成。当回复的长度太长,则用可变大小缓冲区。另外,如果一开始用固定大小缓冲区,当记录过程中大小超过16kb,则会转换成可变大小缓冲区。
8、身份验证
redis用int记录是否验证身份,属性名是authenticated,0表示未通过验证,1表示已经通过验证。
当未验证身份,除了auth以外的命令,都会被redis服务器拒绝。
9、时间
redis共有3个属性记录客户端的时间相关的内容:ctime、lastinteraction、obuf_soft_limit_reached_time,类型都是time_t。
ctime记录客户端与redis服务器创建连接的时间,clientlist命令可以查看客户端连接的秒数,用age表示,单位是秒。
lastinteraction表示客户端与redis服务器最后一次交互的时间,可以是服务器发送信息给客户端的时间,也可以是客户端发送信息给服务器的时间。这个时间主要用来判断空转时间,即连接后没有操作的时间,client list可以查看空转时间,用idle表示,单位是秒。
obuf_soft_limit_reached_time表示输出缓冲区第一次达到软性限制的时间。
——written by linhxx 2017.09.08