概念
从之前的水文了解到Redis支持了好几种数据结构是吧,本次就从简单的string开始。
虽然我们知道Redis是用C语言开发的,但是Redis内部使用的字符串却不是C语言的字符串。Redis的字符串是自己定制的一种简单动态字符串,简称SDS(simple dynamic string,SDS),并且保留了部分C语言下的字符串特性,比如以\0
结尾,那是不是Redis就没有用到C语言的字符串呢?其实在比如Redis打印日志是用的是C语言原生的字符串的,除此之外都是使用的SDS,比如:
static int db_setfenv (lua_State *L) {
luaL_checktype(L, 2, LUA_TTABLE);
lua_settop(L, 2);
if (lua_setfenv(L, 1) == 0)
luaL_error(L, LUA_QL("setfenv")
" cannot change environment of given object");
return 1;
}
结构定义
上面噼里啪啦胡乱讲了一通概括了一句话,Redis使用的是带有C原生字符串的某些特性的自定义数据结构,叫做简单动态字符串SDS。
那么既然C语言有了字符串了,为啥Redis还要造个寂寞火箭呢?来,接着看
如果在C语言下我要计算一个字符串长度是怎么样的呢?来段最简单的C代码
int main() {
int len = strlen("自由书,一个程序爱好者的天堂");
int len2 = strlen("自由书,一个程序爱好者\0的天堂");
printf("长度: %d\n", len);
printf("长度2: %d\n", len2);
return 0;
}
打印输出的结果是:
长度: 42
长度2: 33
有没有觉得奇怪呢?两个字符串只是一个在中间插入\0
长度差这么大呢?其实C语言的字符串长度计算是从字符串第一个字符开始计数,直到遇到\0
后结束。想想10W次/秒读写的Redis如果是这样计算长度会有这么高的效率吗?其次更重要的是安全性,我给各位官老爷安排个演示

看到了吗,我们在操作Redis时可能会意外的插入\0
,然后计算字符串长度时Redis的字符串并不会像原生一样以\0
结尾。保证了安全性,到这里裤子都脱到磕膝盖了,小伙伴是不是想看看Redis的SDS是什么样的结构了,先看下老版本的SDS
# 在src/sds.h里有定义
/*
* Redis字符串数据结构
*/
struct sdshdr {
// 字符串长度
int len;
// 还未分配的长度
int free;
// 字符串数组
char buf[];
};
可以看到在Redis的SDS中是定义了字符串的长度的,所以当客户端获取值字符串的长度时是直接获取返回的,而不是每次计算的。
我们针对字符串的新增和修改来画图演示:
新增字符串
比如我们执行新创建一个key,然后返回长度
> set zys 'redis'
> strlen zys
"5"

此时redis会申请分配一个长度为5+1的数组,为了保留C字符串的特性以\0
结尾,但是设置给sdshdr的len=5,free=0,buf就等于字符串数组。
修改字符串长度
上面的图是redis设置一个字符串的过程,下面我们来展示下修改追加字符串,把redis
修改为hellow redis
, 执行以下命令
> set zys 'hellow redis'
> strlen zys
"12"

上图就是把redis
修改为hellow redis
后的变化,可以看到len=12,free=12。在Redis内部会扩容,在这里具体是分配字符串长度的两倍。
空间管理策略
我们在上面演示了Redis新增和修改字符串内部的变化,但是其实还没演示完,因为还有字符串变少的情况和为什么要留free个空位呢?
空间预分配
空间预分配是Redis在操作字符串扩长时,会分配字符串所需的空间,并且还会分配一定的空余空间,用于后续使用。具体分配多少Redis是有计算公式的
- 如果新设置的值的长度小于1M,那么Redis会分配和字符串同等长度的空余空间,此时SDS的len和free是相等的。就拿上面的例子来讲,修改过后字符串为
hellow redis
长度为12,那么len=12,然后会分配同等长度的空余空间,所以free=12,那么此时在buf的长度是12+12+1字节,别忘记了还有个\0
- 如果新设置的值的长度大于1M,那么Redis就分配1M的空余空间,比如修改过后的长度是2M,那么此时len=2M,free=1M,那么buf里面的长度其实是1M+2M+1byte。
那么问题来了,就按上面的图来操作字符串,现在字符串是hellow redis
此时len=12,free=12,那我把字符串修改为hellow ziyoushu
, len和free该等于多少呢?没错,len=15 ,free=9。这样来看redis频繁的操作字符串其实内部并不是频繁的创建和扩容的,而是预分配的策略来保证空间可用的
空间惰性回收
惰性回收其实是比如操作字符串缩减时,Redis并不会马上回收多余的空间,而是把剩余的空间记录给free,为的后续可能存在扩充字符串时使用。