Redis必学(四)redis底层数据结构

1 redis底层数据结构

1.1 SDS简单动态字符串

    SDS用于存储字符串,但不同于一般的C语言字符串存储方式,SDS不仅存储字符串的内容,同时也存储字符串的部分属性。如下图所示:
在这里插入图片描述
其中:

free表示sds中空余长度。
len表示字符串内容长度。
buf表示字符串内容。

1.1.1 特性

a) 常数复杂度获取字符串长度

    正常获取一个C语言字符串,需要遍历整个字符串才能确定长度,而通过SDS,只需要访问len属性,就可以立即获取SDS字节。

b) 扩容–杜绝缓冲区溢出
    如果是C语言本身的字符串,在用户增加这个字符串长度时,需要为该变量申请足足够的内存,否则会造成缓冲区溢出。但是使用SDS则不会有这个问题。
    对SDS进行修改时,API会检查SDS的空间是否满足内存需要,如果不满足,则会自动申请(这部分对用户来说是透明的,redis帮忙做了)内存,扩展至所需大小,然后再执行操作。

c) 空间预分配

    对SDS进行修改时之后,SDS的长度小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时SDS的len属性值将和free属性值相同。举个栗子每当SDS的len变为13字节,那么free也为13字节,那么buf的总长度为13+13+1=27(额外的一字节实时保存空字符串)
    如果对SDS修改后,长度大于1MB,那么程序会为SDS的free分配1MB。举个栗子:当SDS修改后长度为30MB,那么程序的free=1MB,SDS的buf长度为:30MB+1MB+1byte。

d)惰性空间释放
    当SDS的API需要速短SDS保存的字符串时,程序不立即回收多出来的字节,而是将这些字节分配给free,并等待使用。
比如:len长度为8字节,free为4字节,当buf释放3节点后,len=5,free=7。
   通过惰性空间释放策略,SDS避免了缩短字符串所需的内存重分配策略,并未将来的可能的增长操作提供优化。

e) 二进制安全

    程序会以处理二进制的方式来处理buf中的数据,所以不会其中的数据做任何限制。

1.2 链表

    Redis底层的链表是作为双向链表实现的:
多了LI在这里插入图片描述
   多个ListNode可以通过prev和next指针组成双端链表:
在这里插入图片描述
   虽然仅仅使用多个ListNode结构就可以组成链表,但是和SDS类似,Redis对链表也封装了一层功能更强大的数据结构:
在这里插入图片描述
   list结构为链表提供了表头指针head、表尾指针tail,以及链表长度计数器len,而dup、free和match成员则是用于实现多态链表所需的特定函数:

dup函数用于复制链表节点的值。
free函数用于释放链表节点的值。
match函数用于对比链表节点的和另一个输入值是否相等

redis链表结构为:
在这里插入图片描述

1.3 字典

   Redis 的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

1.3.1 哈希表结构

哈希表结构为:在这里插入图片描述

table属性是一个数组,数组中的每个元素都指向dictEntry结构,这个结构保存着键值对。
size属性记录了哈希表的大小,也即是tables数组的大小,
used属性则记录了哈希表目前已有节点的数量。
sizemask属性的值总是等于size-1,这个属性和键的哈希值决定该键对会被放置在table的哪个索引上。

   dictEntry结构:
在这里插入图片描述

key保存着键
v保存这值
next 指向链表的下一个元素,这是因为多个哈希值相同的键值对会作为单项链表元素保存在table的一项中。

比如:
在这里插入图片描述

1.3.2 字典结构

   字典的结构dict:
在这里插入图片描述

type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型的键值对的函数。redis会根据不同用途设置不同的函数。
privdata 属性则保存了需要传给哪些类型特定函数的可选参数。
ht 属性是一个包含两个项的数组,数组中每个向都是一个哈希表,ht[0]和ht[1]。ht[1]哈希表只会在对ht[0]扩容rehash时进行使用。除了ht[1]外,另一个和rehash有关的属性就是rehashidx,在未进行rehash时,它的值为-1。

