《Java后端知识体系》系列之Redis数据结构的底层实现

我们知道Redis中有五种数据结构:String、Map、List、Set、ZSet,那么这些数据结构在Redis中又是如何实现的呢?

String(动态字符串)

  • 介绍SDS:
    • SDS是什么?

      • 在Redis中并没有直接使用C语言的字符串(以空字符结尾的字符数组),而是构建了一种名为简单动态字符串(SDS)的抽象类型,并且SDS用作Redis的默认字符串。
      • 在Redis中一个可以被修改的字符串值都是用SDS来表示这样一个字符串值的,因此在Redis中键值对的底层都是由SDS来实现的。
      • 举例:如果客户端执行命令:SET msg "hello world",那么Redis将在数据库中创建一个新的键值对,其中

      • 键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串"msg"的SDS

      • 键值对的值也是一个字符串对象,对象的底层实现是保存着字符串"hello world"的SDS。

对于SDS不仅用来保存Redis中的字符串值,还被用作缓冲区(buffer):AOF模块中的AOF缓冲区、以及客户端状态中的输入缓冲区。

  • 定义
    • 每个sds.h/sdshdr结构表示一个SDS值:
struct sdshdr{
	//记录buf数组中已使用字节的数量
	//等于SDS所保存字符串的长度
	int len;

	//记录buf数组数组中未使用字节的数量
	int free;
	
	//字节数组,用于保存字符串
	char buf[];
}

例子如下图:
在这里插入图片描述
上图中定义了一个Redis字符串,该字符串存放的情况如上图中。

  • free属性为0,表示这个SDS没有分配任何未使用空间

  • len属性值为5,表示这个SDS保存了一个5字节长的字符串

  • buf属性是一个char类型的数组,该数组中依次存放Redis五个字符,而最后一个字节则保存空字符'/0'

SDS遵循C语言字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性中。并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作都是由SDS函数自动完成。这样的一个好处是可以重用C语言字符串函数库中的函数。

以上的SDS中并没有分配未使用空间
在这里插入图片描述
上图中在Redis字符串后面新增了五个空字节,这个五个字节表示分配的未使用空间。因此free=5

  • 分析属性 len
    • 在SDS中len属性记录SDS的长度,所以获取一个SDS长度的复杂度仅为O(1),这确保获取字符串长度的工作不会成为Redis的性能瓶颈。
      设置和更新SDS长度的工作是由SDS的API在执行时自动完成的,使用SDS无需手动修改长度。
  • 杜绝缓冲区溢出

在平时初始化数组时一般需要给数组初始化一定的长度,那么在SDS中是如何保证buf(char数组)的内存时足够的呢?并且保证buf不会内存溢出的呢?

在SDS中设定了一套空间分配策略,该策略完全杜绝了发生缓冲区溢出的可能性:

当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需要的要求,如果不满足的话,API会自动讲SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,如果满足则直接修改。因此使用SDS不需要手动修改SDS的空间大小,也不会出现缓冲区溢出的问题。

例如:我们使用SDS Api中的拼接字符串函数sdscat拼接msg字符串,那么执行sdscat(msg,'Cluster')
此时SDS在执行sdscat(msg,'Cluster')之前会检查msg的长度是否足够,如果msg目前的空间不足以拼接Cluster,那么sdscat会先扩展msg的空间,然后再去执行sdscat(msg,'Cluster'),拼接完成后如图
在这里插入图片描述
sdscat操作不仅对SDS进行拼接操作,还会为SDS分配13字节的未使用空间(free=13),拼接后的字符串长度是13,这就涉及到了SDS的空间预分配策略

  • 空间预分配策略

空间预分配策略主要用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展时,此时会为SDS分配额外的未使用空间。

空间预分配策略如下:

  • 如果对SDS修改后,SDS的长度(len属性的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间,这时len属性的值和free属性的值相同。也就是如上的msg例子中修改后msg的len=13free=13buf的实际长度为13+13+1=27

  • 如果对SDS修改后,SDS的长度将大于等于1MB,那么程序会分配1MB的未使用空间。例如,如果修改后SDS的len变为20MB,那么程序会分配1MB的未使用空间。SDS的buf数组的实际长度为20MB+1MB+1byte。
    通过空间预分配策略Redis可以减少连续执行字符串增长操作所需的内存重分配次数。

  • 惰性空间释放

惰性空间释放主要用于优化SDS的字符串缩短操作,当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待以后使用。
例如通过SDS API中的sdstrim函数来移除指定的字符,当执行sdstrim(msg,"Cluster")后,SDS并不会释放Cluster的7字节空间,而是将这7个字节保留在SDS中,如果以后对SDS进行增长操作,则这些空间就会排上用场。
在这里插入图片描述

  • 二进制安全

在SDS的buf数组中保存的并不是字符,而是保存的一系列的二进制数据,这样就确保了Redis可以适用各种不同的场景。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值