参考文献:

  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中将会以如下的格式存在:

  1. void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
  2. redisClient *c = (redisClient*) privdata;
  3. int nread, readlen;
  4. size_t qblen;
  5. REDIS_NOTUSED(el);
  6. REDIS_NOTUSED(mask);
  7. // 设置服务器的当前客户端
  8. server.current_client = c;
  9. // 读入长度(默认为 16 MB)
  10. readlen = REDIS_IOBUF_LEN;
  11. ........
  12. ........
  13. // 读入内容到查询缓存
  14. nread = read(fd, c->querybuf+qblen, readlen);
  15. ........
  16. ........
  17. processInputBuffer(c);
  18. }

命令参数的解析

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

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

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

    2.1 processInlineBuffer

    2.2 processMultibulkBuffer
  1. // 处理客户端输入的命令内容
  2. void processInputBuffer(redisClient *c) {
  3. while(sdslen(c->querybuf)) {
  4. .......
  5. .......
  6. /* Determine request type when unknown. */
  7. // 判断请求的类型
  8. // 两种类型的区别可以在 Redis 的通讯协议上查到:
  9. // http://redis.readthedocs.org/en/latest/topic/protocol.html
  10. // 简单来说,多条查询是一般客户端发送来的,
  11. // 而内联查询则是 TELNET 发送来的
  12. if (!c->reqtype) {
  13. if (c->querybuf[0] == '*') {
  14. // 多条查询
  15. c->reqtype = REDIS_REQ_MULTIBULK;
  16. } else {
  17. // 内联查询
  18. c->reqtype = REDIS_REQ_INLINE;
  19. }
  20. }
  21. // 将缓冲区中的内容转换成命令,以及命令参数
  22. if (c->reqtype == REDIS_REQ_INLINE) {
  23. if (processInlineBuffer(c) != REDIS_OK) break;
  24. } else if (c->reqtype == REDIS_REQ_MULTIBULK) {
  25. if (processMultibulkBuffer(c) != REDIS_OK) break;
  26. } else {
  27. redisPanic("Unknown request type");
  28. }
  29. /* Multibulk processing could see a <= 0 length. */
  30. if (c->argc == 0) {
  31. resetClient(c);
  32. } else {
  33. /* Only reset the client when the command was executed. */
  34. // 执行命令,并重置客户端
  35. if (processCommand(c) == REDIS_OK)
  36. resetClient(c);
  37. }
  38. }
  39. }

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将被转换为:

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

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

查找命令的实现

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

查找命令

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

  1. // 特别处理 quit 命令
  2. if (!strcasecmp(c->argv[0]->ptr,"quit")) {
  3. addReply(c,shared.ok);
  4. c->flags |= REDIS_CLOSE_AFTER_REPLY;
  5. return REDIS_ERR;
  6. }
  7. /* Now lookup the command and check ASAP about trivial error conditions
  8. * such as wrong arity, bad command name and so forth. */
  9. // 查找命令,并进行命令合法性检查,以及命令参数个数检查
  10. c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
  11. if (!c->cmd) {
  12. // 没找到指定的命令
  13. flagTransaction(c);
  14. addReplyErrorFormat(c,"unknown command '%s'",
  15. (char*)c->argv[0]->ptr);
  16. return REDIS_OK;
  17. } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
  18. (c->argc < -c->cmd->arity)) {
  19. // 参数个数错误
  20. flagTransaction(c);
  21. addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
  22. c->cmd->name);
  23. return REDIS_OK;
  24. }

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

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

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

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

根据这个结构体,我们可以看到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函数里面,在真正的执行一个命令的实现函数。

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

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

  1. void call(redisClient *c, int flags)

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

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

执行后续工作

在执行完命令的实现函数之后,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. SICP 1.17-1.19

    1.16 -------------> 不考虑0的情况 <------------ (define (fe b n) (define (fet m c) (cond ((= m n) c) ...

  2. laravel在wamp中输入地址后总是无法访问

    在wamp中的apache中conf的httpd.conf #LoadModule rewrite_module modules/mod_rewrite.so 改为 LoadModule rewrit ...

  3. Win8Metro(C#)数字图像处理--2.18图像平移变换

    原文:Win8Metro(C#)数字图像处理--2.18图像平移变换  [函数名称] 图像平移变换函数TranslationProcess(WriteableBitmap src,int x,in ...

  4. Socket进阶篇

    Socket简介 1,socket是什么? 2,socket的作用 3,socket怎么用 4,socket的扩展 ——————————————————- socket是什么? Socket这个名词现 ...

  5. .NET中的GC总结

    来自<CLR via C# 3rd Edition>总结 只管理内存,非托管资源,如文件句柄,GDI资源,数据库连接等还需要用户去管理 循环引用,网状结构等的实现会变得简单.GC的标志也压 ...

  6. 微信小程序把玩(一)Hello WeApp

    原文:微信小程序把玩(一)Hello WeApp 本篇默认已经成功安装微信小程序工具 新建项目 AppID查看公众开发平台设置查看(https://mp.weixin.qq.com) 项目名称随意填写 ...

  7. WebBrowser 的 DocumentCompleted事件不执行的解决方法

    原文:WebBrowser 的 DocumentCompleted事件不执行的解决方法 WebBrowser 的 DocumentCompleted事件不执行的解决方法: 使用WebBrowser的P ...

  8. RedHat 7.3 修改ASM磁盘绑定路径

    RedHat 7中,很多命令发生了改变,绑定磁盘不再是start_udev,而是udevadm,具体绑定方式,请看另一篇博文: http://www.cnblogs.com/zx3212/p/6757 ...

  9. MongoDB.Driver 管道 Aggregate

    目前mongodb for C#这个驱动,在进行Aggregate时,只支持BsonDocument类型,也就是说,你的集合collection也必须返回的是BsonDocument,而实体类型是不可 ...

  10. WCF研究-前篇

    前篇 1.从SOA说起 2.什么是WCF 3.WCF通信模型 4.Endpoint与ABC以及元数据    1.SOA (Service Oriented  Architecture) Ø 一种组件架 ...