Redis源码解析:18Hiredis同步API和回复解析API代码解析.docx

Redis的sentinel模式使用了Hiredis代码,Hiredis是redis数据库一个轻量级的C语言客户端库。它实现的向Redis发送命令的API函数redisCommand,使用方法类似于printf。因此只要熟悉redis命令,就可以很容易的使用该函数将redis命令字符串,转换成统一请求协议格式之后,发送给Redis服务器。

Hiredis库包含三类API:同步操作API、异步操作API和回复解析API。本文主要介绍同步操作API和回复解析API,下一篇介绍异步操作API。

一:同步操作API

所谓的同步操作,就是以阻塞的方式向Redis服务器建链,发送命令,接收命令回复。使用同步操作API,主要涉及以下三个API函数:

redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);

1:TCP建链

redisConnect函数创建一个上下文结构redisContext,并向Redis服务器发起TCP建链。该函数是同步建链API,因此该函数返回后,要么TCP已经建链成功了,要么建链期间发生了错误,可以通过检查redisContext结构的err和errstr属性得到错误类型和错误类型。

redisConnect的代码较简单,如下:

redisContext *redisConnect(const char *ip, int port) {
    redisContext *c;

    c = redisContextInit();
    if (c == NULL)
        return NULL;

    c->flags |= REDIS_BLOCK;
    redisContextConnectTcp(c,ip,port,NULL);
    return c;
}

redisContext上下文结构用于保存所有与Redis服务器连接的状态。比如socket描述符,输出缓存,回复解析器等。该结构的定义如下:

typedef struct redisContext {
    int err; /* Error flags, 0 when there is no error */
    char errstr[128]; /* String representation of error when applicable */
    int fd;
    int flags;
    char *obuf; /* Write buffer */
    redisReader *reader; /* Protocol reader */
} redisContext;

属性err为非0时,表示与Redis服务器的连接发生了错误,属性errstr就包含一个描述该错误的字符串。因此,每次与Redis进行交互之后,就需要检查该属性判断是否发生了错误,一旦有错误发生,则立即终止与Redis的链接。

属性fd就是与Redis服务器链接的socket描述符;flags表示客户端标志位,表示客户端当前的状态;

obuf就是输出缓存,当用户调用redisCommand向Redis发送命令时,命令字符串首先就是追加到该输出缓存中;

reader是一个回复解析器,后续在“回复解析API”中会详细介绍。

2:发送命令,接收回复

用户可以调用redisCommand函数向Redis服务器发送命令,该函数返回Redis的回复信息。该函数的原型如下:

void *redisCommand(redisContext *c, const char *format, ...)

该函数类似于printf,支持不定参数,使用起来很方便,比如:

    reply = redisCommand(context, "SET foo bar");

redisCommand函数返回NULL表示发生了错误,可以通过检查redisContext结构中的err得到错误类型;如果执行成功,则返回值是一个指向redisReply结构的指针,其中包含了Redis的回复信息。

可以在格式字符串中使用”%s”,表示在命令中插入一个字符串,此时使用strlen判断字符串的长度:

    reply = redisCommand(context, "SET foo %s", value);

如果需要在命令中传递二进制安全的字符串,可以使用”%b”,此时需要一个size_t类型的参数表示该字符串的长度:

    reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

redisCommand主要是通过redisvCommand实现的,而redisvCommand主要是通过redisvAppendCommand和__redisBlockForReply两个函数实现。它们的代码如下:

void *redisvCommand(redisContext *c, const char *format, va_list ap) {
    if (redisvAppendCommand(c,format,ap) != REDIS_OK)
        return NULL;
    return __redisBlockForReply(c);
}

void *redisCommand(redisContext *c, const char *format, ...) {
    va_list ap;
    void *reply = NULL;
    va_start(ap,format);
    reply = redisvCommand(c,format,ap);
    va_end(ap);
    return reply;
}

redisvAppendCommand函数作用是解析用户的输入,并将用户输入的命令字符串转换成Redis统一请求协议的格式,存储到上下文结构redisContext中的输出缓存obuf中,它的代码如下:

int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
    char *cmd;
    int len;

    len = redisvFormatCommand(&cmd,format,ap);
    if (len == -1) {
        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
        return REDIS_ERR;
    }

    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
        free(cmd);
        return REDIS_ERR;
    }

    free(cmd);
    return REDIS_OK;
}

int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
    sds newbuf;

    newbuf = sdscatlen(c->obuf,cmd,len);
    if (newbuf == NULL) {
        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
        return REDIS_ERR;
    }

    c->obuf = newbuf;
    return REDIS_OK;
}

redisvAppendCommand首先调用redisvFormatCommand函数,用于解析用户输入的命令字符串,并将字符串转换成协议格式之后,保存在cmd中。然后调用__redisAppendCommand函数,将cmd追加到输出缓存c->obuf中。代码较简单,不在赘述。

在redisvCommand函数中,调用redisvAppendCommand之后,接下来就是调用__redisBlockForReply函数,将输出缓存中的内容发送给Redis服务器,并读取Redis的回复,并解析之。

__redisBlockForReply函数主要是通过redisGetReply实现的,它们的代码如下:

static void *__redisBlockForReply(redisContext *c) {
    void *reply;

    if (c->flags & REDIS_BLOCK) {
        if (redisGetReply(c,&reply) != REDIS_OK)
            return NULL;
        return reply;
    }
    return NULL;
}
int redisGetReply(redisContext *c, void **reply) {
    int wdone = 0;
    void *aux = NULL;

    /* Try to read pending replies */
    if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
        return REDIS_ERR;

    /* For the blocking context, flush output buffer and read reply */
    if (aux == NULL && c->flags & REDIS_BLOCK) {
        /* Write until done */
        do {
            if (redisBufferWrite(c,&wdone) == REDIS_ERR)
                return REDIS_ERR;
        } while (!wdone);

        /* Read until there is a reply */
        do {
            if (redisBufferRead(c) == REDIS_ERR)
                return REDIS_ERR;
            if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
                return REDIS_ERR;
        } while (aux == NULL);
    }

    /* Set reply object */
    if (reply != NULL) *reply = aux;
    return REDIS_OK;
}

在redisGetReply中,首先是循环调用redisBufferWrite,将输出缓存c->obuf中的所有内容发送给Redis。然后循环调用redisBufferRead,读取Redis的回复,并调用函数redisGetReplyFromReader对回复信息进行解析。

redisBufferRead函数的代码如下:

int redisBufferRead(redisContext *c) {
    char buf[1024*16];
    int nread;

    /* Return early when the context has seen an error. */
    if (c->err)
        return REDIS_ERR;

    nread = read(c->fd,buf,sizeof(buf));
    if (nread == -1) {
        if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
            /* Try again later */
        } else {
            __redisSetError(c,REDIS_ERR_IO,NULL);
            return REDIS_ERR;
        }
    } else if (nread == 0) {
        __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
        return REDIS_ERR;
    } else {
        if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
            __redisSetError(c,c->reader->err,c->reader->errstr);
            return REDIS_ERR;
        }
    }
    return REDIS_OK;
}

该函数主要是从socket中读取数据到buf中,然后通过函数redisReaderFeed,将buf内容追加到解析器的输入缓存中。redisReaderFeed函数属于回复解析API函数。

二:回复解析API

回复解析API的函数主要有下面几个:

    redisReader *redisReaderCreate(void);
    void redisReaderFree(redisReader *reader);
    int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
    int redisReaderGetReply(redisReader *reader, void **reply);

1:输入缓存

解析器结构redisReader,是回复解析API最主要的数据结构。它的部分定义如下:

/* State for the protocol parser */
typedef struct redisReader {
    int err; /* Error flags, 0 when there is no error */
    char errstr[128]; /* String representation of error when applicable */

    char *buf; /* Read buffer */
    size_t pos; /* Buffer cursor */
    size_t len; /* Buffer length */
    size_t maxbuf; /* Max length of unused buffer */
    ...
} redisReader;

其中的err和errstr属性与redisContext结构中的err和errstr属性作用一样,都是用于保存错误类型和错误信息的;

buf属性就是输入缓存,redisReaderFeed函数将读取到的Redis回复信息都存储到该缓存中,该缓存根据回复信息可以动态扩容。len表示当前缓存的容量;pos表示缓存当前的读取索引,每次读取输入缓存时,都是从reader->buf + reader->pos处开始读取,读取数据之后,会增加pos的值;

