redis对象编码源码阅读——字符串编码过程
对于RAW
类型的字符串,需要进一步进行编码,判断它是不是可以表示为整型数;更进一步地,看它能否成为共享变量。当然这都是为了节约内存。
/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
size_t len;
1. 判断是不是能编码
/* 确保这是一个字符串对象,
* 这是我们在这个函数中编码的唯一类型。
* 其他类型使用编码的高效内存表示,但由实现该类型的命令处理。 */
// 判断是不是字符串对象
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
// 判断它的编码类型
if (!sdsEncodedObject(o)) return o;
// 判断它是不是共享对象,引用数是不是大于 1
if (o->refcount > 1) return o;
2. 判断是不是能编码为INT
型
/* 检查是否可以把这个字符串表示成一个长整数。
* 请注意,大于21字符的字符串是不可表示为32位或64位整数的。 */
len = sdslen(s);
// string2l: string 对象转 long 对象的函数
if (len <= 21 && string2l(s,len,&value)) {
/* 此对象可编码为long对象。尝试使用共享对象。
* 注意,当使用 maxmemory 时,我们避免使用共享整数,
* 因为每个对象都需要有一个私有的LRU字段,以便LRU算法正常工作。 */
if ((server.maxmemory == 0 ||
(server.maxmemory_policy != REDIS_MAXMEMORY_VOLATILE_LRU &&
server.maxmemory_policy != REDIS_MAXMEMORY_ALLKEYS_LRU)) &&
value >= 0 &&
value < REDIS_SHARED_INTEGERS)
{
// 释放原来的 string 对象
decrRefCount(o);
// 添加新的全局 long 类型引用
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
// 把对象类型从 string 转为 int
if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
o->encoding = REDIS_ENCODING_INT;
o->ptr = (void*) value;
return o;
}
}
3. 判断是不是能编码为EMBSTR
/* 如果字符串很小,仍然是原始编码,
* 那么尝试EMBSTR编码,它更有效。
* 在这种表示中,对象和SDS字符串在相同的内存块中分配,
* 以节省空间和缓存丢失。 */
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) {
robj *emb;
if (o->encoding == REDIS_ENCODING_EMBSTR) return o;
emb = createEmbeddedStringObject(s,sdslen(s));
decrRefCount(o);
return emb;
}
4. 没法编码,判断是否需要释放多余空间
/* 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
* REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
// sdsavail: 返回 SDS 的未使用空间字节数
if (o->encoding == REDIS_ENCODING_RAW &&
sdsavail(s) > len/10)
{
o->ptr = sdsRemoveFreeSpace(o->ptr);
}
/* Return the original object. */
return o;
}
/* Reallocate the sds string so that it has no free space at the end. The
* contained string remains not altered, but next concatenation operations
* will require a reallocation.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) {
struct sdshdr *sh;
// s 指的是 sdshdr 末尾的位置,即 sdshdr 的字符串的首地址
// 所以这里 (s-(sizeof(struct sdshdr))) 正好是指向 sdshdr 的首地址
sh = (void*) (s-(sizeof(struct sdshdr)));
// 释放掉多余的空间
sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
sh->free = 0;
return sh->buf;
}
zrealloc
函数会保存原来的字符串,然后释放老的空间,然后重新申请一个小一点的正好容下当前字符串的空间