Redis的字符串剖析

Redis的字符串剖析

前言

Redis是使用了C语言实现的,但是由于C语言的字符串性能问题,所有Redis并没有直接使用C语言的字符串存储数据,而是定义了自己的字符串类型(SDS)。

SDS的定义


SDS遵循了C字符串的以空字符表示结尾的惯例,但是空字符的一个字节不计算在SDSlen属性内部,而是由SDS分配额外的一个字节空间存储。遵循C以空字符表示结尾的好处就是,SDS可以直接重用一部分C字符串函数库中的函数。

SDSC字符串的区别

既然Redis定义了自己的字符串类型,那么和C语言的字符串区别位于哪里呢?

字符串长度计算

由于C字符串只记录字符串信息,那么如果获取字符串的长度,程序必须遍历整个字符串去统计,直到遇到空字符串为止。这个操作复杂度为O(N),复杂度随字符串长度而增长。举个例子:


SDS由于本身在len属性中记录了SDS本身的长度,只需要访问SDSlen属性就可以知道长度。操作复杂度为O(1)。举个例子:


通过SDSRedis将获取字符串长度所需的复杂度从O(N)下降为O(1),确保了获取字符串的操作不会成为Redis的性能瓶颈。

杜绝缓冲区溢出

C字符串不记录长度的一个问题是容易造成缓冲区溢出。举个例子:假设程序里有两个在内存中相邻的C字符串S1=RedisS2=MongoDB。如下图所示:


这情况下如果用户执行S1后面拼接字符串S3=Cluster,那么S1的数据将溢出到S2的位置,导致S2保存的内容被意外修改。如下图所示:


SDS的空间分配策略可以完全杜绝发生缓冲区溢出的可能性,因为SDSfree记录了可用的字节数量,如果不够会先扩充。例如先存储了Redis字符串,如下图所示:


程序执行字符串拼接Cluster的时候,会先判断SDS的长度是否足够,发现不够先执行扩展,然后执行拼接。扩展和拼接后freelen属性都会改变,如下图所示:


减少修改字符串时带来的内存重新分配次数

由于C字符串并不记录自身的长度原因,所有对于C字符串来说,每次增长或者缩短一个C字符串,都需要对C字符串的数组进行一次内存的重新分配。1)如果是增长操作,那么操作之前如果没有重新分配内存来扩展底层数组的空间大小,将导致缓冲区溢出。2)如果是截断操作,那么执行后需要重新分配内存来释放不再使用的空间。

SDS通过空间预分配和惰性空间释放来解决C字符串的这些缺陷。

空间预分配

空间预分配用于优化SDS的字符串增长操作:当对SDS进行修改的时候,并需要对SDS的空间进行扩展的时候,程序除了分配所需要的空间外,还会分配额外的未使用空间。

分配额外的公式如下:

1)如果SDS进行修改后,SDS的长度小于1MB,那么程序分配和len属性同样大小的未使用空间。这时候SDSlen属性和free属性值相同。例如:修改后SDSlen属性为13字节,那么程序重新分配13个未使用的字节,这时候SDSbuf数组实际长度将变为13+13+1(空字符表示字符串结束)=27字节。

2)如果SDS修改后,SDS的长度大于等于1MB,那么程序只会分配1MB未使用的空间。例如:修改后SDSlen20MB,再分配未使用空间SDS的实际长度为20MB+1MB+1byte

惰性空间释放

空间惰性释放用于优化SDS的字符串缩短操作。当SDS缩短了字符串后,程序并不会立即使用内存重分配来回收多余的字节,而是使用free属性记录,等将来使用。

例如:SDSXYXaYYbcXYY需要去除XY字符。去除之前free=0len=11


去除后不立即回收释放的空间,通过free记录,等需要使用的时候可以直接使用,而不用扩展。


例如去除XY字符后,需要拼接Redis字符串,通过惰性空间释放策略,为可能有的增长操作提供了优化。


展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读