vlambda博客
学习文章列表

Redis底层数据结构

Redis本身是一个基于C语言开发的内存数据库,它包含String、Set、List、Hash、Set、ZSet等数据类型。那么它底层的数据结构是什么样的呢?简单而言,redis的数据结构包含简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表等。

简单动态字符串
Redis没有直接用C语言的字符串,而是自己构建了一种名为【简单动态字符串】(Simple Dynamic String, SDS)的抽象类型,并将SDS作为Redis的默认字符串类型表示。
  
    
    
  
struct sdshdr{ //记录buf数组中已使用字节的数量 //等于 SDS 保存字符串的长度 int len; //记录 buf 数组中未使用字节的数量 int free; //字节数组,用于保存字符串 char buf[]; }
这样做的好处是:
  • 常量复杂度获取字符串长度,直接读取len属性,而不需要通过遍历计数
  • 杜绝缓冲区溢出。C语言在strcat函数进行拼接时,如果没有分配足够长度的内存空间,就会造成缓冲区溢出。而对于SDS数据类型,在进行字符修改时,会首先根据len属性检查内存空间是否满足需求,如果不满足,会进行空间扩展。
  • 减少修改字符串的内存重新分配次数。C语言不记录字符串的长度,如果要修改字符串,必须要重新分配内存。SDS通过len和free属性的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放2种策略:
      (1)空间预分配:对字符串进行空间扩展时,扩展的内存比实际需要的多,可以减少分配次数
      (2)惰性空间释放,对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用free属性将这些字节的数量记下来,等待后续使用。
  • 二进制安全。SDS的API都是以二进制的方式来处理buf中的元素,并且SDS不是以空字符串来判断是否结束,而是以len属性表示的长度来判断字符串是否结束。
  • 兼容部分C字符串函数。

链表
Redis构建了链表的实现。
  
    
    
  
typedef struct listNode{ //前置节点 struct listNode *prev; //后置节点 struct listNode *next; //节点的值 void *value; }listNode
  
    
    
  
typedef struct list{ //表头节点 listNode *head; //表尾节点 listNode *tail; //链表所包含的节点数量 unsigned long len; //节点值复制函数 void (*free) (void *ptr); //节点值释放函数 void (*free) (void *ptr); //节点值对比函数 int (*match) (void *ptr,void *key); }list;
Redis链表特性
  • 双端:具有前置节点和后置节点的引用
  • 无环:表头节点的prev指针和表尾的next指针都指向NULL,对链表的访问都是以NULL结束
  • 带链表长度计数器:通过len属性获取链表长度
  • 多态:链表节点使用void * 指针来保存节点值,可以保存不同类型的值

字典
  
    
    
  
typedef struct dictht{ //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 //总是等于 size-1 unsigned long sizemask; //该哈希表已有节点的数量 unsigned long used;
}dictht
哈希表是由数组table组成,table中的每个元素都是指向entry结构
  
    
    
  
typedef struct dictEntry{ //键 void *key; //值 union{ void *val; uint64_tu64; int64_ts64; }v;
//指向下一个哈希表节点,形成链表 struct dictEntry *next; }dictEntry
基本上与JAVA和HashMap一致。

跳跃表
Redis中跳跃表节点定义如下:
  
    
    
  
typedef struct zskiplistNode { //层 struct zskiplistLevel{ //前进指针 struct zskiplistNode *forward; //跨度 unsigned int span; }level[];
//后退指针 struct zskiplistNode *backward; //分值 double score; //成员对象 robj *obj;
} zskiplistNode
  
    
    
  
typedef struct zskiplist{ //表头节点和表尾节点 structz skiplistNode *header, *tail; //表中节点的数量 unsigned long length; //表中层数最大的节点的层数 int level;
}zskiplist;
更多详情见文章《 》

整数集合
整数集合是Redis用于保存整数值的集合抽象数据类型。

压缩列表
压缩列表是Redis为节省内存而开发的顺序型数据结构,通常作为列表键和哈希键的底层实现之一。

总结
大多数情况下,Redis使用简单字符串SDS作为字符串的表示,相对于C语言字符串,SDS具有常数复杂度获取字符串长度,杜绝了缓存区的溢出,减少了修改字符串长度时所需的内存重分配次数,以及二进制安全能存储各种类型的文件,并且还兼容部分C函数。
通过为链表设置不同类型的特定函数,Redis链表可以保存各种不同类型的值,除了用作列表键,还在发布与订阅、慢查询、监视器等方面发挥作用(后面会介绍)。
Redis的字典底层使用哈希表实现,每个字典通常有两个哈希表,一个平时使用,另一个用于rehash时使用,使用链地址法解决哈希冲突。
跳跃表通常是有序集合的底层实现之一,表中的节点按照分值大小进行排序。
整数集合是集合键的底层实现之一,底层由数组构成,升级特性能尽可能的节省内存。
压缩列表是Redis为节省内存而开发的顺序型数据结构,通常作为列表键和哈希键的底层实现之一。
参考链接:https://www.cnblogs.com/ysocean/p/9080942.html