?Redis本质上是一个数据结构服务器,支持键值对类型存储的内存管理系统,可以用作数据库、缓存和消息中间件,在我日常的开发中,基本上使用redis作为缓存中间件。
?在Redis中有两个重要的角色,一个是服务器server,一个是客户端client,他们是一对多的关系,服务器会保存每个与之相连接的客户端的状态信息。本文会从这两个角色,分析客户端和服务器中一些比较重要的属性结构,以及一个命令从客户端发送到服务器接受、处理并返回的实现原理。
?Redis服务器启动后,需要经过一些列的初始化及配置的设置,之后就可以接收客户端的命令请求,进行相关操作了,那么服务器初始化的整个过程,主要包含以下几步
?客户端首次创建,连接到服务器之后,会向服务器发送命令,并得到相应的结果和回复。在redis中,客户端可以分为三种类型:一种是负责执行Lua脚本的伪客户端,一种是用来加载aof文件的伪客户端,还有一种就是通过网络连接的普通客户端。
?对于通过网络与redis服务器连接的普通客户端和lua脚本的客户端,服务器都会创建相对应的client 结构,用于记录他们的状态信息,我们先看下客户端在redisServer结构中是如何保存的:
struct redisServer {
...
// 存放普通客户端的列表
list *clients; /* List of active clients */
// 存放lua脚本客户端
client *lua_client; /* The "fake client" to query Redis from Lua */
...
};
?看这段源码,redisServer是表示redis服务器的结构,在这其中clients是一个链表,链表中每个节点代表与之连接的客户端client,新增加的客户端会添加到列表的末尾。而lua_client指针指向了代表lua脚本的伪客户端(fake client: 伪造的客户端)。
?通过以上,我们了解到clients链表中每个节点都代表一个客户端client,那么在redis中,client会保存客户端(这里指的是普通的网络客户端)的哪些状态信息呢,看如下源码:
typedef struct client {
...
// fd是每个客户端连接的标识
int fd; /* Client socket. */
// 这个记录的是客户端的名称,默认没有名称
robj *name; /* As set by CLIENT SETNAME. */
// 记录了客户端的角色和相关状态
int flags; /* Client flags: CLIENT_* macros. */
// 客户端状态的输入缓冲区,保存客户端的命令请求
sds querybuf; /* Buffer we use to accumulate client queries. */
// 下面这两个是解析出来的命令和参数
int argc; /* Num of arguments of current command. */
robj **argv; /* Arguments of current command. */
// 一个是根据argv[0]解析出来的命令,一个是最后一次执行的命令
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
// 下面两个是输出缓冲区(固定缓冲区大小)
int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES];
// 下面这个是可变缓冲区,链表,节点是字符串对象
list *reply; /* List of reply objects to send to the client */
// 身份验证标识
int authenticated; /* When requirepass is non-NULL. */
...
} client;
?客户端状态包含但不限于以下信息:
client setname
命令设置;?以上部分信息可以通过命令client list
查询,获取的就是当前服务端所有正在连接的客户端信息。
?以上就是客户端整个生命周期的状态描述,下面我们来看一下,一个命令从客户端发送,到服务器执行后返回,整个过程都执行了哪些操作。
?一个命令从发送到处理并回复的过程,客户端和服务器都执行了很多操作。简单来看,就是客户端发送命令,如get key
;服务器接收命令,并转化成相关函数操作,得到结果,然后发送给客户端;客户端接收数据并呈现给用户。在这一个流程中,具体包含哪些细节呢?
?综上,使用流程图来表示以上客户端与服务器的加载过程,以及命令的实现流程,如下图: