Redis服务器可以与多个客户端建立网络连接,每个客户端也可以向服务器发送命令请求。Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。
客户端
客户端结构
每个进行连接的客户端,都会创建一个redisClient结构,保存了客户端当前的信息。
1 | typedef struct redisClient { |
Redis服务器的clients属性是一个链表,保存了所有客户端的状态。
1 | struct redisServer{ |
套接字描述符
fd属性记录了客户端正在使用的套接字描述符。根据客户端的不同类型,fd的值可以为-1或者大于-1的整数。
如果fd的值为-1,代表该客户端为伪客户端。伪客户端的请求来源于AOF文件或者LUA脚本而不是来自于网络。这种客户端不需要使用套接字进行连接。普通客户端的fd属性的值为大于-1的整数,记录客户端套接字的描述符。
名字
客户端的名称记录在name属性中。在默认情况下,客户端是没有名字的。通过CLIENT SETNAME命令可以设置名字。
1 | 127.0.0.1:6379> CLIENT LIST |
如果客户端没有设置名字,那么name属性将会指向NULL,否则指向一个字符串对象,保存客户端的名字。
标志
客户端的flag属性记录了客户端的角色,其值可以为单个标志或者多个标志的二进制值。
命令请求
客户端的输入输出缓冲区用于保存客户端发送的命令请求,用querybuf属性进行保存。
例如当客户端发送了SET key value命令,那么querybuf的内容将为以下值
1 | *3 |
querybuf的大小最大为1GB,如果超过,Redis会关闭该连接。
在保存到缓冲区之后,会对请求的内容进行分析,并将得出的命令参数和个数保存到argv属性和argc属性中去。 例如对于刚才的命令,argc的值会设置为3,argv会设置为[set,key,value]数组。
当分析出argc和argv之后,服务器会根据argv[0]的值,在命令表中查找对应的命令实现函数,然后将cmd属性指向该命令结构。然后使用cmd属性所指向的redisCommand结构并设置命令参数信息,调用命令实现函数,执行对应的命令。
输出缓冲区
执行命令完成后,命令回复会被保存到输出缓冲区之中。每个客户端都有一个可变大小的缓冲区和固定大小的缓冲区。
其中固定大小的缓冲区有buf和bufpos两个属性组成。buf属性是一个字节数组,bufpos记录了buf数组目前已使用的字节数量。其作用是保存那些长度比较少的回复。
可变大小的缓冲区有reply属性保存。reply是一个链表连接多个字符串对象,用于保存那些长度比较大的回复,例如非常长的字符串,很多项组成的列表,很多元素的集合。
身份验证
authenticated属性记录了客户端是否通过了身份验证。如果其值为0代表未通过身份验证。
客户端的创建与关闭
创建普通客户端
客户端使用connect函数创建连接时,服务器会调用连接事件处理器,为客户端创建相应的客户端状态,并将该客户端状态添加到服务器状态结构clients链表的末尾。
关闭普通客户端
当出现以下情况时会出现客户端关闭:
- 客户端与服务器的网络连接被关闭。
- 发送了带有不符合协议格式的命令请求。
- 客户端成为CLIENT KILL命令的目标
- 发送的命令请求大小超过了输入缓冲区的大小
- 命令回复超过了输出缓冲的大小
- 用户为服务器设置了timeout配置选项。当空转时间超过该选项值时会被关闭。
伪客户端
服务器在初始化时会创建执行Lua脚本的Redis命令的伪客户端,并将该伪客户端关联在服务器状态的lua_client属性值中。在服务器关闭时,该客户端才会被关闭。
服务器在载入AOF文件时,会创建执行AOF文件的伪客户端,在执行完成后,立即关闭该客户端。
服务器
命令请求过程
发送命令请求
Redis服务器的命令请求来自与Redis客户端。客户端会将命令请求转化为协议格式,通过连接到服务器的套接字,将协议的命令请求发送给服务器。
读取命令请求
当套接字变得可读时,会调用命令处理器来执行以下操作
读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区中。
对输入缓冲区的命令请求进行分析,提取命令参数。
调用命令执行器执行指定的命令。
命令执行器
先根据客户端的argv[0]参数,在命令表中查找参数指定的命令,并保存到cmd属性值中。cmd的属性是redisCommand,其结构如下:
1 | struct redisCommand { |
在查找到命令以后,执行预备操作,例如检查cmd指针是否为NULL,参数个数是否正确等等。当完成预备操作后,服务器才开始真正的执行命令。redisCommandProc是一个函数指针的别名,参数为redisClient。
1 | typedef void redisCommandProc(redisClient *c); |
在服务器真正实行命令时,只需要调用以下语句即可。
1 | client->cmd->proc(client); |
被调用的命令实现函数会执行指定的操作,产生相应的命令回复,保存到输出缓冲区之中。
在执行完成以后,服务器还需要执行一些后续操作,例如打印慢日志,AOF日志输出。完成后续操作以后,就可以处理下一个命令请求了。
serverCron函数
Redis中的serverCron函数会每隔一段执行一次,负责管理服务器的资源,保证服务器运转良好。
在serverCron会进行例如更新服务器时间缓存,执行持久化操作,关闭客户端等等。