Redis-String类型

栏目: 数据库 · 发布时间: 5年前

内容简介:编码方式为raw时,robj中的ptr指向一个sds结构中字符串的起始位置。sds结构分为两个部分,头部由len,alloc,flags组成,尾部是buf,存储字符串数据的地方。比如存储一个“value”,会是下面的结构:

编码方式为raw时,robj中的ptr指向一个sds结构中字符串的起始位置。

sds结构分为两个部分,头部由len,alloc,flags组成,尾部是buf,存储字符串数据的地方。

比如存储一个“value”,会是下面的结构:

Redis-String类型

当存储的字节数小于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(写博客的时候是44)时,可能会被转换成embstr编码。

embstr

编码为embstr的时候,字符串同样是用sds结构存储的,不过此时是紧接着robj对象之后的。

示意图如下:

Redis-String类型

int

int类型的编码就更简单了,存储整数可以说是最省空间的了,直接就把整数的值放在了robj的ptr属性里面。

Redis-String类型

当把String类型的值当成数字来操作,并且值的编码不是int的时候,redis会把String编码自动转换成int(如果没有错误的话)。

代码分析

通过下面的代码分析,可以了解 redisset key value 以及 incr 命令的执行流程,以及各种编码在redis中的自动转换。

set命令

下载redis源码,编译,gdb,然后 b setCommand 在setCommand处打一个断点。

启动客户端,添加一个新的键值对 set key value ,服务器端停在了断点处,backtrace:

(gdb) backtrace
#0  setCommand (c=0x7ffff691ed00) at t_string.c:96
#1  0x000000000042bf86 in call (c=c@entry=0x7ffff691ed00, flags=flags@entry=15) at server.c:2229
#2  0x000000000042c76f in processCommand (c=0x7ffff691ed00) at server.c:2515
#3  0x000000000043c4f5 in processInputBuffer (c=0x7ffff691ed00) at networking.c:1357
#4  0x0000000000425f0d in aeProcessEvents (eventLoop=eventLoop@entry=0x7ffff683c0a0, flags=flags@entry=11)
    at ae.c:443
#5  0x000000000042613b in aeMain (eventLoop=0x7ffff683c0a0) at ae.c:501
#6  0x0000000000422c56 in main (argc=<optimised out>, argv=0x7fffffffd0f8) at server.c:3899
(gdb)

如果继续往下执行会依次执行 setGenericCommand -> setKey-> dbAdd -> dictAdd ,在查看了代码之后发现 setGenericCommand, setKey, dbAdd 的函数参数中都有 robj* val ,猜测在这之前作为值的robj对象就已经构建好了,经过自己向后追踪,确实是这样。

setCommand 调用setGenericCommand的时候是如下调用的:

setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);

而setGenericCommand的原型:

void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply);

值对象是在setCommand之前被构建并且存入到c->argv[2]中的,c->argv[1]中的是键,而c->argv[0]中的是命令。

经过查找,发现了构建位置, processInputBuffer->processInlineBuffer->createObject

关键点一:解析接收到的参数

在processInlineBuffer中,服务器接收到的参数会被解析成字符串数组。

来看 processInlineBuffer

processInputBuffer: int processInlineBuffer(client *c) {
    char *newline;
    int argc, j, linefeed_chars = 1;
    sds *argv, aux;
    size_t querylen;

    // 省略一些代码,只看如何生成值对象的

    argv = sdssplitargs(aux,&argc);	
	// aux是所有参数组成的字符串,类型为sds
	// split之后得到了argv字符串数组
    sdsfree(aux);
    if (argv == NULL) {
        addReplyError(c,"Protocol error: unbalanced quotes in request");
        setProtocolError("unbalanced quotes in inline request",c,0);
        return C_ERR;
    }

	// 省略代码。。。。

    /* Setup argv array on client structure */
	// 上面split之后获得了参数的个数argc
    if (argc) {
        if (c->argv) zfree(c->argv);
        c->argv = zmalloc(sizeof(robj*)*argc);
    }

    /* Create redis objects for all arguments. */
	// 从参数字符数组argv中构建robj数组,存储到client->argv中
    for (c->argc = 0, j = 0; j < argc; j++) {
        if (sdslen(argv[j])) {
            c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
            c->argc++;
        } else {
            sdsfree(argv[j]);
        }
    }
    zfree(argv);
    return C_OK;
}

在上面的processInlineBuffer中,把接收到的参数使用sdssplitargs分离开来,生成一个字符串数组。

关键点二:参数转为robj

在processInlineBuffer的稍后部分,对于字符串数组中的每一个元素,都生成了一个robj对象,这些对象都是String型,且编码为raw。

然后对每个sds调用createObject生成robj对象,存储到client->argv数组中。

createObject代码:

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution), or
     * alternatively the LFU counter. */
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    return o;
}

关键点三:raw转embstr/int

在之前提到的 setCommand 函数中,调用 setGenericCommand 之前,有如下代码:

void setCommand(client* c) {
    // pass
    c->argv[2] = tryObjectEncoding(c->argv[2]);
	setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}

通过上面的分析,我们知道c->argv[2]就是输入命令 set key value 中的“value”, tryObjectEncoding 把这个原本是raw编码的String类型对象转换成了embstr编码。

转换代码如下:

