Redis3.0底层数据结构

底层数据结构

简单动态字符串

SDS的定义

len:SDS的总长度
alloc:已分配的长度
buf:存放实际的字符串(末尾要加一位的结束符)

SDS与C字符串的区别

SDS封装了C字符串,用空间换效率,满足了Redis要求的安全性、效率性以及功能方面的要求

1.常数复杂度获得字符串长度

通过len属性直接获取字符串的长度,len长度在SDS的API执行时自动修改

2.避免缓冲区溢出

SDS的空间分配策略杜绝了发生缓冲区溢出的可能性

  1. 当需要对SDS进行修改时,会先检查SDS的空间是否满足修改所需的大小
  2. 如果不满足,API会自动将SDS的空间扩展至执行修改所需的大小
  3. 在确保空间足够的情况下才会执行实际的修改操作
3.减少修改字符串带来的重分配次数

空间预分配:SDS每次扩展的时候会额外分配未使用空间,减少分配次数

  • 修改后的长度小于1MB则空间翻倍
  • 修改后的空间大于1MB则(n+1)MB

惰性空间释放:SDS每次进行缩短操作的时候不会进行len缩小,为将来有需要的时候做准备

  • 修改alloc的值,等同于逻辑删除
4.二进制安全

由于C语言的字符串必须符合某种编码,而且除了字符串的末尾以外不能包含空字符,所以存储的字符有限,而SDS就没有这些限制。
SDS以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设。数据在写入和读取时是一样的。
这都归功于SDS的alloc属性来判断字符串是否结束。

5.兼容部分C字符串函数

因为SDS存储时buf的结尾会添加一个结束符,来模拟一个C字符串

6.比较
C字符串SDS
获得字符串长度的复杂度为O(N)获得字符串长度的复杂度为O(1)
API是不安全的,可能造成缓冲区溢出API是安全的,不会造成缓冲区溢出
修改字符串长度N必然需要执行N次内存重分配修改字符串长度N次最多需要执行N次内存重分配
只能保存文本数据可以保存文本或者二进制数据
可以使用<string.h>库中的函数可以使用一部分<String.h>库中的函数

链表

链表和链表节点的实现

多个链表可以通过pre和next指针组成双端链表

Redis的链表实现的特性可以总结如下

  • 双端:链表节点带有pre和next指针
  • 无环:链表是无环的
  • 带头尾指针:可以通过O(1)复杂度访问头尾
  • 带链表长度计数器:O(1)复杂度获得长度
  • 多态:链表节点可以通过void*指针保存节点值,保存不同类型的值

字典

字典的实现

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

哈希表

哈希表的table属性是一个数组,数组中的每个元素都指向一个dirEntry结构的指针
size属性记录了哈希表的大小

哈希算法:通过n & (size - 1)
解决键冲突:采用链地址法
rehash

随着操作的不断执行,哈希表保存的键值对会增加或减少,这时程序就需要根据哈希表的大小进行相应的扩容。这是通过rehash来实现的

rehash过程
  1. 根据ht[0]的已使用空间 n 来给ht[1]分配空间
    • 扩展操作:大于 2n 的第一个数字
    • 收缩操作:大于 n 的第一个数字
  2. 将保存在ht[0]中的所有键值对rehash到ht[1]上面
  3. 此时ht[0]是空表,将ht[0]指向ht[1],ht[1]置为空,以便下一次使用
渐进式rehash

如果数据规模很大,一次性rehash会造成系统阻塞,所以需要渐进式rehash,将ht[0]中的键值对分多次、渐进式地转移到ht[1]中

  1. 为ht[1]分配送检,让字典同时拥有ht[0]和ht[1]两个哈希表
  2. 在字典中维持一个索引计数器rehashidx,设置为0,表示将要转移第0个桶位
  3. 每次对字典的读或写操作除了命令本身外,还会顺带将ht[0]的rehashidex桶位上的所有键值对转移到ht[1]表中,并将rehashidx+1
  4. 随着字典操作的不断进行,最终在某个时间点上,ht[0]的所有键值对都会被rehash到ht[1]上,然后设置rehashidx为-1,表示已完成

渐进式rehash采用的是一种分而治之的思想,将一次性的rehash操作分摊到对字典的每个添加、删除、查找和更新操作上,从而避免了rehash带来的庞大开销

注意:查找操作会在ht[0]和ht[1]表当中分别查找,新添加的键会放在ht[1]表中,保证ht[0]只减不增。

跳跃表

跳跃表是一个有序数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的

跳跃表头

  • header:指向头结点
  • tail:指向尾结点
  • level:记录最大层数(不考阔表头节点)
  • length:记录跳跃表长度

跳跃表节点

  • level:
    • 前进指针:记录当前层的下一个节点位置
    • 跨度:下一个节点位置与当前节点位置的举例
  • backward:后退指针:用于表尾向表头遍历
  • score:分值,用于分值从小到大排序
  • obj:成员对象
跳跃表节点

层:跳跃表节点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度。
前进指针:遍历时需要从高到低寻找跨度为1的层级前进
跨度:用来计算排位,即元素在表当中的为此
后退指针:用于倒序遍历跳跃表中的所有节点
分值和成员:字典排序分值来确定先后顺序,小在前,大在后

