参考文献:

  1. Redis 是如何处理命令的(客户端)
  2. 我是如何通过添加一条命令学习redis源码的
  3. 从零开始写redis客户端(deerlet-redis-client)之路——第一个纠结很久的问题,restore引发的血案
  4. redis命令执行流程分析
  5. 通信协议(protocol)
  6. Redis主从复制原理
  7. Redis配置文件详解

当用户在redis客户端键入一个命令的时候,客户端会将这个命令发送到服务端。服务端会完成一系列的操作。一个redis命令在服务端大体经历了以下的几个阶段:

  1. 读取命令请求
  2. 查找命令的实现
  3. 执行预备操作
  4. 调用命令实现函数
  5. 执行后续工作

读取命令的请求

从redis客户端发送过来的命令,都会在readQueryFromClient函数中被读取。当客户端和服务器的连接套接字变的可读的时候,就会触发redis的文件事件。在aeMain函数中,将调用readQueryFromClient函数。在readQueryFromClient函数中,需要完成了2件事情:

  1. 将命令的内容读取到redis客户端数据结构中的查询缓冲区。
  2. 调用processInputBuffer函数,根据协议格式,得出命令的参数等信息。

    例如命令 set key value 在query_buffer中将会以如下的格式存在:

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = (redisClient*) privdata;
int nread, readlen;
size_t qblen;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask); // 设置服务器的当前客户端
server.current_client = c; // 读入长度(默认为 16 MB)
readlen = REDIS_IOBUF_LEN; ........
........ // 读入内容到查询缓存
nread = read(fd, c->querybuf+qblen, readlen); ........
........ processInputBuffer(c);
}

命令参数的解析

在上一节中,我们看到在readQueryFromClient函数中会将套接字中的数据读取到redisClient的queryBuf中。而对于命令的处理,实际是在processInputBuffer函数中进行的。

在函数中主要做了以下的2个工作:

  1. 判断请求的类型,例如是内联查询还是多条查询。具体的区别可以在通信协议(protocol)里面看到。本文就不详细叙述了。
  2. 根据请求的类型,调用不同的处理函数:

    2.1 processInlineBuffer

    2.2 processMultibulkBuffer
// 处理客户端输入的命令内容
void processInputBuffer(redisClient *c) {
while(sdslen(c->querybuf)) { .......
....... /* Determine request type when unknown. */
// 判断请求的类型
// 两种类型的区别可以在 Redis 的通讯协议上查到:
// http://redis.readthedocs.org/en/latest/topic/protocol.html
// 简单来说,多条查询是一般客户端发送来的,
// 而内联查询则是 TELNET 发送来的
if (!c->reqtype) {
if (c->querybuf[0] == '*') {
// 多条查询
c->reqtype = REDIS_REQ_MULTIBULK;
} else {
// 内联查询
c->reqtype = REDIS_REQ_INLINE;
}
} // 将缓冲区中的内容转换成命令,以及命令参数
if (c->reqtype == REDIS_REQ_INLINE) {
if (processInlineBuffer(c) != REDIS_OK) break;
} else if (c->reqtype == REDIS_REQ_MULTIBULK) {
if (processMultibulkBuffer(c) != REDIS_OK) break;
} else {
redisPanic("Unknown request type");
} /* Multibulk processing could see a <= 0 length. */
if (c->argc == 0) {
resetClient(c);
} else {
/* Only reset the client when the command was executed. */
// 执行命令,并重置客户端
if (processCommand(c) == REDIS_OK)
resetClient(c);
}
}
}

processMultibulkBuffer 和 processInlineBuffer

processMultibulkBuffer主要完成的工作是将 c->querybuf 中的协议内容转换成 c->argv 中的参数对象。 比如 *3\r\n$3\r\nSET\r\n$3\r\nMSG\r\n$5\r\nHELLO\r\n将被转换为:

 argv[0] = SET
argv[1] = MSG
argv[2] = HELLO

具体的过程就不贴代码了。同样processInlineBuffer也会完成将c->querybuf 中的协议内容转换成 c->argv 中的参数的工作。

查找命令的实现

到了这一步,准备工作都做完了。redis服务器已将查询缓冲中的命令转换为参数对象了。接下来将调用processCommand函数进行命令的处理。processCommand函数比较长,接下来我们分段进行解析。

查找命令

服务器端首先开始查找命令。主要就是使用lookupCommand函数,根据命令对应的名字,去找到对应的执行函数以及相关的属性信息。

    // 特别处理 quit 命令
