Redis客户端
推荐这篇文章: Redis 是如何处理命令的(客户端)
本文我们追踪一下,一行命令是如何从Redis客户端发送到服务端,并且服务端将执行结果返回给客户端的。以客户端的命令行交互模式为例。
Redis客户端的命令行交互模式基于linenoise
行编辑库开发的,和redis是同一个作者。
在redis-cli.c
文件中我们找到客户端程序的main()
入口函数。函数的开始直接初始化config
配置结构。config
是一个结构体类型的全局变量,其成员包含了客户端所需的各种配置参数。在主函数的最后,我们可以找到进入交互模式的方法repl()
。
/* Start interactive mode when no command is provided */
if (argc == 0 && !config.eval) {
/* Ignore SIGPIPE in interactive mode to force a reconnect */
signal(SIGPIPE, SIG_IGN);
/* Note that in repl mode we don't abort on connection error.
* A new attempt will be performed for every command send. */
cliConnect(0);
repl();
}
2
3
4
5
6
7
8
9
10
当类型为SOCK_STREAM
的套接字不再连接时,进程写该套接字会产生SIGPIPE
信号。signal(SIGPIPE, SIG_IGN);
只是简单的忽略这个信号,以免客户端因为和服务端失去连接而中断。
初始化和连接服务器
cliConnect()
函数会根据配置config
尝试通过TCP或者unix socket连接服务器,连接成功后为已连接的套接字设置SO_KEEPALIVE
选项来检测服务端是否停止服务,也可以避免耗时长的任务因为超时而中断。如果redis设置了鉴权,客户端连接函数还会向服务端发送一条命令来验证用户是否合法。另外cliConnect()
函数调用redisConnect()
连接成功后会返回初始化的redis客户端的上下文描述结构体context
,结构体的类型名为redisContext
。
auth命令
/* Send AUTH command to the server */
static int cliAuth(void) {
redisReply *reply;
if (config.auth == NULL) return REDIS_OK;
reply = redisCommand(context,"AUTH %s",config.auth);
if (reply != NULL) {
freeReplyObject(reply);
return REDIS_OK;
}
return REDIS_ERR;
}
2
3
4
5
6
7
8
9
10
11
12
发送命令函数redisCommand()
接受三个参数:context
, auth命令格式字符串以及客户端配置中保存的auth信息(也是字符串)。下面列了redisCommand()
函数调用其他函数的顺序,这条auth命令字符串经过了格式化之后被追加到c->obuf
字符串的后面。
redisCommand
redisvCommand
redisvAppendCommand
redisvFormatCommand
__redisAppendCommand
sdscatlen
__redisBlockForReply
redisGetReply
redisGetReplyFromReader
redisReaderGetReply
redisBufferWrite
redisBufferRead
redisGetReplyFromReader
2
3
4
5
6
7
8
9
10
11
12
13
c->obuf
这个字符串是redis客户端程序的全局变量context
的成员obuf
,它被用作命令缓冲区。__redisBlockForReply()
会根据c->flags & REDIS_BLOCK
来判断是否阻塞执行redis命令:如果是非阻塞的话,多条命令将缓冲到缓冲区obuf
中,待合适的时机一次性发送给客户端执行。
上面提到过,在调用redisConnect()
初始化了context
全局变量。初始化时它执行了c->flags |= REDIS_BLOCK;
,使得一开始redis客户端执行命令是阻塞型的。于是,__redisBlockForReply()
就会立即执行redisGetReply()
来发送(redisBufferWrite()
)缓冲区中的命令并且读取(redisBufferRead()
, redisGetReplyFromReader()
)来自服务端的返回数据。
关于redis客户端和服务端之间的通信协议RESP
,网上已经有很多的介绍了,这里就不加以赘述了。
选择数据库
在执行完auth命令,用户提供的密码正确时,连接函数最后还执行了cliSelect()
命令向服务器发送了SELECT
命令选择数据库,告诉服务器客户端期望读取的数据库是哪个,同理,发送命令的方法如下:
reply = redisCommand(context,"SELECT %d",config.dbnum);
进入不同的客户端模式
连接成功后,客户端将进入我们想要的环境,如命令行交互模式。执行repl()
函数。repl()
函数是一个大循环,读取终端输入的命令,格式化后通过RESP协议发送给服务端进程,服务端处理后将结果返回给客户端,客户端接收到后显示在终端。