Redis是一个开源的key-value存储系统,现代软件的很多场景都需要使用这种类似的内存数据库,因此对Redis的深入了解是非常有必要的。
本篇文章主要是通过阅读黄健宏老师的《Redis设计与实现》来记录自己的学习进度和加以总结。
这是一份在github中开源的带有注释的Redis中简单动态字符串头文件源码,有兴趣阅读的朋友可以点开看。
Redis中,C字符串只会作为字符串字面量用在一些无需对字符串值进行修改的地方。
当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用SDS(simple dynamic string, SDS)来表示字符串值。
从单纯的C风格字符串,SDS用结构来对字符串进行描述,进行了一系列的优化。
/*
* 保存字符串对象的结构
*/
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
优化点:
- 获取字符串长度的时间复杂度由O(N)变为O(1)。
- 杜绝缓冲区溢出。
- 减少修改字符串长度时所需的内存重新分配次数。
- 二进制安全。
- 兼容部分C字符串函数。
杜绝缓冲区溢出的关键在于对字符串进行操作的时候,会去检查它的当前长度在进行操作后,空间是否会发生溢出,如果可能溢出,则会动态扩容。
因为动态扩容是一个比较耗时的操作,中间会经历复杂的内存分配算法,和系统调用,陷入内核等等。故而采取一个合理的策略,能够使得减少动态扩容的次数很重要。
Redis采用空间预分配的策略来对这一行为进行优化,当SDS的API对一个SDS进行修改,并需要扩容,程序会为SDS分配出额外的空间。
- 如果SDS进行修改后,SDS的长度(len)小于1MB,那么分配free和len相同的大小,比如len分配了个字节,则free也会拿到5个字节,SDS的buf数组总长度为5+5+1=11个字节。
- 如果对SDS进行修改后,SDS的长度将大于1MB,则程序会分配1MB的未使用空间。比如修改后SDS的len长度为10MB,则SDS的buf数组总长度为10MB+1MB+1byte.
在扩展SDS空间之前,SDS API会先检测未使用的空间是否足够,如果足够,则API会直接使用未使用的空间,而无需重写分配,这就使得SDS将连续增长N此字符串所需的内存分配从必定N次,降低至最多N次。
Redis的动态字符串采用了惰性空间释放的策略,对于一个较长的字符串,如果要截取其中某一部分,舍弃另一部分,那么只会在SDS中将舍弃部分加到free字段里,而不会真正的释放这部分空间,如果真的需要进行空间释放,那么Redis也提供了独立的接口。
二进制安全:C字符串中必须 符合某种编码,如ASCII,并且需要以空字符结尾。这些限制使得C字符串无法保存图片、音频、视频、压缩文件这样的二进制数据。但SDS的API都是二进制安全的,所有的SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中数据做任何限制、过滤、或者假设,数据在写入时是什么样子,那么它被读取时也是什么样子。
C字符串和SDS之间的区别: