一、概览
一个命令请求从发送到获得回复的过程中,客户端和服务器需要完成一系列操作。
redis > set key value
那么从客户端发送SET KEY VALUE命令到获得回复OK期间,客户端和服务器共需要执行以下操作:
1)客户端向服务器发送命令请求SET KEY VALUE。
2)服务器接收并处理客户端发来的命令请求SET KEY VALUE,在数据库中进行设置操作,并产生命令回复OK。
3)服务器将命令回复OK发送给客户端。
4)客户端接收服务器返回的命令回复OK,并将这个回复打印给用户观看。
以上是最显而易见的内容,那么这个过程深入分析包含了哪些内容呢?
整个执行完成并返回主要包括三部分:
1) 建立连接阶段,响应了socket的建立,并且创建了client对象;
2) 处理阶段,从socket读取数据到输入缓冲区,然后解析并获得命令,执行命令并将返回值存储到输出缓冲区中;
3)数据返回阶段,将返回值从输出缓冲区写到socket中,返回给客户端,最后关闭client。
PS:想更深入的了解,建议查看以下文章及阅读对应源码
Redis 命令执行过程(上) - 程序员历小冰 - 博客园
Redis 命令执行过程(下) - 程序员历小冰 - 博客园
二、理解&案例:
服务端可用时接收来自多个客户端的请求,通过I/O多路复用程序同时监听多个套接字,有请求事件产生则进入事件队列;再逐一出队,文件时间分派器根据事件的属性去关联实际的事件处理器。
例如:set key value,在执行之前redis客户端与服务端需经过连接,使用了--连接应答处理器;set为命令,使用了--命令请求处理器;处理完成返回结果,使用了--命令回复处理器;最后解除了套接字与事件处理器之间的关联。
具体命令请求的执行过程如下:
整体的交互过程如下(如有理解错误,请指正):
更进一步的细节内容见下文(可结合上图,便于理解):
1、客户端发送命令请求
当用户在客户端中键入一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器。
2、服务端读取命令请求
当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行以下操作:
1)读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区里面。
2)对输入缓冲区中的命令请求进行分析,提取出命令请求中包含的命令参数,以及命令参数的个数,然后分别将参数和参数个数保存到客户端状态的argv属性和argc属性里面。
3)调用命令执行器,执行客户端指定的命令。
3、服务端命令执行
1)查找命令
命令执行器根据客户端状态的argv[0]参数,在命令表中查找参数所指定的命令,并将找到的命令保存到客户端状态的cmd属性里面。
命令表是一个字典,字典的键是一个个命令名字,比如"set"、"get"、"del"等等;而字典的值则是一个个redisCommand结构,每个redisCommand结构记录了一个Redis命令的实现信息.
案例:
SET命令的名字为"set",实现函数为setCommand;命令的参数个数为-3,表示命令接受三个或以上数量的参数;命令的标识为"wm",表示SET命令是一个写入命令,并且在执行这个命令之前,服务器应该对占用内存状况进行检查,因为这个命令可能会占用大量内存。
GET命令的名字为"get",实现函数为getCommand函数;命令的参数个数为2,表示命令只接受两个参数;命令的标识为"r",表示这是一个只读命令。
2)执行准备
在获得执行所需的:命令,对应的函数、参数、参数个数后,服务器需要进行一些预处理操作,主要包括:
① 命令校验:检查客户端状态的cmd指针是否指向NULL。
② 参数校验:根据客户端cmd属性指向的redisCommand结构的arity属性,检查命令请求所给定的参数个数是否正确。
③ 权限校验:检查客户端是否已经通过了身份验证,未通过身份验证的客户端只能执行AUTH命令。
④ 内存检测:如果服务器打开了maxmemory功能,那么在执行命令之前,先检查服务器的内存占用情况,并在有需要时进行内存回收,从而使得接下来的命令可以顺利执行
⑤ 其他校验...
3)调用命令实现函数
如下图所示,服务器将要执行命令的实现保存到了客户端状态的cmd属性里面,并将命令的参数和参数个数分别保存到了客户端状态的argv属性和argv属性里面,当服务器执行命令时,只需要一个指向客户端状态的指针作为参数,调用实际执行函数。
被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf属性和reply属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。
4)执行后续操作
如在Redis.config里面有相关配置,则后续操作包含:慢日志记录、redisCommand结构属性更新、AOF持久化记录、主从复制命令传播等。
4、服务端将命令回复发送客户端
命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。发送完毕后,回复处理器会清空客户端状态的输出缓冲区,为下一个命令请求做好准备。
5、客户端接收并打印命令回复
当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户观看。
三、相关概念了解--事件
Redis服务器是一个事件驱动程序:
文件事件(file event):Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
时间事件(time event):Redis服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象。
(一)、文件事件
文件事件处理器使用I/O多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
文件事件处理器的四个组成部分,它们分别是套接字、I/O多路复用程序、文件事件分派器(dispatcher),以及事件处理器。
1、文件事件处理器
1.1、连接应答处理器
为了对连接服务器的各个客户端进行应答,服务器要为监听套接字关联连接应答处理器。
1.2、命令请求处理器
为了接收客户端传来的命令请求,服务器要为客户端套接字关联命令请求处理器。
1.3、命令回复处理器
为了向客户端返回命令的执行结果,服务器要为客户端套接字关联命令回复处理器。
1.4、复制处理器(主从复制专用-不常用,不深入)
当主服务器和从服务器进行复制操作时,主从服务器都需要关联特别为复制功能编写的复制处理器。
(二)、时间事件
Redis的时间事件分为以下两类:
定时事件:让一段程序在指定的时间之后执行一次。比如说,让程序X在当前时间的30毫秒之后执行一次。
周期性事件:让一段程序每隔指定时间就执行一次。比如说,让程序Y每隔30毫秒就执行一次。
一个时间事件主要由以下三个属性组成:
❑id:服务器为时间事件创建的全局唯一ID(标识号)。ID号按从小到大的顺序递增,新事件的ID号比旧事件的ID号要大。
❑when:毫秒精度的UNIX时间戳,记录了时间事件的到达(arrive)时间。
❑timeProc:时间事件处理器,一个函数。当时间事件到达时,服务器就会调用相应的处理器来处理事件。
一个时间事件是定时事件还是周期性事件取决于时间事件处理器的返回值:
❑如果事件处理器返回ae.h/AE_NOMORE,那么这个事件为定时事件:该事件在达到一次之后就会被删除,之后不再到达。
❑如果事件处理器返回一个非AE_NOMORE的整数值,那么这个事件为周期性时间:当一个时间事件到达之后,服务器会根据事件处理器返回的值,对时间事件的when属性进行更新,让这个事件在一段时间之后再次到达,并以这种方式一直更新并运行下去。比如说,如果一个时间事件的处理器返回整数值30,那么服务器应该对这个时间事件进行更新,让这个事件在30毫秒之后再次到达。