redis源代码分析 – protocol

我们跟踪一个普通的get命令来遍历几个关键函数,熟悉协议处理的过程。

你可以通过telnet或者redis_cli、利用lib库发送请求给redis server。前者的是一种裸协议的请求发送到服务端,而后两者会对键入的请求进行协议组装帮助更好的解析(常见的是长度放到前头,还有添加阿协议类型)。

Requests格式

*参数的个数 CRLF
$第一个参数的长度CRLF
第一个参数CRLF
...
$第N个参数的长度CRLF
第N个参数CRLF

例如在redis_cli里键入get a,经过协议组装后的请求为

2\r\n$3\r\nget\r\n$1\r\na\r\n

下面这个图涵盖了接收request,处理请求,调用函数,发送reply的过程。

redis的网络事件库,我们在前面的文章已经讲过,readQueryFromClient先从fd中读取数据,先存储在c->querybuf里(networking.c 823)。接下来函数processInputBuffer来解析querybuf,上面说过如果是telnet发送的裸协议数据是没有*打头的表示参数个数的辅助信息,针对telnet的数据跳到processInlineBuffer函数,而其他则通过函数processMultibulkBuffer。
这两个函数的作用一样,解析c->querybuf的字符串,分解成多参数到c->argc和c->argv里面,argc表示参数的个数,argv是个redis_object的指针数组,每个指针指向一个redis_object, object的ptr里存储具体的内容,对于”get a“的请求转化后,argc就是2,argv就是

(gdb) p (char*)(*c->argv[0])->ptr
$28 = 0x80ea5ec "get"
(gdb) p (char*)(*c->argv[1])->ptr
$26 = 0x80e9fc4 "a"

协议解析后就执行命令。processCommand首先调用lookupCommand找到get对应的函数。在redis server 启动的时候会调用populateCommandTable函数(redis.c 830)把readonlyCommandTable数组转化成一个hash table(server.commands),lookupCommand就是一个简单的hash取值过程,通过key(get)找到相应的命令函数指针getCommand( t_string.c 437)。
getCommand比较简单,通过另一个全局的server.db这个hash table来查找key,并返回redis object,然后通过addReplyBulk函数返回结果。

下面介绍一下reply协议。
bulk replies是以$打头消息体,格式$值长度\r\n值\r\n,一般的get命令返回的结果就是这种个格式。

redis>get aaa
$3\r\nbbb\r\n

对应的的处理函数addReplyBulk

addReplyBulkLen(c,obj);
addReply(c,obj);
addReply(c,shared.crlf);

error messag是以-ERR 打头的消息体,后面跟着出错的信息,以\r\n结尾,针对命令出错。

redis>d
-ERR unknown command 'd'\r\n

处理的函数是addReplyError

addReplyString(c,"-ERR ",5);
addReplyString(c,s,len);
addReplyString(c,"\r\n",2);

integer reply 是以:打头,后面跟着数字和\r\n。

redis>incr a
:2\r\n

处理函数是

addReply(c,shared.colon);
addReply(c,o);
addReply(c,shared.crlf);

status reply以+打头,后面直接跟状态内容和\r\n

redis>ping
+PONG\r\n

这里要注意reply经过协议加工后,都会先保存在c->buf里,c->bufpos表示buf的长度。待到事件分离器转到写出操作(sendReplyToClient)的时候,就把c->buf的内容写入到fd里,c->sentlen表示写出长度。当c->sentlen=c->bufpos才算写完。

还有一种Multi-bulk replies,lrange、hgetall这类函数通常需要返回多个值,消息结构与请求的格式一模一样。相关的函数是setDeferredMultiBulkLength。临时数据存储在链表c->reply里,处理方式同其他的协议格式。

10 Comments

  1. huoxiaochai 说道:

    请教下redis的事务中途down机了,怎么保持acid特性呢?

    • hoterran 说道:

      程序没保证,可能事务里的某个命令成功,然后down机器,然会给客户的是所有命令失败。
      redis没有回滚和前滚。

  2. fuyou001 说道:

    请教楼主

    readQueryFromClient 方法里调用nread = read(fd, buf, REDIS_IOBUF_LEN); 之后就调用processInputBuffer
    这里没有用循环调用read 如果因为网络原因,导致client一个请求分二部分到server 第一个ip包到了,但第二个包还没到
    这里的read 是阻塞真到第二个包到吗

    源代码 read 只读到1024字节就会返回,在极端情况下,client发送一个批处理的command(假设超过了1024 byte)
    这时候readQueryFromClient是不是有会有问题??
    感谢!!!

    • hoterran 说道:

      这里使用的是非阻塞io,如果包没有读全,则会跳过这次read,把已读到的部分数据存在querybuf里,等到下次读全了才会处理。

      • fuyou001 说道:

        怎么判断读全clinet端数据呢,返回EOF吗

      • fuyou001 说道:

        nread = read(fd, buf, REDIS_IOBUF_LEN);
        ……
        if (nread) {
        c->querybuf = sdscatlen(c->querybuf,buf,nread);
        c->lastinteraction = time(NULL);
        } else {
        return;
        }
        processInputBuffer(c);
        没有发现在哪判断有没有读全 client发送的数据??

        • hoterran 说道:

          tcp是数据流何为读全数据呢?从querybuf读取然后分解到argc,就是processInputBuffer 会判断协议的正确性,如果协议只有部分,会留在querybuf里

        • jsz 说道:

          processInlineBuffer 和 processMultibulkBuffer 返回 REDIS_ERR,则表示还没有读全,等下一次进入 readQueryFromClient,就会继续往 querybuf 里写。如博主所说,判断是在 processInputBuffer 里做的,在 readQueryFromClient 的时候无从判断。

  3. [...] (3)    redis源代码分析 – protocol:http://www.hoterran.info/redis_protocol [...]

  4. [...] 5.REDIS源代码分析 – PROTOCOL [...]

Leave a Reply