在这里插入图片描述
   当需要将一个新的键值添加到字典中时,程序需要先根据键值对的键计算出哈希值和索引值,再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上。
hash = dict -> type -> hashFunction(key)
index = hash & dict -> ht[x].sizemask

1.3.3 扩容和减容rehash

   随着操作不断进行,哈希值的保存键值对会逐渐增加或者减少,为了让哈希表的负载因子维持在一个合理的范围内。
   当满足以下任意一个条件时,程序会自动扩容:

  1. 当服务器目前没有执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大等于1.
  2. 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5.
    负载因子公式为ht[0].used/ht[0].size
    另一方面当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行减容。

1.4 跳跃表

   Redis跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素成员是比较长的字符串,Redis就会跳跃表来作为集合键的底层实现。
跳跃表节点zskiplist
在这里插入图片描述

header: 跳跃表头节点。
tail:跳跃表尾节点。
level:层数最大那个节点的层数。
length:不包括头节点的,节点数量。

跳跃表节点相关信息 : zskiplistNode结构为:
在这里插入图片描述

层:表示跳跃表节点的level数组,这里可以包含多个元素,每个元素都包含一个指向其他节点的指针,一般来说层数越多,访问其他节点的速度就越快。
前进指针:表示指向表尾放心的前进指针。一个level,就有一个前进指针,说明一个节点可以有多个前进指针。
跨度:记录了两个节点间的距离。跨度越大距离越远。
后退指针:节点的后退指针用于从表尾向表头方向访问的指针,和前进指针不同,一个节点只能有一个后退指针。用BW字样标记。
分值:是一个浮点数,跳跃表的顺序就是按照分值大小来排序。
节点的成员对象:指向一个字符串对象,表示保存的成员对象。

   通过多个跳跃表节点就可以组成一个跳跃表。多个zskiplistNode组成zskiplist。程序可以通过的快速访问zskiplist的表头或者表尾来获取节点信息。

1.5 整数集合

   Redis通过整数集合来保存整数值,它可以保存的类型为:int16_t,int32_t或者int64_t,并且保证集合中不会出现重复元素。
整数集合数据结构为:
在这里插入图片描述

length属性记录了集合长度	
contents数组是整数集合的底层实现,整数的每个元素都是contents数组的一个数组项,从小到大排列,且不包含重复数据。
encoding属性:
	1. int16:-32768-32769
	2. int32:-2147483648-2147483648
	3. int64:-372036854775808-372036854775808

1.5.1 数据类型扩展升级

   当要将一个新元素添加到整数集合时,如果新元素的类型比整数集合现有所有元素都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合。
   升级整数集合并添加新元素共分为三步进行:
1)根据新元素类型,扩展整数集合底层数组的空间大小,为新元素分配空间。
2)将底层数组现有的所有元素都转换成与新元素相同的类型,并进行排序。
3)将新元素根据排序大小,放在正确的位置。
   比如:原整数集合都是INTSET_ENC_INT16编码的整数集合,集合中包含的都是int16类型,如果新元素为65535,超过了int16类型的数据,是int32类型。此时需要将原INTSET_ENC_INT16编码转化为INTSET_ENC_INT32编码,再根据新的编码方式,将老数据和新数据排序放入,放正确的位置上。

1.5.2 升级的优点

   升级有两个好处,一个是提升灵活性,另一个是节约内存。

1.6 压缩列表

   压缩列表是Redis为了节约内存开发的,是有一系列特殊编码的连续内存块组成的数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数组。
压缩列表结构:在这里插入图片描述

zlbytes:记录整个压缩列表所占的内存的字节数。
zltail:记录压缩列表表尾节点距离起始地址的偏移量。
zllen:记录压缩列表节点数。
entrx:压缩列表的各个节点
zlen:表示压缩列表结尾。

每个压缩列表节点可以保存一个字节数组或者一个整数值。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值