内容简介:编码方式为raw时,robj中的ptr指向一个sds结构中字符串的起始位置。sds结构分为两个部分,头部由len,alloc,flags组成,尾部是buf,存储字符串数据的地方。比如存储一个“value”,会是下面的结构:
编码方式为raw时,robj中的ptr指向一个sds结构中字符串的起始位置。
sds结构分为两个部分,头部由len,alloc,flags组成,尾部是buf,存储字符串数据的地方。
比如存储一个“value”,会是下面的结构:
当存储的字节数小于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(写博客的时候是44)时,可能会被转换成embstr编码。
embstr
编码为embstr的时候,字符串同样是用sds结构存储的,不过此时是紧接着robj对象之后的。
示意图如下:
int
int类型的编码就更简单了,存储整数可以说是最省空间的了,直接就把整数的值放在了robj的ptr属性里面。
当把String类型的值当成数字来操作,并且值的编码不是int的时候,redis会把String编码自动转换成int(如果没有错误的话)。
代码分析
通过下面的代码分析,可以了解 redis 对 set 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类型》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- golang的值类型,指针类型和引用类型&值传递&指针传递
- Scala 类型的类型(三)
- Scala 类型的类型(二)
- Scala 类型的类型(三)
- Scala 类型的类型(二)
- golang: 类型转换和类型断言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
打造有吸引力的学习型社群
苏平、田士杰、吕守玉 / 机械工业出版社 / 45.00元
本书首先对社群的定位、准备和吸引粉丝方面等做了饶有趣味的介绍,从社群黏度的提升、社群知识的迭代与转化和社群的持续发展等多个角度入手,对学习型社群的运营手段、运营模式、运营规律和运营经验等进行了全方位剖析。从中国培训师沙龙这个公益社群近十年成功运营的经验中,为如何经营好学习型社群总结出了一套系统性的、具有实操价值的方法。并以此为基础,扩展到知识管理、团队管理、内容IP等领域,为有致于社团建设以及优质......一起来看看 《打造有吸引力的学习型社群》 这本书的介绍吧!