maxbuf属性表示输入缓存所允许的最大闲置空间。为了节省内存空间,当buf为空,并且当前buf的闲置空间大于reader->maxbuf时,就会释放r->buf,重新为其申请空间。该属性的默认值为16K。如果置为0,表示无此限制。

 

redisReaderFeed就是将从socket读取的Redis回复信息,追加到输入缓存的函数。其代码如下:

int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
    sds newbuf;

    /* Return early when this reader is in an erroneous state. */
    if (r->err)
        return REDIS_ERR;

    /* Copy the provided buffer. */
    if (buf != NULL && len >= 1) {
        /* Destroy internal buffer when it is empty and is quite large. */
        if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
            sdsfree(r->buf);
            r->buf = sdsempty();
            r->pos = 0;

            /* r->buf should not be NULL since we just free'd a larger one. */
            assert(r->buf != NULL);
        }

        newbuf = sdscatlen(r->buf,buf,len);
        if (newbuf == NULL) {
            __redisReaderSetErrorOOM(r);
            return REDIS_ERR;
        }

        r->buf = newbuf;
        r->len = sdslen(r->buf);
    }

    return REDIS_OK;
}

2:解析

在redisGetReply函数中,将Redis的回复信息追加到解析器输入缓存之后,接下来就会调用函数redisGetReplyFromReader对解析器的输入缓存中的消息进行解析,解析的内容以redisReply结构进行组织。

如果回复信息是嵌套的话,则形成一颗以redisReply结构为节点的多叉树;如果回复信息只是基本信息的话,则该树仅仅包含一个根节点。redisCommand函数就是返回一个指向redisReply结构树根节点的指针。redisReply结构树的宽度没有限制,但是深度的最大值为7,也就是仅允许最多7层嵌套。

首先看一下redisReply结构的定义如下:

/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
    int type;
    long long integer;
    int len;
    char *str;
    size_t elements;
    struct redisReply **element;
} redisReply;

该结构中的type成员表示Redis回复信息的类型,可以有下面几种类型:

REDIS_REPLY_STATUS:状态回复,状态信息以‘+‘开头。str属性保存Redis回复的状态信息字符串,该字符串的长度保存在len属性中。

REDIS_REPLY_ERROR:错误回复,错误信息以‘-‘开头。str属性保存Redis回复的错误信息字符串,该字符串的长度保存在len属性中。

REDIS_REPLY_INTEGER:整数回复,整数信息以‘:‘开头。integer 属性保存Redis回复的整数值。

REDIS_REPLY_STRING:单行字符串回复,这种信息以‘$‘开头。str属性保存Redis回复的字符串信息,该字符串的长度保存在len属性中。

REDIS_REPLY_NIL:Redis回复”nil”。

以上的类型可以称为基本类型。

REDIS_REPLY_ARRAY:数组回复,也就是嵌套回复,数组信息以‘*‘开头,后接数组元素个数。数组中的元素可以是以上所有基本类型,也可以是REDIS_REPLY_ARRAY类型,也就是数组嵌套数组。

数组元素的个数保存在elements属性中,数组元素也以redisReply结构表示,指向数组元素的指针保存在element指针数组中,也就是说,指针数组element中保存了所有孩子节点的指针。

经过回复解析API函数redisReaderGetReply的解析之后,最终形成的redisReply结构树,非叶子节点只能是REDIS_REPLY_ARRAY类型,叶子节点的类型只能是基本类型。

处理数组信息的代码较复杂,以一个例子说明。假设Redis的回复信息是:"*3\r\n*3\r\n:11\r\n:12\r\n:13\r\n*3\r\n:21\r\n:22\r\n:23\r\n:31\r\n"。

分析该字符串,第一个字符为”*”,表明这是一条数组回复,后面的3表示数组元素的个数,因此,最终形成的树,根节点有3个孩子节点。

接下来就是根节点各个孩子节点的信息。第一个孩子节点首字符还是”*”,说明该孩子节点又是一个数组信息,它也有3个孩子。接下来就是3个孩子信息,也就是3个整数:11,12和13。

接下来是根节点第二个孩子节点的信息。首字符还是”*”,说明该孩子节点也是一个数组,它也有3个孩子,分别是整数:21,22和23。

接下来是根节点最后一个孩子的信息,首字符是”:”,说明该孩子节点是一个整数,整数值为31。

根据以上的分析,最终形成的树如下图:

