redis数据结构

一,概述

    本文主要简单介绍下redis的主要数据结构,分别是动态字符串、链表、字典、跳跃表、整数集合、压缩列表。

二,简单动态字符串

    redis自己构建了一个名为简单动态字符串(simple dynamic string,SDS)的抽象类型。SDS作为redis的默认字符串,在redis的数据库里,包含字符串值的键值对在底层都是由SDS实现的。

free:bug数组中未使用字节数量。

len:SDS所保存字符串长度。

buf:char类型数组,以空字符 '\0' 结尾。

空字符的1字节,不计算入len长度,由于redis本身是有C语言编写,加空字符结尾是为了使用C语言字符串的特性,同时又比C语言字符串多了len和free属性,比如获取字符串长度,C语言需要去遍历字符串直到空字符结尾,而SDS直接获取len属性即可,复杂度仅为O(1)。

SDS相比C字符串另一个好处是,它的空间分配策略杜绝了缓冲区溢出,SDS的空间分配策略,解除了字符串长度和底层数组间的关联,也就是在SDS中,buf的长度并不一定是字符串长度。

空间预分配:用于优化SDS的字符串增长操作。

当字符串变长后,如果变更后的长度小于buf长度,那么不会重新分配内存空间,若变更后的长度大于buf长度,那么就需要内存重新分配空间,这时,若len属性的值小于1MB,那么程序分配和len属性值同样大小的未使用空间,这时len属性值和free属性值相同,即变更后的buf长度,等于len +free + 1的值;若变更后len属性值大于等于1MB,那么程序会分配1MB的未使用空间。    

惰性释放空间:用于优化SDS字符串缩短操作。

当SDS缩短字符串长度时,内存并不会马上释放多余空间,而是会以free记录的形式将数量记录起来,可以等待后续扩展字符串使用。

三,链表

redis使用链表作为列表键的底层实现,除了列表键之外,发布与订阅、慢查询、监视器等功能也用到了链表。

由多个listNode组成的双端链表

代码:

typedef struct listNode{

    struct listNode *prev;//前置节点

    struct listNode *next;//后置节点

    void *value;//节点的值

}listNode

redis使用list来持有listNode链表:

代码:

typedef struct list{

    listNode *head;//表头节点

    listNode *tail;//表尾节点

    unsinged long len;//链表所包含的节点数量

    void *(*dup) (void *ptr);//节点值复制函数

    void (*free) (void *ptr);//节点值释放函数

    int (*macth) (void *ptr,void *key);//节点值对比函数

}list

链表特性:

1,双端:链表节点带有 prev和next指针,获取某个节点的前置节点和后置节点的复杂度为o(1)。

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

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

4,带链表长度计数器:len属性对list持有的链表节点进行计数,程序获取链表节点数量复杂度为o(1)。

5,多态:链表节点使用void* 指针来保存节点值,并可通过list结构的dup,free,match三个属性节点设置特定函数。

四 字典

redis的数据库使用字典来作为底层实现,对数据库的增、删、改、查操作也是构建在对字典的操作上。

除了用来表示数据库之外,字典还是哈希键的底层实现之一,redis的字典使用哈希表作为底层实现。

哈希表:

typedef struct dictht{

    dictEntry **table;//哈希表数组

    unsigned long size;//哈希表大小

    unsigned long sizemask;//哈希表大小掩码,用户计算索引值,总是等于size-1

    unsigned long used;//该哈希表已有节点数量

}dictht

哈希表节点:

typedef struct dictEntry {

    void *key;//键

    union{ //值

        void *val;

        uint64_tu64;

        int64_ts64;

    } v;

    struct   dictEntry *next;//指向下一个哈希表节点,形成链表

}dictEntry 

字典:

typedef struct dict{

    dictType *type;//类型特定函数

    void *privdata;//私有数据

    dictht ht[2];//哈希表

    int  trehashidx;//rehash索引,当rehash不在进行时,值为-1

}dict

type:是一个指向dictType结构的指针。

privdata:保存需要传给特定函数的可选参数。

ht:两个数组,一般情况下只使用ht[0]哈希表,ht[10]哈希表用作扩展备份。

哈希算法:

根据字典dict的type,计算键key的哈希值,再使用哈希表的sizemask和刚计算的哈希值,计算出索引值,使用murmurhash2算法。

rehash:

负载因子 = ht[0].used / ht[0].size ;

服务器目前没有执行bgsave或者bgrewriteaof命令,负载因子大于等于1 ,即进行rehash扩展操作。

服务器目前正在执行bgsave或者bgrewriteaof命令,负载因子大于等于5,即进行rehash扩展操作。

另,负载因子小于0.1时,进行收缩操作。

rehash不是一次性全部扩展完成,而是渐进式的,

dict里的trehashidx值,就是渐进式rehash的进度计数器,trehashidx为0时,rehash开始,在rehash期间,每次对字典执行添加、删除、查找、更新操作时,程序除了指定操作外,还会顺带将ht[0]哈希表在trehashidx索引上的所有键值对rehash到ht[1],rehash完成后,trehashidx加1,当所有ht[0]都rehash到ht[1]后,trehashidx置为-1。

五 跳跃表

跳跃表不做多介绍,redis只有少数几个地方用到了,一个是实现有序集合键,一个是集群节点中用作内部数据结构。

header:指向跳跃表的表头节点。

tail: 指向跳跃表的表尾节点。

level:记录跳跃表内,层数最大的那个节点的层数(表头节点层数不计算内)。

length: 跳跃表长度,即跳跃表节点数(表头节点不计算内)。

六 整数集合

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

保存类型可以 int16_t,int32_t,int64_t 的整数值,集合中不会出现重复元素。

typedef struct inset{

    uint32_t encoding;//编码方式

    uint32_t  length;//集合包含的元素数量

    int8_t  contexts[];//保存元素的数组

}inset

contexts[] 数组是整数集合的 底层实现,整数集合的每个元素都是contexts数组的一个数组项,各个项在数组中按值的大小从小到大排列,并且不包含重复项。

七 压缩列表

压缩列表是列表键和哈希键的底层实现之一。

当列表键只包含少量列表项,并且每个列表项要么是小整数值,要么是长度比较短的字符串,那么redis会把压缩列表当做列表键的底层实现。

当哈希键只包含少量键值对,并且每个键值对的键和值要么是小整数值,要么就是长度比较短的字符串,那么redis会把压缩列表当做哈希键的底层实现。

压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。


zlbytes:记录整个压缩列表占用的内存字节数,在对压缩列表进行内存重分配或者计算zlend的位置时使用。

zltail:记录压缩列表 表尾节点距离压缩列表的起始地址有多少字节,通过这个偏移量,程序无需遍历整个压缩列表就可以确定表尾节点地址。

zllen:记录压缩列表包含的节点数量,当这个属性小于uint16_max(65535)时,这个属性的值就是包含的节点数量,当大于65535时,需要真实遍历才能计算得出。

entryX:列表节点。

zlend:特殊值oxff(十进制255),标记压缩列表末端。


压缩列表节点

previous_entry_length:字节为单位,长度为1字节或5字节。当前一节点长度小于254字节,previous_entry_length为1字节;当前一节点长度大于等于254字节,previous_entry_length为5字节,属性第一个子节被设置成OxFE,之后四个字节则保存前一节点长度。

encoding:记录节点content保存的数据类型及长度

content:保存节点的值





参考文献《redis设计与实现第二版》

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