redis 数据结构

来源:互联网 发布:qsv转换flv软件 编辑:程序博客网 时间:2024/05/18 11:24

概述

redis数据库中每个键值对(key-value)都是由对象(object)组成:
- 数据库的key都是字符串对象(string object)
- 数据库的value可以是字符串对象、列表、字典、集合、有序集合对象

但是这五种类型,在redis底层并不是简单的封装c\c++的基本类型,redis设计了自己的一套底层数据结构。redis底层数据结构:

  1. 简单动态字符串
  2. 链表
  3. 字典
  4. 跳跃表
  5. 整数集合
  6. 压缩列表

下面会分别讨论这几种数据结构,以及redis是怎么使用他们构成value的数据类型

1 简单动态字符串

redis没有直接使用c语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型。在redis中,键值对的键是一个字符串对象,对象的底层实现是一个SDS。

1.1 SDS的定义

struct sdshdr{    int len    //buf中已占用空间的长度len,等于字符串长度    int free   //buf中剩余空间的长度    char buf[] //数据空间}

一个SDS例子如下:

这里写图片描述

  • free值为0,代表这个SDS的未使用空间为0
  • len为5 表示保存了一个长度为5的字符串
  • buf数组char类型数组 用于实际存储

1.2 SDS字符串和c字符串的区别

c语言使用长度为N+1的字符数组表示长度为N的字符传,并且字符数组最后一个元素总是空字符’\0’。例如:

这里写图片描述

但是c语言的这种方式,不能满足redis对于安全性、性能、功能的要求。

1.2.1 SDS可以常数复杂度获取字符串长度

c语言字符串要想获得字符串长度,需要遍历字符数组,复杂度为O(n)。
SDS字符串获取字符串数组长度,只需要返回struct中len字段 复杂度为O(1)。

1.2.2 杜绝缓冲区溢出

c 字符串 不记录字符串长度,除了获取的时候复杂度高以外,还容易导致缓冲区溢出。

这里写图片描述

如果我们现在将s1的内容修改为redis cluster,但是又忘了重新为s1分配足够的空间,这时候就会出现以下问题:

这里写图片描述

s1的数据溢出到s2所在空间中,导致s2保存的内容意外被修改。
如果使用SDS字符串就不会出现这种问题,在调用SDS修改api时,会检查free字段长度,如果不够则会扩展char[] buf ,重新分配空间,避免了缓冲区溢出问题。

1.2.3 减少修改字符串带来的内存重新分配次数

如1.2.1中描述,c语言字符串每次修改,都系要重新分配内存空间,而SDS字符串,因为有预留字符数组buf,可以极大的减少内存重新分配的次数。

1.2.4 二进制安全

C 字符串中的字符必须符合某种编码,并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存想图片,音频,视频,压缩文件这样的二进制数据。
但是在Redis中,不是靠空字符来判断字符串的结束的,而是通过len这个属性。那么,即便是中间出现了空字符对于SDS来说,读取该字符仍然是可以的。

2 链表

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
链表在Redis 中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis 就会使用链表作为列表键的底层实现。

2.1 链表数据结构

每一个链表节点用一个listNode结构表示:

typedef struct listNode{      struct listNode *prev;      struct listNode * next;      void * value;  }

链表的的结构为:

typedef struct list{    //表头节点    listNode  * head;    //表尾节点    listNode  * tail;    //链表长度    unsigned long len;    //节点值复制函数    void *(*dup) (void *ptr);    //节点值释放函数    void (*free) (void *ptr);    //节点值对比函数    int (*match)(void *ptr, void *key);}

本质上就是一个双向链表

3 字典

实现类似于java中的hashMap

4 跳跃表

redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另外一个是在集群节点中用作内部数据结构。

4.1 跳跃表定义

我们先来看一下一整个跳跃表的完整结构:

这里写图片描述

redis 的跳跃表主要由两部分组成:zskiplist(链表)和zskiplistNode (节点)
zskiplistNode数据结构

typedef struct zskiplistNode{   //层     struct zskiplistLevel{     //前进指针        struct zskiplistNode *forward;    //跨度        unsigned int span;    } level[];  //后退指针    struct zskiplistNode *backward;  //分值    double score;  //成员对象    robj *obj;}
  1. 层:level 数组可以包含多个元素,每个元素都包含一个指向其他节点的指针。
  2. 前进指针:用于指向表尾方向的前进指针
  3. 跨度:用于记录两个节点之间的距离
  4. 后退指针:用于从表尾向表头方向访问节点
  5. 分值和成员:跳跃表中的所有节点都按分值从小到大排序。成员对象指向一个字符串,这个字符串对象保存着一个SDS值  

zskiplist数据结构:

typedef struct zskiplist {     //表头节点和表尾节点     structz skiplistNode *header,*tail;     //表中节点数量     unsigned long length;     //表中层数最大的节点的层数     int level;}zskiplist;

4.2 总结

  • 跳跃表是有序集合的底层实现之一
  • 主要有zskiplist 和zskiplistNode两个结构组成
  • 每个跳跃表节点的层高都是1至32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的对象必须是唯一的
  • 节点按照分值的大小从大到小排序,如果分值相同,则按成员对象大小排序

5 整数集合

《Redis 设计与实现》中这样定义整数集合:“整数集合是集合建的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现。”
我们可以这样理解整数集合,他其实就是一个特殊的集合,里面存储的数据只能够是整数,并且数据量不能过大。

5.1 整数集合定义

数据结构:

typedef struct intset{    //编码方式    uint32_t enconding;   // 集合包含的元素数量    uint32_t length;    //保存元素的数组        int8_t contents[];} 

一个整数集合结构示例:

这里写图片描述

  • encoding:用于定义整数集合的编码方式
  • length:用于记录整数集合中变量的数量
  • contents:用于保存元素的数组,虽然我们在数据结构图中看到,intset将数组定义为int8_t,但实际上数组保存的元素类型取决于encoding

在上述数据结构图中我们可以看到,intset 在默认情况下会帮我们设定整数集合中的编码方式,但是当我们存入的整数不符合整数集合中的编码格式时,就需要使用到Redis 中的升级策略来解决
Intset 中升级整数集合并添加新元素共分为三步进行:

1、根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间
2、将底层数组现有的所有元素都转换成新的编码格式,重新分配空间
3、将新元素加入到底层数组中

5.2 总结

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

6 压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

6.1 压缩列表的构成

一个压缩列表构成如下:

这里写图片描述

1、zlbytes:用于记录整个压缩列表占用的内存字节数
2、zltail:记录要列表尾节点距离压缩列表的起始地址有多少字节
3、zllen:记录了压缩列表包含的节点数量。
4、entryX:要说列表包含的各个节点
5、zlend:用于标记压缩列表的末端
这里写图片描述

6.2 总结

  • 压缩列表是一种为了节约内存而开发的顺序型数据结构
  • 压缩列表被用作列表键和哈希键的底层实现之一
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值
  • 添加新节点到压缩列表,可能会引发连锁更新操作。
原创粉丝点击