上图中,非叶子节点都是REDIS_REPLY_ARRAY类型的redisReply结构,叶子节点是REDIS_REPLY_INTEGER类型的redisReply结构。

回复解析API函数redisReaderGetReply的作用,就是解析回复信息,最终形成一颗这样的redisReply结构树。

在回复解析API的代码中,使用redisReadTask任务结构解析回复信息,构建每个redisReply结构节点,填充到树中合适的位置。

redisReadTask结构包含解析器结构redisReader中,redisReader结构剩下的定义如下:

typedef struct redisReader {
    ...
    redisReadTask rstack[9];
    int ridx; /* Index of current read task */
    void *reply; /* Temporary reply pointer */

    redisReplyObjectFunctions *fn;
    void *privdata;
} redisReader;

在redisReader结构中,redisReadTask结构数组rstack大小为9。rstack[0]用于处理redisReply结构树中的根节点;rstack[1]表示处理redisReply结构树中的第一层子节点,以此类推。

ridx属性表示当前正在处理第几层子节点;fn属性是一个redisReplyObjectFunctions结构体,该结构中包含了用于生成各种类型redisReply结构的函数;

reply属性指向redisReply结构树中的根节点

构建每个redisReply结构节点的redisReadTask结构定义如下:

typedef struct redisReadTask {
    int type;
    int elements; /* number of elements in multibulk container */
    int idx; /* index in parent (array) object */
    void *obj; /* holds user-generated value for a read task */
    struct redisReadTask *parent; /* parent task */
    void *privdata; /* user-settable arbitrary field */
} redisReadTask;

type表示该redisReadTask结构当前处理的信息类型,与其当前构建的redisReply结构节点中的type一致;

elements表示当前构建的REDIS_REPLY_ARRAY类型的redisReply结构节点中,包含的子节点数目。也就是redisReply结构节点中,数组element中的元素个数;idx表示当前构建的redisReply结构节点,在其父节点element数组中的索引;obj就是指向当前正在构建的REDIS_REPLY_ARRAY 类型的redisReply结构节点,parent表示正在处理当前节点的父节点的redisReadTask结构。

回复解析API函数redisReaderGetReply的代码如下:

int redisReaderGetReply(redisReader *r, void **reply) {
    /* Default target pointer to NULL. */
    if (reply != NULL)
        *reply = NULL;

    /* Return early when this reader is in an erroneous state. */
    if (r->err)
        return REDIS_ERR;

    /* When the buffer is empty, there will never be a reply. */
    if (r->len == 0)
        return REDIS_OK;

    /* Set first item to process when the stack is empty. */
    if (r->ridx == -1) {
        r->rstack[0].type = -1;
        r->rstack[0].elements = -1;
        r->rstack[0].idx = -1;
        r->rstack[0].obj = NULL;
        r->rstack[0].parent = NULL;
        r->rstack[0].privdata = r->privdata;
        r->ridx = 0;
    }

    /* Process items in reply. */
    while (r->ridx >= 0)
        if (processItem(r) != REDIS_OK)
            break;

    /* Return ASAP when an error occurred. */
    if (r->err)
        return REDIS_ERR;

    /* Discard part of the buffer when we've consumed at least 1k, to avoid
     * doing unnecessary calls to memmove() in sds.c. */
    if (r->pos >= 1024) {
        sdsrange(r->buf,r->pos,-1);
        r->pos = 0;
        r->len = sdslen(r->buf);
    }

    /* Emit a reply when there is one. */
    if (r->ridx == -1) {
        if (reply != NULL)
            *reply = r->reply;
        r->reply = NULL;
    }
    return REDIS_OK;
}

首先,将r->ridx置为0,然后初始化r->rstack[0],表示接下来开始构建根节点。

接下来的语句,就是循环调用processItem函数,直到r->ridx再次等于-1。循环调用processItem函数的过程,就是以深度优先的顺序构建redisReply结构树的过程。

processItem函数的代码如下:

