当前位置:首页 > PHP教程 > PHP总结归纳

redis源代码分析22–协议

redis默认使用tcp协议的6379端口,其协议是文本行格式的而不是二进制格式的,每一行都以rn结尾,非常容易理解。 参考protocolspecification.html就知道,发布到redis的命令有如下几种返回格式(对于不存在的值,会返回-1,此时client library应返回合适的n

redis默认使用tcp协议的6379端口,其协议是文本行格式的而不是二进制格式的,每一行都以”rn”结尾,非常容易理解。

参考protocolspecification.html就知道,发布到redis的命令有如下几种返回格式(对于不存在的值,会返回-1,此时client library应返回合适的nil对象(比如c语言的null),而不是空字符串):

1)第一个字节是字符“-”,后面跟着一行出错信息(error reply)

比如lpop命令,当操作的对象不是一个链表时,会返回如下出错信息:

“-err operation against a key holding the wrong kind of valuern”

2)第一个字节是字符“+”,后面跟着一行表示执行结果的提示信息(line reply)

比如set命令执行成功后,会返回”+okrn”

3)第一个字节是字符“$”,后面先跟一行,仅有一个数字,该数字表示下一行字符的个数(若不存在,则数字为-1)(bulk reply)

比如get命令,成功时返回值的类似于“$7rnmyvalue”,不存在时返回的信息为“$-1rn”

4)第一个字节是字符“*”,后面先跟一行,仅有一个数字,该数字表示bulk reply的个数(若不存在,则数字为-1)(multi-bulk reply)

比如lrange命令,若要求返回0–2之间的值,则成功时返回值类似于”*3rn$6rnvalue1rn$7rnmyvaluern$5rnhellorn”,不存在时返回的信息类似于”*-1rn”。

5)第一个字节是字符“:”,后面跟着一个整数值(integer reply)

比如incr命令,成功时会返回对象+1后的值。

而client发布命令的格式有如下几种,第一个字符串都必须是命令字,不同的参数之间用1个空格来分隔:
1)inline command::仅一行 

比如 exists命令,client发送的字节流类似于”exists mykeyrn”。
2)bulk command:类似于返回协议的bulk reply,一般有两行,第一行依次为“命令字 参数 一个数字”,该数字表示下一行字符的个数

比如set命令,client发送的字节流类似于”set mykey 5rnhellorn”。

3)multi-bulk command:跟返回协议的multi-bulk reply类似。

比如上面的set命令,用multi-bulk协议表示则为“*3rn$3rnsetrn$5rnmykeyrn$5rnhellorn”。

尽管对于某些命令该协议发送的字节流比bulk command形式要多,但它可以支持任何一种命令,支持跟多个二进制安全参数的命令(bulk command仅支持一个),也可以使得client library不修改代码就能支持redis新发布的命令(只要把不支持的命令按multi-bulk形式发布即可)。redis的官方文档中还提到,未来可能仅支持client采用multi-bulk command格式发布命令。

另外提一下,client library可以连续发布多条命令,而不是等到redis返回前一条命令的执行结果才发布新的命令,这种机制被称作pipelining,支持redis的client library大多支持这种机制,读者可自行参考。

最后来看看redis实现时用来返回信息的相关函数。

redis 会使用addreplysds、addreplydouble、addreplylonglong、addreplyulong、 addreplybulklen、addreplybulk、addreplybulkcstring等来打包不同的返回信息,最终调用addreply 来发送信息。

addreply会将发送信息添加到相应redisclient的reply链表尾部,并使用 sendreplytoclient来发送。sendreplytoclient会遍历reply链表,并依次发送,其间如果可以打包 reply(server.glueoutputbuf为真),则可以使用gluereplybuffersifneeded把reply链表中的值合并到一个缓冲区,然后一次性发送。

static void addreply(redisclient *c, robj *obj) {
    if (listlength(c->reply) == 0 &&
        (c->replstate == redis_repl_none ||
         c->replstate == redis_repl_online) &&
        aecreatefileevent(server.el, c->fd, ae_writable,
        sendreplytoclient, c) == ae_err) return;
    if (server.vm_enabled && obj->storage != redis_vm_memory) {
        obj = dupstringobject(obj);
        obj->refcount = 0; /* getdecodedobject() will increment the refcount */
    }
    listaddnodetail(c->reply,getdecodedobject(obj));
}
static void sendreplytoclient(aeeventloop *el, int fd, void *privdata, int mask) {
    redisclient *c = privdata;
    int nwritten = 0, totwritten = 0, objlen;
    robj *o;
    redis_notused(el);
    redis_notused(mask);
    /* use writev() if we have enough buffers to send */
    if (!server.glueoutputbuf &&
        listlength(c->reply) > redis_writev_threshold &&
        !(c->flags & redis_master))
    {
        sendreplytoclientwritev(el, fd, privdata, mask);
        return;
    }
    while(listlength(c->reply)) {
        if (server.glueoutputbuf && listlength(c->reply) > 1)
            gluereplybuffersifneeded(c);
        o = listnodevalue(listfirst(c->reply));
        objlen = sdslen(o->ptr);
        if (objlen == 0) {
            listdelnode(c->reply,listfirst(c->reply));
            continue;
        }
        if (c->flags & redis_master) {
            /* don't reply to a master */
            nwritten = objlen - c->sentlen;
        } else {
            nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen);
            if (nwritten <= 0) break;
        }
        c->sentlen += nwritten;
        totwritten += nwritten;
        /* if we fully sent the object on head go to the next one */
        if (c->sentlen == objlen) {
            listdelnode(c->reply,listfirst(c->reply));
            c->sentlen = 0;
        }
        /* note that we avoid to send more thank redis_max_write_per_event
         * bytes, in a single threaded server it's a good idea to serve
         * other clients as well, even if a very large request comes from
         * super fast link that is always able to accept data (in real world
         * scenario think about 'keys *' against the loopback interfae) */
        if (totwritten > redis_max_write_per_event) break;
    }
    if (nwritten == -1) {
        if (errno == eagain) {
            nwritten = 0;
        } else {
            redislog(redis_verbose,
                "error writing to client: %s", strerror(errno));
            freeclient(c);
            return;
        }
    }
    if (totwritten > 0) c->lastinteraction = time(null);
    if (listlength(c->reply) == 0) {
        c->sentlen = 0;
        aedeletefileevent(server.el,c->fd,ae_writable);
    }
}

关于client library的实现,可按照前面介绍的格式自己实现,也可以阅读现有的client library来加深理解。


【说明】本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:)!