if (!strcasecmp(c->argv[0]->ptr,"quit")) {
addReply(c,shared.ok);
c->flags |= REDIS_CLOSE_AFTER_REPLY;
return REDIS_ERR;
} /* 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);
if (!c->cmd) {
// 没找到指定的命令
flagTransaction(c);
addReplyErrorFormat(c,"unknown command '%s'",
(char*)c->argv[0]->ptr);
return REDIS_OK;
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
(c->argc < -c->cmd->arity)) {
// 参数个数错误
flagTransaction(c);
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
c->cmd->name);
return REDIS_OK;
}

那么命令的定义在哪里呢?答案在redis.c文件中,定义了一个如下的实现:

struct redisCommand redisCommandTable[]= {
..... {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0}, .....
}

Redis将所有它能支持的命令以及对应的“命令处理函数”之间对应关系存放在数组redisCommandTable[]中,该数组中保存元素的类型为结构体redisCommand,此中包括命令的名字以及对应处理函数的地址,在Redis服务初始化的时候,这个结构体会在初始化函数中被转换成struct redisServer结构体中的一个dict,这个dict被赋值到commands域中。结构体详细的实现如下:

/*
* Redis 命令
*/
struct redisCommand { // 命令名字
char *name; // 实现函数
redisCommandProc *proc; // 参数个数
int arity; // 字符串表示的 FLAG
char *sflags; /* Flags as string representation, one char per flag. */ // 实际 FLAG
int flags; /* The actual flags, obtained from the 'sflags' field. */ /* Use a function to determine keys arguments in a command line.
┆* Used for Redis Cluster redirect. */
// 从命令中判断命令的键参数。在 Redis 集群转向时使用。
redisGetKeysProc *getkeys_proc; /* What keys should be loaded in background when calling this command? */
// 指定哪些参数是 key
int firstkey; /* The first argument that's a key (0 = no keys) */
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */ // 统计信息
// microseconds 记录了命令执行耗费的总毫微秒数
// calls 是命令被执行的总次数
long long microseconds, calls;
}

根据这个结构体,我们可以看到set执行的信息如下:

  1. 命令名称是set
  2. 执行函数是setCommand
  3. 参数个数是3

执行命令前的准备工作

在上节,我们看到了Redis是如何查找命令,以及一个命令最终的定义和实现是在哪里的。接下来我们来看下 processCommand后面部分的实现。这部分主要的工作是在执行命令之前做一点的检查工作 :

  1. 检查认证信息,如果redis服务器配置有密码,在此处会做一次验证
  2. 集群模式下的处理,此处不多做展开。
  3. 检查是否到了Redis配置文件中,限制的最大内存数。如果达到了限制,需要根据配置的内存释放策略做一定的释放操作。
  4. 检查是否主服务,并且这个服务器之前是否执行 BGSAVE 时发生了错误,如果发生了错误则不执行。
  5. 如果Redis服务器打开了min-slaves-to-write配置,则没有足够多的slave可写的时候,拒绝执行写操作。
  6. 如果当前的Redis服务器是个只读的slave的话,拒绝执行写操作。
  7. 当redis处于发布和订阅上下文的时候,只能执行订阅和退订相关的命令。
  8. 如果slave-serve-stale-data 配置为no的时候,只允许INFO 和 SLAVEOF 命令。( Redis配置文件详解)
  9. 如果服务器正在载入数据到数据库,那么只执行带有 REDIS_CMD_LOADING 标识的命令,否则将出错。
  10. 如果Lua 脚本超时,只允许执行限定的操作,比如 SHUTDOWN 和 SCRIPT KILL。

到此Redis执行一个命令前的检查工作基本算完成了。接下来将调用call函数执行命令。

调用命令实现函数

在call函数里面,在真正的执行一个命令的实现函数。

// 执行实现函数
c->cmd->proc(c);

那么这个c是指什么呢?我们来看下call函数的定义:

void call(redisClient *c, int flags)

可见call函数传入的是redisClient这个结构体的指针。那么这个结构体在哪里创建的呢?是在"读取命令的请求"的阶段就已经创建好了。在redisClient中,定义了一个struct redisCommand *cmd 属性,在查找命令的阶段便被赋予了对应命令的执行函数。因此在此处,将会调用对应的函数完成命令的执行。

typedef struct redisClient {
// 记录被客户端执行的命令
struct redisCommand *cmd, *lastcmd;
}

执行后续工作

在执行完命令的实现函数之后,Redis还有做一些后续工作包括:

  1. 计算命令的执行时间
  2. 计算命令执行之后的 dirty 值
  3. 是否需要将命令记录到SLOWLOG中
  4. 命令复制到 AOF 和 slave 节点

redis 命令的调用过程的更多相关文章

  1. 深入Redis命令的执行过程

    深入Redis命令的执行过程 Redis 服务器: Redis 服务器实现与多个客户端的连接,并处理这些客户端发送过来的请求,同时保存客户端执行命令所产生的数据到数据库中.Redis 服务器依靠资源管 ...

  2. Redis 命令执行过程(上)

    今天我们来了解一下 Redis 命令执行的过程.在之前的文章中<当 Redis 发生高延迟时,到底发生了什么>我们曾简单的描述了一条命令的执行过程,本篇文章展示深入说明一下,加深读者对 R ...

  3. Redis 命令执行全过程分析

    今天我们来了解一下 Redis 命令执行的过程.我们曾简单的描述了一条命令的执行过程,本篇文章展示深入说明一下,加深大家对 Redis 的了解. 如下图所示,一条命令执行完成并且返回数据一共涉及三部分 ...

  4. Redis 命令执行过程(下)

    在上一篇文章中<Redis 命令执行过程(上)>中,我们首先了解 Redis 命令执行的整体流程,然后细致分析了从 Redis 启动到建立 socket 连接,再到读取 socket 数据 ...

  5. Redis 源码简洁剖析 12 - 一条命令的处理过程

    命令的处理过程 Redis server 和一个客户端建立连接后,会在事件驱动框架中注册可读事件--客户端的命令请求.命令处理对应 4 个阶段: 命令读取:对应 readQueryFromClient ...

  6. 【源码】Redis命令处理过程

    本文基于社区版Redis 4.0.8   1.命令解析 Redis服务器接收到的命令请求首先存储在客户端对象的querybuf输入缓冲区,然后解析命令请求的各个参数,并存储在客户端对象的argv和ar ...

  7. 关于Redis中交互的过程

    一.Redis启动 加载配置(命令行或者配置文件) 启动TCP监听,客户端的列表保存在redisserver的clients中 启动AE Event Loop事件,异步处理客户请求 事件处理器的主循环 ...

  8. Redis命令总结及其基础知识讲述

    1.redis的不同之处 Redis拥有其他数据库不具备的数据结构,又拥有内存存储(这使得redis的速度非常快),远程操作(使得redis可以与多个客户端和服务器进行连接).持久化(使得服务器可以在 ...

  9. REdis命令处理流程处理分析

    分析版本:REdis-5.0.4. REdis命令处理流程可分解成三个独立的流程(不包括复制和持久化): 1) 接受连接请求流程: 2) 接收请求数据和处理请求流程,在这个过程并不会发送处理结果给Cl ...

随机推荐

  1. jquery 调用js成员

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  2. openFrameworks 是一个旨在助力你进行开创性工作的开源 C++ 工具箱(是很多其它类库的组合)

    openFrameworks 是一个旨在助力你进行开创性工作的开源 C++ 工具箱,提供了简单且直观的实验框架.该工具箱具有常见的工具,并集合了众多常见的库: OpenGL, GLEW, GLUT,  ...

  3. Windows NT WinLogon Notify

    在NT系列Windows操作系统中,恶意软件可以通过关联Winlogon特定的事件来使自身被启动,如Lock,Logoff,Logon,Shutdown,StartScreenSaver,StartS ...

  4. SQL Server Update 所有表的某一列(列名相同,类型相同)数值

    ); WITH T AS (SELECT SchemaName = c.TABLE_SCHEMA, TableName = c.TABLE_NAME, ColumnName = c.COLUMN_NA ...

  5. 自定义View相关的博客收藏

    颜色: http://android.jobbole.com/83283/ 坐标: http://android.jobbole.com/83276/ 流程介绍: http://android.job ...

  6. Failed to recover corrupt cache entry

    RangeError java.lang.RuntimeException: ERROR: Failed to recover corrupt cache entry at com.sun.deplo ...

  7. 【Windows10 IoT开发系列】Powershell命令行实用程序

    原文:[Windows10 IoT开发系列]Powershell命令行实用程序 更新帐户密码: 强烈建议你更新默认的管理员帐户密码.若要更新帐户密码,你可以发出以下命令: net user Admin ...

  8. Windows系统版本判定那些事儿(有图,各种情况,很清楚)

    前言 本文并不是讨论Windows操作系统的版本来历和特点,也不是讨论为什么没有Win9,而是从程序员角度讨论下Windows获取系统版本的方法和遇到的一些问题.在Win8和Win10出来之后,在获取 ...

  9. SQL基础复习2

    一.视图 1.创建视图      创建视图后加 WITH CHECK OPTION 2.视图查询 数据库系统的处理方法: 视图消解法(View Resolution) 步骤: 进行有效性检查-> ...

  10. XP系統IIS最大連接數修改

    方法一: 安裝軟件 http://download.microsoft.com/download/iis50/Utility/5.0/NT45/EN-US/MtaEdt22.exe 然後進入  W3S ...