static int processItem(redisReader *r) {
    redisReadTask *cur = &(r->rstack[r->ridx]);
    char *p;

    /* check if we need to read type */
    if (cur->type < 0) {
        if ((p = readBytes(r,1)) != NULL) {
            switch (p[0]) {
            case '-':
                cur->type = REDIS_REPLY_ERROR;
                break;
            case '+':
                cur->type = REDIS_REPLY_STATUS;
                break;
            case ':':
                cur->type = REDIS_REPLY_INTEGER;
                break;
            case '$':
                cur->type = REDIS_REPLY_STRING;
                break;
            case '*':
                cur->type = REDIS_REPLY_ARRAY;
                break;
            default:
                __redisReaderSetErrorProtocolByte(r,*p);
                return REDIS_ERR;
            }
        } else {
            /* could not consume 1 byte */
            return REDIS_ERR;
        }
    }

    /* process typed item */
    switch(cur->type) {
    case REDIS_REPLY_ERROR:
    case REDIS_REPLY_STATUS:
    case REDIS_REPLY_INTEGER:
        return processLineItem(r);
    case REDIS_REPLY_STRING:
        return processBulkItem(r);
    case REDIS_REPLY_ARRAY:
        return processMultiBulkItem(r);
    default:
        assert(NULL);
        return REDIS_ERR; /* Avoid warning. */
    }
}

首先得到构建当前节点的redisReadTask结构cur,然后从输入缓存中读取首个字符,以判断接下来的回复信息的类型,赋值到cur->type中。

得到类型信息之后,就调用不同的函数处理不同的类型。首先看一下处理数组类型的函数processMultiBulkItem的实现:

static int processMultiBulkItem(redisReader *r) {
    redisReadTask *cur = &(r->rstack[r->ridx]);
    void *obj;
    char *p;
    long elements;
    int root = 0;

    /* Set error for nested multi bulks with depth > 7 */
    if (r->ridx == 8) {
        __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
            "No support for nested multi bulk replies with depth > 7");
        return REDIS_ERR;
    }

    if ((p = readLine(r,NULL)) != NULL) {
        elements = readLongLong(p);
        root = (r->ridx == 0);

        if (elements == -1) {
            if (r->fn && r->fn->createNil)
                obj = r->fn->createNil(cur);
            else
                obj = (void*)REDIS_REPLY_NIL;

            if (obj == NULL) {
                __redisReaderSetErrorOOM(r);
                return REDIS_ERR;
            }

            moveToNextTask(r);
        } else {
            if (r->fn && r->fn->createArray)
                obj = r->fn->createArray(cur,elements);
            else
                obj = (void*)REDIS_REPLY_ARRAY;

            if (obj == NULL) {
                __redisReaderSetErrorOOM(r);
                return REDIS_ERR;
            }

            /* Modify task stack when there are more than 0 elements. */
            if (elements > 0) {
                cur->elements = elements;
                cur->obj = obj;
                r->ridx++;
                r->rstack[r->ridx].type = -1;
                r->rstack[r->ridx].elements = -1;
                r->rstack[r->ridx].idx = 0;
                r->rstack[r->ridx].obj = NULL;
                r->rstack[r->ridx].parent = cur;
                r->rstack[r->ridx].privdata = r->privdata;
            } else {
                moveToNextTask(r);
            }
        }

        /* Set reply if this is the root object. */
        if (root) r->reply = obj;
        return REDIS_OK;
    }

    return REDIS_ERR;
}

首先得到构建当前节点的redisReadTask结构cur,然后调用readLine函数,从输入缓存中读取一行信息(”\r\n”之前的内容),并解析出当前节点中包含的元素个数elements。

如果elements不是-1,说明正确解析到了数组元素个数,接下来调用r->fn->createArray创建一个数组类型的redisReply结构节点。然后将创建的redisReply结构信息记录到cur中:将元素个数记录到cur->elements中,将创建的redisReply记录到cur->obj中:

cur->elements = elements;
cur->obj = obj;

数组类型的redisReply结构节点创建完成后,接下来就是开始构建其各个子节点。首先就是将r->ridx加1,并初始化r->rstack[r->ridx]结构,注意这里置r->rstack[r->ridx].idx为0.表示接下来首先构建第一个子节点。

下面是创建数组类型redisReply结构的函数createArrayObject的代码:

static void *createArrayObject(const redisReadTask *task, int elements) {
    redisReply *r, *parent;

    r = createReplyObject(REDIS_REPLY_ARRAY);
    if (r == NULL)
        return NULL;

    if (elements > 0) {
        r->element = calloc(elements,sizeof(redisReply*));
        if (r->element == NULL) {
            freeReplyObject(r);
            return NULL;
        }
    }

    r->elements = elements;

    if (task->parent) {
        parent = task->parent->obj;
        assert(parent->type == REDIS_REPLY_ARRAY);
        parent->element[task->idx] = r;
    }
    return r;
}

