目的
Redis现在是各个系统几乎都在使用的一种分布式高可用的缓存内存中的数据结构存储系统。可以作为数据库、缓存消息中间件、订阅发布系统等。我们都知道redis中有string、sets、sorted sets、hash、list类型。但是这些我们经常使用的数据结构的底层是怎么实现的。今天先记录一下string的结构。主要是参照Redis设计与实现和一些网上的资料总结的一个学习笔记。
C语言字符串和SDS在Redis中的使用
redis是用C语言实现的,所以在redis中的string有一些是直接使用C语言的字符串。但是在redis中还使用了叫做简单动态字符串(simple dynamic string)。redis中的string主要就是使用这两种string。但是什么时候用哪种呢?具体我们来看一下。
C语言string:主要使用在一些无序对字符串进行修改的地方,比如说作为key、打印日志
SDS:SDS是redis中默认字符串表示。几乎用于所有需要使用字符串操作的地方,包括AOF缓冲区等模块。
我们举一个例子:
redis> set aaa “bbb”
这个时候aaa使用的是C语言字符串,bbb则是使用SDS
RPUSH list “aaa” “bbb” “ccc”
list这个key使用的是C语言字符串,aaa bbb ccc则是使用了3个SDS来保存
SDS定义
SDS的定义其实很简单,一共有3个属性。len、free、buf
struct sdshdr{
//记录buf数组中已经使用的数量
//等于整个字符串目前已经使用的长度(不包括结束字符"\0")
int len;
//记录buf数组中未使用的字节数量
int free;
//字节数组,用于保存字符串的地方
char buf[]
}
我们可以看到相比C语言中的字符串,SDS多了两个属性len和free。但就是这两个简单的属性再加上一些扩容策略就可以是的性能有一个很大的提升。接下来我们可以看一下SDS的优势点在哪里。
SDS优势点
- 快速获取字符串的长度
在SDS的结构中有一个len的属性,如果要获取字符串长度则直接返属性即可。如果C语言字符串,则需要循环字节数组遇到了”0″计算出整个字符串的长度。很明显可以看到SDS使用的复杂度是O(1)。所以说在redis中获取字符串的长度对性能几乎是没有影响的。 - 杜绝缓冲区溢出
在C语言字符串中,当你添加字符到一个字符串是s1长度时如果忘记在执行前为s1分配足够多的空间,那么s1的数据将溢出到之后的字符串s2中,导致s2被意外的修改。
然而SDS不同,每次对SDS字符串修改之前都会去判断字符串的容量是否足够。如果不够则会扩充SDS的内存大小。所以完全避免了这个错误。 - 减少字符串修改带来的内存分配次数
在C语言字符串中,每次对字符串的修改都会影响到内存的分配。如果增长字符串,则会为字符串重新分配一个新的内存空间。如果减少字符串,则会对减少的内存空间做内存回收,否则会引起内存泄漏。
那么SDS是如何减少内存分配的呢? 是通过两点
- 空间的预分配
当SDS的API对一个SDS进行修改,并且需要扩展空间的时候,不仅会对SDS分配所需要的空间,还会为SDS分配额外的未使用空间。这个未使用空间的长度则记录在free属性中。这个预分配空间的策略根据SDS的长度决定。
1 当SDS小于1MB的时候。每次扩容长度则跟len相同。举一个例子如果一个SDS扩容为12个字节,那么 SDS函数将会再添加一个12字节的预分配长度。既 len=12 free=12 buf长度则为25字节 (多出来的1个是结束符”0″)
2 当SDS大于1MB的时候,每次预分配长度则为1MB。相当于如果这个SDS是10MB,那么每次扩容之后的free的长度回是1MB - 惰性空间释放
当一个SDS字符串缩短操作时,程序并不会马上重新收回多余出来的内容,而是用free字段将这些空余的空间记录下来。当下次如果需要往SDS添加字符,则可以再次使用这些空余空间。当然也不是意味着永远不会被回收,SDS有API来释放这些内存空间。
- 二进制安全
开始没有理解什么事二进制安全,网上找了一些资料。简单总结:**二进制安全的意思就是,只关心二进制化的字符串,不关心具体格式,只会严格的按照二进制的数据存取,不会妄图以某种特殊格式解析数据。
C语言字符串的字符必须符合某种编码(比如ASCII)并且不能包含空字符,否则空字符之后的字符将会被忽略。二SDS则都是使用二进制处理,不仅可以存放字符串还可以放字节流,比如图片,视频等。因为redis不使用空字符串来判断长度而是用len属性。
总结
因为redis作为一个数据库存储来使,而且redis是一个单线程,一旦一个操作阻塞了之后之后的所有操作都会被影响。所以对性能的要求特别高,所以redis自己构建了这种字符串。 其实SDS结构十分简单,简单的使用了len,free两个字段配合一些策略,就大幅度的提高了性能和减少了内存重新分配的次数。值得我们学习,可以应用在其他程序设计中。