内容简介:编码方式为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: 类型转换和类型断言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
jQuery 技术内幕
高云 / 机械工业出版社 / 2014-1-1 / 99元
本书首先通过“总体架构”梳理了各个模块的分类、功能和依赖关系,让大家对jQuery的工作原理有大致的印象;进而通过“构造 jQuery 对象”章节分析了构造函数 jQuery() 的各种用法和内部构造过程;接着详细分析了底层支持模块的源码实现,包括:选择器 Sizzle、异步队列 Deferred、数据缓存 Data、队列 Queue、浏览器功能测试 Support;最后详细分析了功能模块的源码实......一起来看看 《jQuery 技术内幕》 这本书的介绍吧!