Redis的命令处理流程

·  阅读 624

我们在使用Redis时,都是向Redis发送各种命令,然后接受命令的返回数据。但是每个命令的执行过程是怎样的呢?Redis怎么处理每个命令的呢?

在正式进入命令执行流程分析之前,我们先要了解一下Redis的启动过程。

Redis的启动

我们只看单机Redis的启动流程,哨兵、集群、module等都先跳过,这样使我们分析起来更简单。就算简化了分析过程,Redis启动时也做了一堆的事,限于本人文采不行,还是直接上图吧。

Redis启动流程

图中省略了很多,但不影响我们接下来的分析。

大家都知道,Redis通过事件来处理网络IO,从图中可以看到,Redis通过aeCreateFileEvent函数创建TCP handler事件。

void initServer(void) {
    ...
    /* Create an event handler for accepting new connections in TCP and Unix
     * domain sockets. */
    for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
                serverPanic(
                    "Unrecoverable error creating server.ipfd file event.");
            }
    }
    if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
        acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
    ...
}
复制代码

我们只分析TCP事件,忽略Unix domain。可以看到TCP事件的处理句柄为acceptTcpHandler

连接Redis

当我们通过下面的命令连接Redis时:

$ redis-cli -h host -p port -a password
复制代码

TCP连接事件被触发,acceptTcpHandler事件句柄被调用。

acceptTcpHandler

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    ...
    // 接受客户端的请求
    cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
    ...
    // 处理客户端的请求
    acceptCommonHandler(cfd,0,cip);
    ...
}
复制代码

acceptCommonHandler

static void acceptCommonHandler(int fd, int flags, char *ip) {
    client *c;
    // 创建新的客户端
    if ((c = createClient(fd)) == NULL) {
        serverLog(LL_WARNING,
            "Error registering fd event for the new client: %s (fd=%d)",
            strerror(errno),fd);
        close(fd); /* May be already closed, just ignore errors */
        return;
    }
    ...
}
复制代码

服务端状态server会保存每个连接的客户端信息,

struct redisServer {
    ...
    list *clients;              /* List of active clients */
    ...
}
复制代码

createClient

// 新建客户端,设置客户端事件,并将客户端信息保存到server.clients队列中
client *createClient(int fd) {
    ...
    if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    ...
}
复制代码

这里,又新建了个IO事件,用来读取客户端发送的命令,事件的句柄为readQueryFromClient

发送命令

redis-cli> set mykey hello
复制代码

当我们向Redis发送这条命令时,readQueryFromClient句柄被触发。

readQueryFromClient

readQueryFromClient读取客户端发送的命令。

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    // 读取客户端发送的命令,并保存到缓存中
    ...
    processInputBufferAndReplicate(c);
}
复制代码

processInputBufferAndReplicate

void processInputBufferAndReplicate(client *c) {
    if (!(c->flags & CLIENT_MASTER)) {
        processInputBuffer(c);
    } else {
        // 如果是master服务,先忽略
        ...
    }
}
复制代码

processInputBuffer

void processInputBuffer(client *c) {
    ...
    while(c->qb_pos < sdslen(c->querybuf)) {
        ... // 前置判断,忽略
        // 从缓存中读取命令,并解析Redis协议
        // https://redis.io/topics/protocol Redis协议描述
        // 这里只需要知道知道函数的功能就行,不需要详细了解具体的实现。
        if (c->reqtype == PROTO_REQ_INLINE) {
            if (processInlineBuffer(c) != C_OK) break;
        } else if (c->reqtype == PROTO_REQ_MULTIBULK) {
            if (processMultibulkBuffer(c) != C_OK) break;
        } else {
            serverPanic("Unknown request type");
        }
        
        ...
            if (processCommand(c) == C_OK) {
                ...
            }
        ...
    }
}
复制代码

processCommand

processCommand执行命令,

/* If this function gets called we already read a whole
 * command, arguments are in the client argv/argc fields.
 * processCommand() execute the command or prepare the
 * server for a bulk read from the client.
 *
 * If C_OK is returned the client is still alive and valid and
 * other operations can be performed by the caller. Otherwise
 * if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) {
    ...
    /* Now lookup the command and check ASAP about trivial error conditions
     * such as wrong arity, bad command name and so forth. */
     // 从命令字典表中查找该命令,并设置命令的处理函数
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    ...
    /* Exec the command */
    // 执行命令
    // 只关注call函数,其他先注释掉。
    //if (c->flags & CLIENT_MULTI &&
    //    c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
    //    c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
    //{
    //    queueMultiCommand(c);
    //    addReply(c,shared.queued);
    //} else {
        call(c,CMD_CALL_FULL); // 只关注call这里
    //    c->woff = server.master_repl_offset;
    //    if (listLength(server.ready_keys))
    //        handleClientsBlockedOnKeys();
    //}
    return C_OK;
}
复制代码

call执行具体的命令实现函数。之后只需要关注没个命令的具体实现就行了。

后记

命令处理流程调用栈大致如下

acceptTcpHandler
    acceptCommonHandler
        createClient
            readQueryFromClient
                processInputBufferAndReplicate
                    processInputBuffer
                        processInlineBuffer
                        processCommand
                            lookupCommand
                            call
                                具体的命令实现
复制代码

Redis的命令实现函数,都有固定的命名格式的void xxxCommand,所以大家可以在源码目录src下直接搜索。如set命令:

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改