7.1 数据结构

Posted fmys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7.1 数据结构相关的知识,希望对你有一定的参考价值。

7.1.1  value 对象的通用结构

typedef struct redisObject{

unsigned type:4;

unsigned encoding:4;

unsigned  lru:REDIS_LRU_BITS;

int refcount;

void *ptr;

} robj;

type指String、List等结构类型。

encoding指这些结构化类型具体的实现(承载)方式,同一个类型可以有多种实现,例如String可以用int来承载,也可以用封装的char[]来承载,List可以用ziplist或者链表来承载。

lru 表示本对象的空转时长,用于有限内存下长久不访问的对象的清理。

refcount 是应用计数用于对象的垃圾回收。

ptr 指向的是以encoding方式实现这个对象的实际承载者的地址,如string对象对应的是sds地址。

7.1.2 String

三种类型:

字符串

整数

浮点数

1.基本操作

技术图片

 

 

 2.内存数据结构

以int、SDS(simple dynamic string)作为结构存储。

int存放整形数据,sds存放字节/字符串和浮点型数据

1)sds结构

typedef struct sdshdr{

unsigned int len;

unsigned int free;

char buf[];

}

技术图片

 

  在redis实现中仅作为字符串定界符,不表示业务数据内容不能包含字符。如上图sds的bufSize为8,len为5,free为2。

2)buf的扩容与缩容

字符串初始化时,buf的大小=len+1,即加上定界符刚好等于业务数据的长度

当字符串操作完成后预期的长度小于1MB时,扩容后的buf大小=业务串预期长度*2+1,即不考虑 ,buf加倍

对大于1MB的长串,buf总是流出1MB的free空间,即buf以业务串的2倍来扩容,但最大留出1MB的空间。

3)字符串与字节串

sdds中存储的内容可以睡ASCII字符串,也可以是字节串。由于sds通过len字段来确定业务串的长度,业务串可以存储非文本内容

7.1.3 List

1.基本操作

RPUSH/LPUSH

RPOP/LPOP

LINDEX

LRANGE

LTRIM

BLPOP/BRPOP

BLPOPPUSH/BRPOPPUSH

2.内存数据结构

List类型的value对象内部以linkedlist或ziplist承载。当List的元素个数和单个元素的长度较小时,Redis会采用ziplist实现减少内存,否则使用linkedlist结构。

3.linkedlist实现

双向链表实现,

typedef struct list{

listNode *head;

listNode *tail;

void *(*dup) (void *ptr);

void (*free) (void *ptr) ;

int (*match) (void *ptr , void *key);

unsigned long len;

} list;

 

typedef struct listNode{

struct listNode *prev;

struct listNode *next;

void *value;

} listNode;

list的定义了头尾元素指针和列表长度,所以POP/PUSH操作、LLEN操作的复杂度为O(1)。因为是链表,LINDEX类的操作的复杂度仍是O(N)

4.ziplist实现

1)列表结构

List的所有内容被放置在连续的内存中,结构如下:

<zlbytes><zltail><zllen><entry><entry>...<zlend>

zlbytes表示ziplist的总长度;zltail表示最末元素;ziplist是连续内存,所以实际zltail的值是最末元素距离ziplist头的偏移量;zllen表示元素个数;后续每个<entry>即元素自身内容,是自包含的;zlend恒为0xFF作为ziplist的定界符。

ziplist对于获取RPUSH、RPOP、LLEN等操作复杂度为O(1)。LPUSH/POP涉及全列表元素移动,复杂度为O(N),但是ziplist用于元素个数较少,N本身不大。

2)元素结构

每个entry包含两部分:

相邻的前一个entry的长度

自描述的本entry内容

前邻entry长度记录的作用是方便实现双向遍历,类似linkedlist的节点prev指针。

偏移量即前一个entry的长度-1,故直接记录长度。entry长度大于255需要超过1字节来表达。Reids支持最多5个字节来表达前邻entry长度,大多情况entry长度不会超过200,总是用5个字节会浪费存储。所以,Redis设计两种长度的长度实现,相邻entry的长度小于254时,其length用1字节存放,否则用5字节。

