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
}

这里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
- embstr存储有限长度的字符串
- 使用embstr编码格式,一次分配内存,回收也是一次,
使用raw编码格式,需要多次分配内存,回收也是多次。 - embstar编码格式存储数据是在连续内存空间,更好的使用内存空间。
扩容策略
字符串长度小于1M时,扩容采取加倍策略,也就是保留100%的冗余空间。当字符串长度超过1M时,为了避免内存浪费,每次只会多分配1M大小的冗余空间。
网友评论