整数集合

当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现

整数集合的实现

encoding:编码方式,有16位、32位、64位三种实现
length:集合元素的数量
contents[]:保存元素的数组,从小到大排序,不包含任何重复项,数据类型真正取决于encoding

升级

当我们需要将一个新元素添加到整数集合当中时,如果新元素的类型(encoding)比现有的元素类型都要长时,整数集合需要先进行升级,将content中的所有数据都升级为新元素的类型,然后再添加新元素

升级步骤
  1. 根据新元素的类型,扩展整数集合底层数组空间大小,并为新元素分配空间
  2. 将底层数组中现有的元素转换成相同类型,然后将类型转换后的元素放到正确的位置上(先往后放)
  3. 将新元素添加进去
升级的好处
  1. 提升整数集合的灵活性:动态升级,不必担心类型错误的出现
  2. 尽可能的节约内存:只有需要的时候才会进行升级
不支持降级

压缩列表

压缩列表是列表建和哈希键的底层实现之一。用于保存少量数值,是为了节约内存而开发的。

压缩列表的构成

zlbytes:记录整个压缩列表所占用的内存字节数
zltail:记录压缩列表表尾距离起始地址共有多少字节,通过zltail可以获得表尾节点地址
zllen:记录了压缩列表中的节点数量
entryX:压缩列表包含的各个节点,节点的长度由节点保存的内容决定
zlend:特殊值0xFF,用于标记压缩列表的末端

压缩列表节点的构成

压缩列表节点可以保存一个字节数组或一个整数值
字节数组:2^6-1, 2^14-1, 2^32-1三种长度之一
整数值:4bit(0~12)之间的无符号数,1B的有符号整数,3B的有符号整数,int16,int32,int64

  • previous_entry_length:0xFE00002766

    • 如果前一个节点长度小于254字节,则previous_entry_length为1
    • 如果前一个节点长度大于254字节,则previous_entry_length为5,然后第一个字节位0xFE
      程序可以通过指针运算,根据当前节点的起始地址计算出前一个节点的起始地址
  • encoding
    记录节点的content属性所保存数据的类型

    • 00、01、10是字节数组编码,表示content为字节数组,分别代表1/2/5字节
    • 11表示整数编码
  • len:剩下的4B记录长度,记录节点的content属性所保存数据的长度

  • content:记录具体存储的内容

连锁更新

在压缩列表中,由于previous_entry_length属性会根据前一个元素的大小而发生位数的变化,同时这种变化还可能会连续造成后续元素的变化,这样就产生了连锁更新的现象(前一个元素长度变化导致)
如果出现了很多连锁更新,那么会对性能造成极大的影响,但是发生的概率很小,而且三五个节点的连续更新对性能的影响不是很大。

底层数据结构总结

简单动态字符串

  • Redis只会使用C字符串作为字面量,在大多数的情况下,Redis采用SDS作为字符串表示
  • 常数复杂度获取字符串长度
  • 杜绝缓冲区溢出
  • 减少修改字符串长度时所需的内存重分配次数
  • 二进制安全
  • 兼容部分C字符串函数

链表

  • 广泛应用于实现Redis的各种功能,比如列表键、发布和订阅、慢查询、监视器等
  • 每个链表节点由一个ListNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以是双端链表
  • 每个链表由一个list结构来表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息
  • 因为链表的前置节点和后置节点的末尾元素都指向null,所以Redis的链表实现是无环链表
  • 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值

字典

  • 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键
  • Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用
  • 当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis采用MurmurHash2算法来计算哈希键的哈希值
  • 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表
  • 在对哈希表进行扩展或者收缩操作时,程序需要将现有哈希表包含的所有键值对rehash到新哈希表里面,并且这个rehash过程并不是一次性完成的,而是渐进式完成的

跳跃表

  • 跳跃表是有序集合的底层实现之一
  • Reids跳跃表是由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点
  • 每个跳跃表节点的层高都是1至32之间的随机数
  • 在同一个跳跃表当中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的
  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序

整数集合

  • 整数集合是集合键的底层实现之一
  • 整数集合的底层实现为数组,这个数组以有序、无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型
  • 升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存
  • 整数集合只支持升级操作,不支持降级操作

压缩列表

  • 压缩列表是一种为节约内存而开发的顺序型数据结构
  • 压缩列表被用作列表键和哈希键的底层实现之一
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或整数值
  • 添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率不高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第 1 章Redis 介绍. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 第 2 章数据类型初探. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 字符串 (Strings) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 列表 (Lists). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 集合 (Sets) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 哈希 / 散列 (Hashes) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 有序集合 (Sorted sets) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 位图 (Bitmaps) 和超重对数 (HyperLogLogs) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 第 3 章从入门到精通(上) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Redis 键 (Keys). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Redis 字符串 (Strings) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 改变和查询键空间 (key space). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Redis 过期 (expires):有限生存时间的键. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 第 4 章从入门到精通(中) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Redis 列表(Lists) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Redis 列表起步. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 列

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值