redis自己构建一种SDS的抽象类型,用作redis的默认字符串,可以用SDS的API操作
redis> SET msg “hello”
作用于缓冲区
c语言每次增长或者缩短一个字符串,都要内存重分配,不做就会内泄漏。SDS通过未使用空间free解除了字符串长度和底层数组长度的关联
空间预分配
惰性空间释放
c语言字符串必须符合某种编码(比如ASCII)中间不能有空字符,会被认为是结尾所以不能保存图片、音频、视频等文件
SDS是二进制安全的,以二进制的方式处理buf数组里的数据,写入什么读取就是什么
遵循C字符串以控制符结尾的惯例 这样是为了可以重用c语言的一部分库函数
链表提供了高效的节点重排能力,链表在Redis中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。
每个链表节点使用一个adlist.h/listNode结构来表示
多个listNode通过prev和next指针组成双端链表
用list来持有链表操作会更方便
字典,乂称为符号表( symbol table )、关联数组( associative array )或映射( map ),是 一种用于保存键值对(key-value pair)的抽象数据结构
tabel属性是一个数组,每个元素指向dict.h/dictEntry结构的指针,dictEntry存放键值对。
size记录哈希表的大小,即table数组的大小
used记录目前已有(键值对)的数量
sizemask属性和哈希值一起决定一个键应该被放到table数组上的哪个索引上面
存放键值对,next是指向下一个哈希表节点的指针(当他们的键的哈希值相等时),解决键冲突问题。
type属性和privdata示性式针对不同类型的键值对,为创建你多态字典设置
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不用的字典设置不同的类型特定函数。
ht属性是一个包含两个项的数组,数组的每一个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用
rehashidx,记录目前的进度,如果没有在进行rehash,那么他的值为-1
当一个新的键值对添加到字典时,要根据键算出哈希值和索引值,然后将新键值对的哈希表节点放到哈希表数组的指定索引上面。
哈希表保存的键值对会增多或者减少,为了让负载因子维持在一个合理的范围之内,当键值对的适量太多或者太少,就需要对哈希表的大小进行扩展或收缩,可以通过rehash(重新散列)操作来完成。
步骤:
如果执行的是扩展操作,那么ht [ 1 ]的大小为第一个大于等于ht [ 0 ] . used*2 的2”( 2的n次方幂);
如果执行的是收缩操作,那么ht [1]的大小为第一个大于等于ht [0] .used的2”
1)ht[0].used当前的值为4,4*2=8,所以ht[1]设置成8
2)将ht[0]包含的四个键值对都rehash到ht[1]
3)释放ht[0],并将ht[1]设置成ht[0]
为避免rehash对服务器性能造成影响,服务器不是一性将ht[0]里面的所有键值对全部rehash到ht[1],而是分多次,渐进式的将ht[0]里面的键值对慢慢地rehash到ht[1]
一次完整的rehash过程:
跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问的目的。
redis使用跳跃表作为有序集合键的底层实现之一,集合数量多或者元素的成员是比较长的字符串时使用。
Redis只在两个地方用到了跳跃表
实现有序集合键
在集群节点中用作内部数据结构
Redis跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义
1. zskiplistNode表示跳跃表节点
2. zskiplist保存跳跃表节点的相关信息(节点数量,表头节点,表尾节点)
最左边为zskiplist结构
1. header: 指向跳跃表的表头节点
2. tail:指向跳跃表的表尾节点
3. level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不算)
4. length:记录跳跃表的长度,跳跃表目前包含节点的数量(表头节点不计)
最右边的四个zskiplistNode
1. level:节点用L1,L2,L3等标记各个层(L1第一层) 每层两个属性:前进指针和跨度
2. 后退指针:BW标记为后退指针
3.分值(score):节点按各自保存的分值从小到大排列
4.成员对象:各个节点中的o1,o2,o3是节点所保存的成员对象
层
1. 一般来说,层数越多,访问其他节点速度越快
2.创建新节点,通过幂次定律随机生成一个1-32之前的值为level数组的大小,也就是高度
跨度:level[i].span属性 用于记录两个节点之间的距离(这是与普通跳跃表的区别之一)。因为在有序集合支持的命令中,有些跟元素在集合中的排名有关,比如获取元素的排名,根据排名获取、删除元素等。通过跳跃表结点的层跨度,可以快速得到该结点在跳跃表中的排名(排位rank)
分值和成员:
只靠多个跳跃表节点可以组成一个跳跃点,但是使用zskiplist结构来持有这些节点,可以更方便操作
整数集合是集合键的底层实现之一,当一个集合会包含整数值元素,并且这个集合的元素数量不多时redis使用整数集合作为集合键的底层实现。
保存的类型为int16_t、int32_t、int64_t的整数值,并且保证不会出现重复元素
当我们向一个整数集合类型比较小的数组(int16_t)中加入一个比目前长的类型时(int32_t),整数集合需要先升级才能将新元素添加到整数集合。
步骤:
压缩列表是列表和哈希键的底层实现之一。列表键只含有少量列表项,并且列表项要么小整数值,要么长度比较短的字符串。哈希键包含的所有键和值都是小整数值或者短字符串。
由一系列特殊编码的连续内存块组成的顺序型数据结构。
一个压缩裂列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值
字节数组长度选择:
<=63(2^6-1)
<=16383(z^14-1)
<=(2^32-1)
整数值长度选择:
4位长,0-12间的无符号整数
1字节或者3字节的有符号整数
int16_t 、int32_t、 int64_t
previous_entry_length:
以字节为单位记录前一个字节的长度
前一个字节长度<254字节,则previous_entry_length长度为1
前一个字节长度>=254字节,则previous_entry_length长度为5
第一个字节会设置为0xFE(后4字节保留前一字节的长度)
从表尾遍历到表头使用下面的原理实现,一直回溯到头
encoding
记录节点的content保存的类型和长度
1字节,2字节,5字节长,值的最高位为00,01,10是字节数组编码
1字节,值的最高位为11开头的是整数编码
上面两种数组的长度都由编码除去最高两位之后的其他记录
content
保存节点的值,根据上面的encoding表对照
当所有的节点长度都为250-253字节之间,所以他们保存的都是1字节长的previous_entry_length。如果讲一个长度>=254的节点设置为表头节点,后面的e1节点保存的previous_entry_length由1变成5,后面的e2,e3…都会更新。这个过程叫连锁更新。
删除节点也可能导致连锁更新—e1-en是250-253 ,big长度>=254,small<254的情况
redis并没有直接使用前面介绍的数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个对象系统包含字符串对象,列表对象,哈希对象,集合对象和有序集合对象
键总是一个字符串对象,而值可以是字符串对象,列表对象,哈希对象,集合对象和有序集合对象的一种
可以用TYPE命令来获取键对应的值对象的类型
对象的ptr指针指向对象的底层实现数据结构,但是这个数据结构由对象的encoding属性决定。
每种类型的对象至少使用了两种不同的编码
用OBJECT ENCODING命令可以查看一个数据库键的值对象的编码
通过encoding属性设定对象所使用的编码,提高了灵活性和效率,redis可以根据不同的场景来为一个对象设置不同的编码。
字符串对象的编码可以使int、raw、embstr。
如果是整数值,会将ptr属性里面(将void*变为long)编码变为int
如果是字符串,并且长度大于32字节,将用SDS保存,编码为raw
如果是字符串,并且长度小于等于32字节,编码为embstr
embstr是专门保存段字符串的一种优化编码方式,raw会调用两次内存分配函数来创建redisObject结构和sdshdr结构,embstr只有一次
embstr的好处
再有需要的时候,程序会将保存的字符串对象转为浮点数,执行操作后再转为字符串。例如数字相加的时候。
编码的转换
int和embstr在某些条件下会变为raw编码
列表对象的编码可以是ziplist或者linkedlist
编码转换
满足下面的条件-----ziplist 任意一个不满足转换linkedlist
哈希对象的编码是ziplist或者hashtable
集合对象的编码是intset或者hashtable
有序集合的编码是ziplist或者skiplist
编码转换
满足下面的条件-----ziplist 任意一个不满足转换skiplist
本文是对学习redis数据结构的总结,参考书籍《redis设计与实现》
单机数据库的实现