Valkey源码剖析(13):执行命令

在将命令请求的参数以及命令结构本身记录到客户端对应的client结构之后,服务器就会调用server.c/processCommand()函数,进行执行命令之前的检查:

int processCommand(client *c) {
    // ...
}

这些检查包括:

  • 检查命令及命令参数是否存在,格式是否正确,排除可能出现的隐性错误

  • 验证客户端的身份和权限能否执行被请求的命令

  • 拒绝执行某些被保护的命令

  • 如果服务器运行在集群模式下,那么考虑是否需要对命令进行转向

  • 处理可能出现的失效情况,尽量避免执行命令可能带来的数据不一致

  • 检查服务器已连接客户端的内存占用情况,并在有需要的情况下释放部分客户端,以便为接下来将要执行的命令留出足够的内存

  • 执行maxmemory指令

  • 检查持久化功能是否正常,尽量确保写命令的正确性

  • 如果服务器为主节点,那么检查主从连接的健康程度以及从节点的数量,尽量确保写命令不会导致主从不一致

  • 检查服务器是否正在载入数据库,如果是的话只允许带有CMD_LOADING标识的命令执行

  • 如果服务器正在处理繁重的工作(比如在执行脚本或调用模块的时候),只允许执行部分命令

  • 如果服务器正处于阻塞状态,那么阻塞该命令直到阻塞解除

等等。

在通过前面大量检查之后,processCommand()函数将调用server.c/call()函数,正式开始执行命令:

int processCommand(client *c) {

    // 执行命令之前的大量检查和测试……

    if (c->flag.multi && c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != quitCommand &&
        c->cmd->proc != resetCommand) {
        // 处理事务情况
        // ...
    } else {
        int flags = CMD_CALL_FULL;
        // 实际地执行命令
        call(c, flags);
        if (listLength(server.ready_keys) && !isInsideYieldingLongCommand()) handleClientsBlockedOnKeys();
    }

    return C_OK;
}

call()函数负责设置服务器和客户端的状态以及各项统计数据,为命令的执行做好最后的准备工作,然后以reactor模式调用客户端请求的命令实现函数,从而实现命令的实际执行:

void call(client *c, int flags) {

    // ...

    // 调用命令实现函数以执行命令
    c->cmd->proc(c);

    // ...

}

在命令函数执行之后,call()函数会再次更新各项状态信息和统计数据、日志等等,并做一些善后处理工作(比如资源回收等)。

以上就是Valkey服务器执行客户端命令的具体流程了。

黄健宏
2026.1.6