Redis String 原理与设计
- String 实现原理
- 三种编码
- 编码的转换
- SDS 定义
String 实现原理
推荐书籍: Redis 设计与实现
推荐博客:
字符串是Redis最基本的数据类型,不仅所有key都是字符串类型,其它几种数据类型构成的元素也是字符串。注意字符串的长度不能超过512M。
为什么字符串长度不能超过 512M?
// 源码定义(检查字符串长度)
static int checkStringLength(redisClient *c, long long size) {
if (size > 512*1024*1024) {
addReplyError(c,"string exceeds maximum allowed size (512MB)");
return REDIS_ERR;
}
return REDIS_OK;
}
三种编码
- int 编码:保存的是可以用 long 类型表示的整数值。
- raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
- embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
127.0.0.1:6379> set str1 121
OK
127.0.0.1:6379> set str2 qweqweqwe
OK
127.0.0.1:6379> set str3 qweqweqweqweqweqweqweqdhjajdskasjhdiqweuyaasddsa
OK
127.0.0.1:6379> object encoding str1
"int"
127.0.0.1:6379> object encoding str2
"embstr"
127.0.0.1:6379> object encoding str3
"raw"
区别:
embstr 使用只分配一次内存空间(因此redisObject和sds是连续的),raw 需要分配两次内存空间(分别为redisObject和sds分配空间)
因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。
编码的转换
当 int 编码保存的值不再是整数,或大小超过了long的范围时,自动转化为raw。
对于 embstr 编码,由于 Redis 没有对其编写任何的修改程序(embstr 是只读的),在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了44个字节。
SDS 定义
struct sdshdr{
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
为什么要有 len ?
- 复杂度:在 C 中获取一个字符串的长度复杂度为 O(N),定义 len 后复杂度为 O(1);
- 二进制安全:用 len 判断字符串结束,可以保存除文本数据以外的图片、音频等数据
在 C 中使用 ‘\0’ 来标志一个字符串的结束,不能保存空字符否则会被认为是字符串的结尾。如果有一个以空字符来分割单词的特殊特殊数据,则 C 只能读取第一个单词。
而 Redis 用 buf[] 存储二进制数据,以 len 来判断结束,不会出现以上问题
为什么要有 free ?
- 空间预分配
在 C 中分配空间是一个比较耗时的工作,但 Redis 作为缓存数据库,数据的频繁修改是常态,而每次增长字符串都需要执行一次内存重分配(C 语言字符串处理特性),对性能产生很大影响。
- 修改后 SDS 长度小于 1MB
- 分配和 len 属性同样大小的未使用空间,这时 len 和 free 的值相同,下次修改的值长度如果小于 free 就不用重新分配空间。此时 buf 长度:free + len + 1 Byte
- 修改后 SDS 长度大于 1MB
- 分配 1 MB 未使用空间,此时 buf 实际长度 1Mb + len + 1 Byte
- 惰性空间释放
当字符串缩短时,程序并不立即使用内存重分配来回收多余的空间,而是用 free 记录下来,以便以后使用。
SDS 提供了相应的 API 可以在我们需要的时候真正地释放 SDS 的未使用的空间。所以不必担心惰性空间释放策略会浪费内存。
为什么SDS 中 buf 数组仍然在一串字符串后面添加 ‘\0’ 标志 ?
SDS 中保存的文本数据可以根据这个标志调用 C 语言原有的函数(<string.h>),如 strcasecmp(sds->buf, “hello world”)进行字符串对比,strcat(c_string, sds->buf)进行字符串追加等。