该函数中,重点是要理解下面的代码:

    if (task->parent) {
        parent = task->parent->obj;
        assert(parent->type == REDIS_REPLY_ARRAY);
        parent->element[task->idx] = r;
    }

这段代码的作用,就是将指向新创建的redisReply结构节点的指针r,存放到其父节点的element数组中,存放索引就是task->idx。

如果task->parent不为NULL,说明当前新建的redisReply结构节点具有父节点,根据当前task得到该父节点redisReply结构parent。然后将当前节点放到存储到父节点的element数组中的task-idx索引处。

接下来是moveToNextTask函数的实现,该函数的主要作用,实际上是变更属性r->ridx和cur->idx。说白了,就是为下一个要创建的节点,找到合适的位置。代码如下:

static void moveToNextTask(redisReader *r) {
    redisReadTask *cur, *prv;
    while (r->ridx >= 0) {
        /* Return a.s.a.p. when the stack is now empty. */
        if (r->ridx == 0) {
            r->ridx--;
            return;
        }

        cur = &(r->rstack[r->ridx]);
        prv = &(r->rstack[r->ridx-1]);
        assert(prv->type == REDIS_REPLY_ARRAY);
        if (cur->idx == prv->elements-1) {
            r->ridx--;
        } else {
            /* Reset the type because the next item can be anything */
            assert(cur->idx < prv->elements);
            cur->type = -1;
            cur->elements = -1;
            cur->idx++;
            return;
        }
    }
}

在while循环中,首先得到处理当前节点的redisReadTask结构cur,然后是正处理该节点父节点的redisReadTask结构prv。

cur->idx记录了当前处理的节点,其在父节点中的element数组中的索引,也就是当前节点是父节点的第几个孩子。

prv->elements表示当前节点的父节点,共有几个孩子。

因此,如果cur->idx小于prv->elements的话,则接下来,cur结构要开始构建当前节点的下一个兄弟节点了,因此将cur->idx加1。

如果cur->idx等于prv->elements的话,说明当前节点,已经是其父节点最后一个孩子节点了。接下来,就开始构建当前节点的叔叔结点了(父节点的兄弟节点),因此r->ridx--,表示回溯。上移一层,将父结点变成当前节点,然后接着判断新的cur点在其父节点中是否是最后一个孩子,若是,则接着回溯,否则开始构建其兄弟节点。

如果当前节点已经是根节点了(r->ridx == 0),因为根节点没有兄弟节点,因此将r->ridx置为-1后,直接返回。

构建好一颗redisReply结构树之后,如果需要释放它,可以通过API函数freeReplyObject实现,代码如下:

void freeReplyObject(void *reply) {
    redisReply *r = reply;
    size_t j;

    switch(r->type) {
    case REDIS_REPLY_INTEGER:
        break; /* Nothing to free */
    case REDIS_REPLY_ARRAY:
        if (r->element != NULL) {
            for (j = 0; j < r->elements; j++)
                if (r->element[j] != NULL)
                    freeReplyObject(r->element[j]);
            free(r->element);
        }
        break;
    case REDIS_REPLY_ERROR:
    case REDIS_REPLY_STATUS:
    case REDIS_REPLY_STRING:
        if (r->str != NULL)
            free(r->str);
        break;
    }
    free(r);
}

标准的深度优先顺序进行释放。不再赘述。

以上就是回复解析API的主要工作流程。构建redisReply结构树和redisReadTask结构的作用比较晦涩,但是却是一个很好的构建多叉树的例子。学习代码时,脑子中跟着代码逐步建立这颗树就好理解了。

参考:

http://blog.csdn.net/it_small_farmer/article/details/41726293

时间: 2024-11-06 12:18:19

Redis源码解析:18Hiredis同步API和回复解析API代码解析.docx的相关文章

Redis源码解析:15Resis主从复制之从节点流程

Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(commandpropagate)两个操作: 同步操作用于将从节点的数据库状态更新至主节点当前所处的数据库状态: 命令传播操作则用于在主节点的数据库状态被修改,导致主从节点的数据库状态不一致时,让主从节点的数据库重新回到一致状态: 1:同步 当客户端向从节点发送SLAYEOF命令,或者从节点的配置文件中配置了