/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;

	// 确保为String类型
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);

    // 一个宏
    //./server.h:1480:
    //#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
    if (!sdsEncodedObject(o)) return o;

    /* It's not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis and may end in places where
     * they are not handled. We handle them only as values in the keyspace. */
     if (o->refcount > 1) return o;

    /* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 20 chars is not
     * representable as a 32 nor 64 bit integer. */
    // 如果可以转换成int,则转换成int编码,然后返回
    len = sdslen(s);
    if (len <= 20 && 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 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
            o->encoding = OBJ_ENCODING_INT;
            o->ptr = (void*) value;
            return o;
        }
    }

    /* If the string is small and is still RAW encoded,
     * try the EMBSTR encoding which is more efficient.
     * In this representation the object and the SDS string are allocated
     * in the same chunk of memory to save space and cache misses. */
    // 尝试转换成embstr
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
        emb = createEmbeddedStringObject(s,sdslen(s));
        decrRefCount(o);
        return emb;
    }

    /* We can't encode the object...
     *
     * Do the last try, and at least optimize the SDS string inside
     * the string object to require little space, in case there
     * is more than 10% of free space at the end of the SDS string.
     *
     * We do that only for relatively large strings as this branch
     * is only entered if the length of the string is greater than
     * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
    if (o->encoding == OBJ_ENCODING_RAW &&
        sdsavail(s) > len/10)
    {
        o->ptr = sdsRemoveFreeSpace(o->ptr);
    }

    /* Return the original object. */
    return o;
}

raw转int的过程都在上面的代码中了,而转embstr需要的函数createEmbeddedStringObject如下:

/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
 * an object where the sds string is actually an unmodifiable string
 * allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
    // 从分配的空间即可看出,robj和字符串数据是在一起的
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
    struct sdshdr8 *sh = (void*)(o+1);

    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;	// ptr指向字符串数据第一个字符
    o->refcount = 1;
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }

    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

incr命令

incr的调用流程如下:

(gdb) backtrace
#0  incrDecrCommand (c=0x7ffff691ed00, incr=1) at t_string.c:345
#1  0x000000000042bf86 in call (c=c@entry=0x7ffff691ed00, flags=flags@entry=15) at server.c:2229
#2  0x000000000042c76f in processCommand (c=0x7ffff691ed00) at server.c:2515
#3  0x000000000043c4f5 in processInputBuffer (c=0x7ffff691ed00) at networking.c:1357
#4  0x0000000000425f0d in aeProcessEvents (eventLoop=eventLoop@entry=0x7ffff683c0a0, flags=flags@entry=11)
    at ae.c:443
#5  0x000000000042613b in aeMain (eventLoop=0x7ffff683c0a0) at ae.c:501
#6  0x0000000000422c56 in main (argc=<optimised out>, argv=0x7fffffffd0f8) at server.c:3899
(gdb)

其实incr和decr命令是分别调用对应的incrCommand和decrCommand函数,不过背后还是incrDecrCommand这个函数。

关键点一:incr无法对超过long long的整数进行自增操作

从下面incrDecrCommand代码可以看出,自增是把raw型或这embstr型编码的字符串转换成long long,然后增加,如果超过这个范围,无法转换,自然无法自增。

void incrDecrCommand(client *c, long long incr) {
    long long value, oldvalue;
    robj *o, *new;

    // 获取键对应的值
    o = lookupKeyWrite(c->db,c->argv[1]);
    
    // 不是String类型,类型不匹配
    if (o != NULL && checkType(c,o,OBJ_STRING)) return;
    
    // 尝试转换,若无法转换,如不是整形,或者溢出,返回错误。
    if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;

    // 递增
    oldvalue = value;
    if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
        (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
        addReplyError(c,"increment or decrement would overflow");
        return;
    }
    value += incr;

    // 下面是写回
    if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
        (value < 0 || value >= OBJ_SHARED_INTEGERS) &&
        value >= LONG_MIN && value <= LONG_MAX)
    {
        new = o;
        o->ptr = (void*)((long)value);
    } else {
        // ********注意这里***********
        // 这个函数会基于给定的long long值构造一个String类型的对象
        new = createStringObjectFromLongLong(value);
        if (o) {
            dbOverwrite(c->db,c->argv[1],new);
        } else {
            dbAdd(c->db,c->argv[1],new);
        }
    }
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
    server.dirty++;
    addReply(c,shared.colon);
    addReply(c,new);
    addReply(c,shared.crlf);
}

我们还可以看下createStringObjectFromLongLong这个函数,如果超出范围,将返回embstr编码的对象,没超则返回int编码对象:

// object.c
robj *createStringObjectFromLongLong(long long value) {
    robj *o;
    if (value >= 0 && value < OBJ_SHARED_INTEGERS) {
        incrRefCount(shared.integers[value]);
        o = shared.integers[value];
    } else {
        if (value >= LONG_MIN && value <= LONG_MAX) {
            o = createObject(OBJ_STRING, NULL);
            o->encoding = OBJ_ENCODING_INT;	// 设置编码
            o->ptr = (void*)((long)value);	// 设置value
        } else {
            o = createObject(OBJ_STRING,sdsfromlonglong(value));
        }
    }
    return o;
}

总结

String有三种编码方式,从client接收过来的字符串(本文只分析了简单的inline)被解析成了raw编码的robj对象,然后redis尝试转换成更加紧凑的embstr或者更节省内存的int编码。

无法对超过long long的值进行自增操作。


以上所述就是小编给大家介绍的《Redis-String类型》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Coding the Matrix

Coding the Matrix

Philip N. Klein / Newtonian Press / 2013-7-26 / $35.00

An engaging introduction to vectors and matrices and the algorithms that operate on them, intended for the student who knows how to program. Mathematical concepts and computational problems are motiva......一起来看看 《Coding the Matrix》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码