Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接,每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求,并向客户端返回命令回复。
通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式处理命令请求,并于多个客户端进行网络通信。
每个与服务器进行连接的客户端,服务端都为这些客户端建立相应的redisClient结构(客户端状态),保存客户端当前的状态信息,以及执行相关功能时需要用到的数据结构。
Redis服务器状态结构的clients属性是一个链表,这个链表保存了所有与服务器连接的客户端的状态结构,对客户端执行批量操作,或者查找某个指定的客户端,都可以通过遍历clients链完成。
struct redisServer{
//一个链表,保存了所有客户端状态
list *clients;
}
1、客户端状态的fd属性记录了客户端正在使用的套接字描述符
typedef struct redisClient{
int fd;
} redisClient;
根据客户端的类型不同fd的属性值可以是-1,或者是大于-1的整数:伪客户端fd属性为-1;普通客户端的fd属性值为大于-1的整数。
2、默认情况下一个连接到服务器的客户端是没有名称的, 可以使用Client setname 命令为客户端设置一个名称。
3、客户端的标志属性flags记录了客户端角色(role),以及客户端状态
typedef struct redisClient{
int flag;
} redisClient;
在主从服务器进行复制操作时,主服务器会成为从服务器的客户端,而从服务器也会成为主服务器的客户端。
REDIS_MASTER 客户端是一个主服务器
REDIS_BLOCKED 客户端正在被列表命令阻塞
REDIS_MULTI | REDIS_DIRTY_CAS 客户端正在执行事务,但事务的安全性已经被破坏
REDIS_SLAVE | REDOS_PRE_PSYNC 客户端是一个从服务器,并且版本低于REDIS2.8
REDIS_LUA_CLIENT | REDIS_FORCE_AOF | REDIS_FORCE_REPL 这是专门执行Lua脚本包含的redis命令的伪客户端 ,它强制服务器将当前执行的命令写入到AOF文件,并复制给从服务器。
4、客户端状态的输入缓冲区用户保存客户端发送的命令请求:
typedef struct redisClient {
sds querybuf;
} redisClient;
5、在服务器将客户端发送的命令请求保存在客户端状态的querybuf属性后,服务器将对命令请求的内容进行分析,并将得出的命令参数以及命令参数的个数分别保存到客户端状态的argv属性和argc属性:
typedef struct redisClient{
robj **argv;
int argc;
} redisClient;
argv属性是一个数组,数组的每个项都是一个字符串队形,其中avgv[0]是要执行的命令,其之后的其他项都是传给命令的参数。
6、当服务器从协议内容中分析并得出argv属性和argc属性的值之后,服务器将根据项argv[0]的值,在命令表中查找命令所对应的实现函数。
命令表是一个字典表,字典的键是一个SDS结构,保存了命令的名字,字典值是命令的名字,字典值是命令所对应的redisCommand结构,这个结构保存了命令的实现函数、命令的标志、命令应该给定的参数个数、命令的总执行次数和总耗时长等统计信息。
当程序在命令表中成功找到argv[0]所对应的redisCommand结构时,它会将客户端状态的cmd指针指向这个结构:
typedef struct redisClient{
struct redisCommand *cmd;
} redisClient;
之后,服务器就可以使用cmd属性所指向的redisCommand结构,以及argv、argc属性中保存的命令参数信息,调用命令实现函数,执行客户端指定的命令。
7、执行命令所得的命令回复会被保存在客户端状态的输出缓冲区里面,每个客户端有两个输出缓冲区可以用,一个缓冲区的大小是固定的,一个大小是可变的。
固定大小的缓冲区用于保存那些长度比较小的回复,可变大小的缓冲区用于保存耐饿长度比较大的回复。
客户端的固定大小缓冲区由buf和bufpos两个属性组成
typedef struct redisClient{
char buf[REDIS_REPLY_CHUNK_BYTES];//REDIS_REPLY_CHUNK_BYTES 默认16 *1024 也就是说固定缓冲区默认 16k
int bufpos;//记录已使用的字节数量
} redisClient;
身份认证
客户端状态的authenticated属性用于记录客户端是否通过了身份验证:
typedef struct redisClient{
int authenticated;
} redisClient;
authenticated属性仅在服务器启用了身份验证功能的时候使用,如果服务器没有启用的话,即使为0也不会拒绝客户端的命令请求。
9、时间
typedef struct redisClient{
time_t ctime;//创建客户端的时间 连接服务器时长(秒)
time_t lastinteraction;//最后一次与服务器互动时间,计算客户端空转时间
//记录缓冲区第一次到达软性限制的时间
time_t obuf_soft_limit_reached_time;
} redisClient;
如果客户端是通过网络连接于服务器进行连接的普通客户端,那么客户端使用connect函数连接到服务器时,服务器就会调用连接事件处理器,为客户端创建相应的客户端状态,并将这个新的客户端状态添加到服务器状态结构clients链表的末尾。
普通客户端可以因为多种原因而被关闭:
客户端进程退出或者被杀死,那么客户端与服务端的网络连接被关闭。
如果客户端向服务端发送了带有不符合协议的命令请求,那么这个客户端也会被关闭。
如果客户端成为了CLIENT KILL命令的目标,那么它也会被关闭。
如果服务端设置了timeout配置项,那么客户端的空转事件超过timeout现象设置的值时,客户端被关闭。
如果客户端发送的命令请求的大小超过了输入缓冲区的限制大小(默认1G),那么客户端会被服务器关闭。
如果要发送给客户端的命令回复的大小超过了输出缓冲区的限制大小,那么这个客户端会被服务器关闭。
服务器使用两种模式来限制客户端输出缓冲区的大小:
硬性限制,如果输出缓冲区的大小超过了硬性限制所设置的大小,那么服务器立即关闭客户端。
软性限制,如果输出缓冲区的大小超过了软性限制设置的大小,但没有超过硬性设置的大小,obuf_soft_limit_reached_time属性记录下客户端达到软性限制的起始事件,之后服务器会继续监视客户端,如果输出缓冲区的大小一直超出软性限制,并且持续时间超过服务器设置的时长,那么服务器将关闭客户端。
服务端会在初始化时创建负责执行Lua脚本中包含redis命令的伪客户端,并将这个伪客户端关联在服务器状态结构的lua_client属性中:
struct redisServer{
redisClient *lua_client;
}
lua_client 伪客户端在服务器运行的整个声明周期中会一直存在,只有服务器关闭时,这个客户端才会关闭。
服务器在载入AOF文件时,会创建用于执行AOF文件的Redis命令的伪客户端,并在载入完成之后,关闭伪客户端。
每天学一点,总会有收获。
说明:尊重作者知识产权,文中内容参考《Redis设计与实现》,仅在此做学习与大家分享。