redis 数据结构

简单动态字符串(SDS)

空间预分配

空间预分配用于优化SDS的字符串增长操作:当SDSAPI对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。

如果对SDS进行修改之后,SDS的长度(也即是len属性的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDS len属性的值将和free属性的值相同。如果对SDS进行修改之后,SDS的长度将大于等于1MB,那么程序会分配1MB的未使用空间。



链表

当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。


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

双端:链表节点带有prevnext指针,获取某个节点的前置节点和后置节点的复杂度都是0(1)。

无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。

带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为0(1)。

带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为0(1)。

多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dupfreematch三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。


哈希

当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。


哈希表结构

table属性是一个数组,数组中的每个元素都是一个指向dict.h/dictEntry结构的指针,每个dictEntry结构保存着一个键值对。size属性记录了哈希表的大小,也即是table数组的大小,而used属性则记录了哈希表目前已有节点(键值对)的数量。sizemask属性的值总是等于size-1,这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面(哈希值与sizemask进行与运算)。

哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对:


哈希结点结构


下图就展示了如何通过next指针,将两个索引值相同的键k1和k0连接在一起。


Redis中的字典由dict.h/dict结构表示:


ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。

除了ht[1]之外,另一个和rehash有关的属性就是rehashidx,它记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1。


rehash

随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。

扩展和收缩哈希表的工作可以通过执行rehash (重新散列)操作来完成,Redis对字典的哈希表执行rehash的步骤如下:

1) 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht [ 0 ] • used属性的值):

•如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0] .Used*2 的2^N(2的n次方幂);

•如果执行的是收缩操作,那么ht [ 1 ]的大小为第一个大于等于ht [ 0 ] . used的2^N。

2) 将保存在ht[0]中的所有键值对rehashht [1 ]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[l]哈希表的指定位置上。

3) 当ht[0]包含的所有键值对都迁移到了ht[l]之后(ht[0]变为空表),释放ht[0],将ht[l]设置为ht[0],并在ht[l]新创建一个空白哈希表,为下一次rehash 做准备。


渐进式rehash

rehash动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。

以下是哈希表渐进式rehash的详细步骤:

1 )为ht [ 1 ]分配空间,让字典同时持有ht [ 0 ]和ht [ 1 ]两个哈希表。

2) 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash 工作正式开始。

3) 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehashht [ 1 ],当rehash工作完成之后,程序将rehashidx属性的值增一。

4) 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehashht [ 1 ],这时程序将rehashidx属性的值设为-1,表不rehash操作已完成。

渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。


整数集合

整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。它可以保存类型为intl6_t, int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。


整数集合结构体

contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组的一个数组项(item),各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。

length属性记录了整数集合包含的元素数量,也即是contents数组的长度。

虽然intset结构将contents属性声明为int8_t类型的数组,但实际上contents数组并不保存任何int8_t类型的值,contents数组的真正类型取决于encoding属性的值,encoding可以设置为INTSET_ENC_INT16,INTSET_ENC_INT32,INTSET_ENC_INT64。


升级与降级

每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级(upgrade )

升级整数集合并添加新元素共分为三步进行:

1)根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。

2) 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。

3) 将新元素添加到底层数组里面。

整数集合的升级策略有两个好处,一个是提升整数集合的灵活性,另一个是尽可能地节约内存。因为整数集合可以通过自动升级底层数组来适应新元素,所以我们可以随意地将intl6_t、int32_t或者int64_t类型的整数添加到集合中,而不必担心出现类型错误,这种做法非常灵活。

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。


压缩列表

压缩列表是一种为节约内存而开发的顺序型数据结构,被用作set,hash和zset的底层实现之一。压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构,可以包含多个节点,每个节点可以保存一个字节数组或者整数值。


压缩列表节点的构成


节点的previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。因为节点的previous_entry_length属性记录了前一个节点的长度,所以程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址。压缩列表的从表尾向表头遍历操作就是使用这一原理实现的。

节点的content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

参考:《redis设计与实现》


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容

  • 前言 我们都知道,redis最基本的数据结构有5种,分别是字符串、列表、哈希表、集合和有序集合。其实准确来说,这种...
    绝色天龙阅读 407评论 0 1
  • 1 简单动态字符串 Redis 是用 C 语言写的,但是对于Redis的字符串,却不是 C 语言中的字符串(即以空...
    爱健身的兔子阅读 318评论 0 0
  • 1. 简单动态字符串(SDS) Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数据),而是自己构...
    JingQ阅读 467评论 0 0
  • [toc][图片上传失败...(image-26e59f-1537503611902)] 1. 对象类型和数据结构...
    王小杰at2019阅读 237评论 0 0
  • 一,概述 本文主要简单介绍下redis的主要数据结构,分别是动态字符串、链表、字典、跳跃表、整数集合、压缩列表...
    忘记M阅读 451评论 0 0