有一个问题:当前邻长度变化时,本entry长度也可能变化,从而引起本entry后一个相邻entry长度变化,以此类推。当然这二种情况概率极小。

entry本身的业务内容是自描述的,意味着第二部分包含了几个信息:本entry的内容类型、长度、和内容本身。

类型和长度同样采用变长编码:

1.4 Map

map内部的key和value不能再嵌套map了,只能是String类型所能表达的内容:整形、浮点型、字符串。

1.基本操作

2.map可应用hashtable和ziplist两种承载方式来实现。对于数据量小的map,采用ziplist实现。

3.hashtable实现

哈希表在Redis中分为三层,自底向上分别是:

dictEntry:管理一个key-value对,同时保留同一个桶中相邻元素的指针,一次维护哈希桶的内部链

dictht:维护哈希表的所有桶链

dict:当dictht需要扩容/缩容时,用于管理dictht的迁移。

1)哈希表

哈希表的核心结构是dictht,它的table字段维护着hash桶,它是一个数组,每个元素指向桶的第一个元素(dicEntry):

typedef struct dictht{

dictEntry **table;

unsigned long size;

unsigned long sizemask;

unsigned long used;//当前hash表存储了多少个键值对

}

由于桶的个数永远是2的n次方,可以用size-1做位运算&快速得到哈希值的模,所以dictht中引入了sizemask,其值恒等于size-1。

2)扩容

根据负载因子判定是否需要增加桶数,负载因子=哈希表中已有元素和哈希桶数的比值,目前有两个阀值:

小于1时一定不扩容;

大于5时一定扩容;

介于1到5之间时,Redis如果没有进行bgsave/bdrewrite操作时则扩容。

如果有大量空桶需要缩容,Redis同样根据负载因子决定是否缩容,目前的缩容阀值是0.1。

无论是扩容还是缩容,桶的数量都是指数变化:扩容时新的桶数秒是现有桶的2n倍,扩到刚好大雨used值(??),缩容后新的桶是原有的0.5n倍,也是缩到刚好大雨used值(??)。

扩/缩容通过新建哈希表的方式实现。将原表迁移到目标表,迁移完成后,目标表覆盖原表。

dict对象维护着哈希表的迁移状态:

ypedef struct dict{

dictType *type;

void *privdata;

dictht ht[2];

long rahashidx;

int iterators;

} dict;

ht[0]代表源表,也是正常情况下访问的表。仅在迁移过程中,ht[1]可用,作为目标表。

此时首先访问源表,如果发现key对应的通已经完成迁移,则重新访问目标表,否则在源表中操作。

dict通过rehashindex记录已经完成迁移的通。如下图:

技术图片

 

 

由于Redis是单线程处理请求,迁移和访问的请求在相同线程内时分复用地进行,因此迁移过程中不存在并发问题。结构性value的并发问题也无须加锁进行(后续7.4节详细介绍)

4.ziplist实现

这里的ziplist和list的ziplist实现类似,都是通过entry存放element。和List不同的是,map对应ziplist的entry个数总是2的整数倍,第奇数个entry存放key,key对应entry的下一个相邻entry存放该key对应的value。

ziplist实现下,map的大多数操作的复杂度不再是O(1)了,由哈希遍历变成链表的顺序遍历,复杂度编程O(N)。但由于采用ziplist的map大小通常偏小,所以性能损失可控。通常情况下,只有很少几个kv对的map,采用ziplist效率反而更高,省去hash计算、内存寻址等操作。尤其对于长字符串key,其hash值计算本身的开销甚至远大于顺序遍历时字符串比较的开销。

7.1.5 Set

 

以上是关于7.1 数据结构的主要内容,如果未能解决你的问题,请参考以下文章

[知识点] 7.1 栈,队列与链表

Fortran数据格式的3F7.1的含义

C#科学计数字符串,如“7.1e-003”转换为decimal类型数据

Worklight 6.1 到 Mobilefirst 7.1 数据库迁移问题

挑战程序设计竞赛(算法和数据结构)——7.1归并排序JAVA实现

数据库-第七章 数据库设计-7.1 数据库设计概述