美文网首页Java
Redis-String内部结构

Redis-String内部结构

作者: lv_shun | 来源:发表于2020-03-21 18:00 被阅读0次

SDS-动态字符串

Redis中的字符串叫SDS (Simple Dynamic String),结构是一个带长度信息的数组。
数据结构:

struct SDS<T> {
  T capacity; // 数组容量
  T len; // 数组长度
  byte flags; // 特殊标识位
  byte[] content; // 数组内容
}

capacity是数组容量,为了扩展用的,len是实际使用的长度。如果append时,数组容量等于使用的长度就需要扩容,然后复制原有内容到新内存在追加新内容,如果字符串的长度非常长,这样的内存分配和复制开销会非常大。
append命令:

int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
    sds newbuf;

    newbuf = sdscatlen(c->obuf,cmd,len);
    if (newbuf == NULL) {
        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
        return REDIS_ERR;
    }

    c->obuf = newbuf;
    return REDIS_OK;
}

sdscatlen方法的作用是扩容,源码如下:

/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
 * end of the specified sds string 's'.
 *
 * 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 sdscatlen(sds s, const void *t, size_t len) {
    // 原字符串长度
    size_t curlen = sdslen(s);
   // 按需调整空间,如果 capacity 不够容纳追加的内容,就会重新分配字节数组并复制原字 符串的内容到新数组中
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;// 内存不足
    memcpy(s+curlen, t, len);// 追加目标字符串的内容到字节数组中
    sdssetlen(s, curlen+len);// 设置追加后的长度值
    s[curlen+len] = '\0';// 让字符串以\0 结尾,便于调试打印
    return s;
}

sds结构是根据存储内容类型不同而使用不同的存储,极致的优化内存。
Redis规定字符串的长度超过512M字节。创建字符串时len和capacity一样长,不会多分配空间,因为绝大多数场景我们不会使用append方法的。

int、embstr、raw

这三种属于redis中string结构的编码格式,由redis根据存储的值内容来确定用哪种编码方式来实现。且不可逆,也就是说只能由占用内存较小的转到内存较大的编码方式。

编码 使用场景
int 整数类型
embstr 较小的值使用
raw 较大的值使用

这里因为版本不同,判断依据不一致,redis 3.2版本之前39字节以上使用raw,3.2版本之后44字节以上使用raw。
(但是测试发现4.0版本时在29就开始使用raw了)。
这里提供计算思路:

首先了解Redis对象头结构体,所有redis对象都有下面的结构头:

struct RedisObject {
int4 type; // 4bits
int4 encoding; // 4bits
int24 lru; // 24bits
int32 refcount; // 4bytes
void *ptr; // 8bytes,64-bit system
} robj;

这样一个结构头需要占用16字节。
然后是sds结构体的大小,这里sds最小是3个字节,结构如下:

struct SDS {
int8 capacity; // 1byte
int8 len; // 1byte
int8 flags; // 1byte
byte[] content; // 内联数组,长度为 capacity
}
两种编码方式内存结构.png

这里embstr中redisObject和sds是连续内存,内存分配是按照2、4、8、16、32、64这样分配的。redis认为分配到64之上就算较大的string了,需要使用raw编码格式来完成。那么使用embstr内存上线就是分配64字节。由于上面提到的redisObject和sds最少各占据16和3,也就是19。那么能存储数据的空间=64-19=45.这里redis规定字符串结尾为\0,占据1个字节,45-1=44.所以字符串大于44个字节就需要raw来进行编码。

embstr vs raw

  1. embstr存储有限长度的字符串
  2. 使用embstr编码格式,一次分配内存,回收也是一次,
    使用raw编码格式,需要多次分配内存,回收也是多次。
  3. embstar编码格式存储数据是在连续内存空间,更好的使用内存空间。

扩容策略

字符串长度小于1M时,扩容采取加倍策略,也就是保留100%的冗余空间。当字符串长度超过1M时,为了避免内存浪费,每次只会多分配1M大小的冗余空间。

相关文章

网友评论

    本文标题:Redis-String内部结构

    本文链接:https://www.haomeiwen.com/subject/pvxuyhtx.html