redis之字符串命令源码解析(一)

形象化设计模式实战             HELLO!架构

在redis的使用中,set/get无疑是使用最普遍的命令,我先telnet连接运行看看

先看get命令,获取一个key服务器返回了两行内容,是"$3\r\n123\r\n"(\r\n为换行符),不难发现3就是“123”的长度,redis的官方文档get返回值为:

Bulk string reply: the value of key, or nil when key does not exist.

可以点击超链接看里面的解释,发现确实如此,那现在就从源码看看get是如何获取数据的。

1、内部数据结构之sds(Simple Dynamic String)

在传统C语言中,表示字符串通常是char *,由于char *类型的功能单一,抽象层次低,并且不能高效地支持一些Redis常用的操作(比如追加操作和长度计算操作),所以在Redis程序内部,绝大部分情况下都会使用sds而不是char *来表示字符串

sds的结构

现假如运行命令 set test "hello redis"

那么set命令创建并保存"test"到一个sdshdr中:(最终保存到数据库是char *类型,指向sdshdr->buf)

struct sdshdr
{
    len  = 4;
    free = 0;
    buf = "test\0";
};

 

将"hello redis"保存到另一个sdshdr中:(最终保存到数据库是robj类型,后序会讲解)

struct sdshdr
{
    len  = 11;
    free = 0;
    buf = "hello redis\0";
};

 那么如果再运行append test " now!",那是不是就会变成

struct sdshdr
{
    len  = 16;
    free = 0;
    buf = "hello redis now!\0";
};

 这样呢?不是!

sdsMakeRoomFor函数描述此场景的内存预分配优化策略

/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 * 
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
/*
 * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
 * buf 至少会有 addlen + 1 长度的空余空间
 * (额外的 1 字节是为 \0 准备的)
 *
 * 返回值
 *  sds :扩展成功返回扩展后的 sds
 *        扩展失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {

    struct sdshdr *sh, *newsh;

    // 获取 s 目前的空余空间长度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空余空间已经足够,无须再进行扩展,直接返回
    if (free >= addlen) return s;

    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的长度
    newlen = (len+addlen);

    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 SDS_MAX_PREALLOC(1024*1024)
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
        // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

    // 内存不足,分配失败,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空余长度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}

很明显在append后,test的长度只有15,远远不够1024*1024的,所以它的新长度应该是16*2+1=31

struct sdshdr
{
    len  = 33;
    free = 16;
    buf = "hello redis now!\0";
};

2、字符串编码

上面说set命令会将字符串数据保存在sdshdr中,那如果是一个数字也会如此吗?答案是不会!

 object.c的tryObjectEncoding方法

/* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 21 chars is not
     * representable as a 32 nor 64 bit integer. */
    // 检查字符串的长度,不对长度小于 21 的字符串进行编码
    // 也不对可以被解释为整数的字符串进行编码
    len = sdslen(s);
    if (len <= 21 && string2l(s,len,&value)) {
        /* This object is encodable as a long. Try to use a shared object.
         * Note that we avoid using shared integers when maxmemory is used
         * because every object needs to have a private LRU field for the LRU
         * algorithm to work well. */
        if (server.maxmemory == 0 &&
            value >= 0 &&
            value < REDIS_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
            //将encoding转为REDIS_ENCODING_INT
            o->encoding = REDIS_ENCODING_INT;
            o->ptr = (void*) value;
            return o;
        }
    }
 

由此可见,字符串类型有两种编码:

1、REDIS_ENCODING_INT使用long类型来保存long类型值

2、REDIS_ENCODING_RAW 使用sdshdr结构来保存sds(也就是char *)、long long double和long double类型值

3、get命令的实现

t_string.c中

int getGenericCommand(redisClient *c) {
    robj *o;

    // 尝试从数据库中取出键 c->argv[1] 对应的值对象
    // 如果键不存在时,向客户端发送回复信息,并返回 NULL
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return REDIS_OK;

    // 值对象存在,检查它的类型
    if (o->type != REDIS_STRING) {
        // 类型错误
        addReply(c,shared.wrongtypeerr);
        return REDIS_ERR;
    } else {
        // 类型正确,向客户端返回对象的值
        addReplyBulk(c,o);
        return REDIS_OK;
    }
}

 这里说明一点,redis的key/value都是由“字典”数据结构实现,在这里不做深究。

/* Add a Redis Object as a bulk reply 
 *
 * 返回一个 Redis 对象作为回复
 */
void addReplyBulk(redisClient *c, robj *obj) {
    //回复字符的长度
    addReplyBulkLen(c,obj);
    //回复要返回的字符
    addReply(c,obj);
    //回复"\r\n"
    addReply(c,shared.crlf);
}

 这样就出现了开始的“"$3\r\n123\r\n"”。

猜你喜欢

转载自lobert.iteye.com/blog/2148640
今日推荐