redis源码解析之事件驱动

Redis 内部有个小型的事件驱动,它主要处理两项任务: 文件事件:使用I/O多路复用技术处理多个客户端请求,并返回执行结果. 时间事件:维护服务器的资源管理,状态检查. 主要的数据结构包括文件事件结构体,时间事件结构体,触发事件结构体,事件循环结构体 /* File event structure */ typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE) */ aeFileProc *rfileProc

Redis源码解析:13Redis中的事件驱动机制

Redis中,处理网络IO时,采用的是事件驱动机制.但它没有使用libevent或者libev这样的库,而是自己实现了一个非常简单明了的事件驱动库ae_event,主要代码仅仅400行左右. 没有选择libevent或libev的原因大概在于,这些库为了迎合通用性造成代码庞大,而且其中的很多功能,比如监控子进程,复杂的定时器等,这些都不是Redis所需要的. Redis中的事件驱动库只关注网络IO,以及定时器.该事件库处理下面两类事件: a:文件事件(file  event):用于处理Redis

Redis源码解析(十五)--- aof-append only file解析

继续学习redis源码下的Data数据相关文件的代码分析,今天我看的是一个叫aof的文件,这个字母是append ONLY file的简称,意味只进行追加文件操作.这里的文件追加记录时为了记录数据操作的改变记录,用以异常情况的数据恢复的.类似于之前我说的redo,undo日志的作用.我们都知道,redis作为一个内存数据库,数据的每次操作改变是先放在内存中,等到内存数据满了,在刷新到磁盘文件中,达到持久化的目的.所以aof的操作模式,也是采用了这样的方式.这里引入了一个block块的概念,其实就

Redis源码分析(一)--Redis结构解析

从今天起,本人将会展开对Redis源码的学习,Redis的代码规模比较小,非常适合学习,是一份非常不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望最终能把他啃完吧,C语言好久不用,快忘光了.分析源码的第一步,先别急着想着从哪开始看起,先浏览一下源码结构,可以模块式的渐入,不过比较坑爹的是,Redis的源码全部放在在里面的src目录里,一下90多个文件统统在里面了,所以我选择了拆分,按功能拆分,有些文件你看名字就知道那是干什么的.我拆分好后的而结果如下: 11个包,这样每

Redis源码解析——双向链表

相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的.教科书般简单的库.但是作为Redis源码的一部分,我决定还是要讲一讲的.(转载请指明出于breaksoftware的csdn博客) 基本结构 首先我们看链表元素的结构.因为是双向链表,所以其基本元素应该有一个指向前一个节点的指针和一个指向后一个节点的指针,还有一个记录节点值的空间 typedef struct listNode { struct listNode *prev; struct listNode *next;

redis源码解析之内存管理

zmalloc.h的内容如下: 1 void *zmalloc(size_t size); 2 void *zcalloc(size_t size); 3 void *zrealloc(void *ptr, size_t size); 4 void zfree(void *ptr); 5 char *zstrdup(const char *s); 6 size_t zmalloc_used_memory(void); 7 void zmalloc_enable_thread_safeness(v

Redis源码解析之ziplist

Ziplist是用字符串来实现的双向链表,对于容量较小的键值对,为其创建一个结构复杂的哈希表太浪费内存,所以redis 创建了ziplist来存放这些键值对,这可以减少存放节点指针的空间,因此它被用来作为哈希表初始化时的底层实现.下图即ziplist 的内部结构. Zlbytes是整个ziplist 所占用的空间,必要时需要重新分配. Zltail便于快速的访问到表尾节点,不需要遍历整个ziplist. Zllen表示包含的节点数. Entries表示用户增加上去的节点. Zlend是一个255

redis源码解析之dict数据结构

dict 是redis中最重要的数据结构,存放结构体redisDb中. typedef struct dict { dictType *type; void *privdata; dictht ht[2]; int rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict; 其中type是特定结构的处

Redis源码解析——字符串map

本文介绍的是Redis中Zipmap的原理和实现.(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结构包含一个头信息.一系列字符串对(之后把一个"字符串对"称为一个"元素"(ELE))和一个尾标记.用图形表示该结构就是: Redis源码中并没有使用结构体来表达该结构.因为这个结构在内存中是连续的,而除了HEAD和红色背景的尾标记END(恒定是0xFF)是固定的8位,其