Redis底层数据结构

117 阅读5分钟

底层结构

redis没有表的概念,只有db,数据都放在db中。

image.png

RedisDB结构

typedef struct redisDb { 
    int id; //id是数据库序号,为0-15(默认Redis有16个数据库) 
    long avg_ttl; //存储的数据库对象的平均ttl(time to live),用于统计 
    dict *dict; //存储数据库所有的key-value 
    dict *expires; //存储key的过期时间 
    dict *blocking_keys;//blpop 存储阻塞key和客户端对象 
    dict *ready_keys;//阻塞后push 响应阻塞客户端 存储阻塞后push的key和客户端对象 
    dict *watched_keys;//存储watch监控的的key和客户端对象 
} redisDb;
  • id:数据编号,默认是0-15(16个数据库)
  • avg_ttl:用于统计数据库中对象的平均存储时间
  • dict: 存储所有的key-value
  • expires:存储key的过期时间 (定时删除,惰性删除,主动删除,LRU)

RedisObject

具体指的就是value,value的一个对象。
结构:

typedef struct redisObject { 
    unsigned type:4;//类型 五种对象类型 
    unsigned encoding:4;//编码
    void *ptr;//指向底层实现数据结构的指针
    //... 
    int refcount;//引用计数
    //... 
    unsigned lru:LRU_BITS; //LRU_BITS为24bit 记录最后一次被命令程序访问的时间 //... 
}robj;
  • type:对象的类型,一共5种基本数据类型,type key。如:String。占4位。
  • encoding:编码方式,通过object encoding查看对象的编码方式。占4位
  • ptr:指针,指向对象的值
  • refcount:引用计数,用来记录对象被引用的次数,主要用于内存的回收(gc的引用计数)。整数。
  • LRU:前16位用于记录key的最近(最后)被访问的时间。 后8位用于计数最近被访问的次数。占24位。

7种type

String字符串类型

本质上是SDS,对原来的C语言字符串做了封装。 结构:

struct sdshdr{
    //记录buf数组中已使用字节的数量 
    int len; 
    //记录 buf 数组中未使用字节的数量 
    int free; 
    //字符数组,用于保存字符串
    char buf[];
}
  • len:已使用的字节数量
  • free:数组剩余字节数
  • buf[]:保存的字符串 好处:
  1. SDS在C字符串的基础上加了len和free字段,获取字符串长度的复杂度位O(1),原来为O(n)。
    buf数组长度为free+len+1 (最后会有一个空字符"\0")。
  2. SDS记录了已使用和未使用的字符串长度,在可能造成缓冲区溢出时会自动分配内存,杜绝了缓冲区溢出。
  3. 可以存取二进制数据,以字符串长度len来作为结束标识。
    C: \0 空字符串 二进制数据包括空字符串,所以没有办法存取二进制数据
    SDS : 非二进制 \0
扩容机制

待补充。。。。

跳跃表

sorted-set有序集合的底层实现,效率高,实现简单。
原理:有序列表进行分层处理,每一层都是一个有序列表。 借鉴图:

image.png

插入与删除

上面例子中,9个结点,一共4层,是理想的跳跃表。 通过抛硬币(概率1/2)的方式来决定新插入结点跨越的层数: 正面:插入上层 背面:不插入 达到1/2概率(计算次数)

牺牲空间,提升时间。

源码实现C
//跳跃表节点 
typedef struct zskiplistNode { 
    sds ele; /* 存储字符串类型数据 redis3.0版本中使用robj类型表示, 但是在redis4.0.1中直接使用sds类型表示 */ 
    
    double score;//存储排序的分值 
    struct zskiplistNode *backward;//后退指针,指向当前节点最底层的前一个节点
    /* 层,柔性数组,随机生成1-64的值 */
    struct zskiplistLevel { 
        struct zskiplistNode *forward; //指向本层下一个节点 
        unsigned int span;//本层下个节点到本节点的元素个数 
    } level[];
} zskiplistNode;

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

字典

散列表(hash)。。待补充

压缩列表

ziplist是由一系列特殊编码的连续内存块组成的顺序型数据结构。
压缩列表结构:

image.png entryX元素编码结构:

image.png

  • previous_entry_length:前一个元素的长度
  • encoding:编码方式
  • content:数据内容

快速列表

整数集合

一个有序的整数集合,存储整数的连续存储结构。
结构图:

image.png

流对象(不太懂)

stream主要由:消息、生产者、消费者和消息组成。
Redis Strean的底层主要使用了liststpack(紧凑列表)和Rax(基数树组成)。

image.png

10种encoding

set对象有:

  • intset:元素是64位以内的整数
  • hashtable:元素是64位以外的整数 String对象有
  • int:int类型的整数
  • raw:简单动态字符串,长度大于44字节
  • embstr:简单动态字符串,长度小于44字节 list有
  • quicklist 快速列表
在较早版本的 redis 中,list 有两种底层实现:

- 当列表对象中元素的长度比较小或者数量比较少的时候,采用压缩列表 ziplist 来存储
- 当列表对象中元素的长度比较大或者数量比较多的时候,则会转而使用双向列表 linkedlist 来存储

- 两者各有优缺点:
ziplist 的优点是内存紧凑,访问效率高,缺点是更新效率低,并且数据量较大时,可能导致大量的内存复制
linkedlist 的优点是节点修改的效率高,但是需要额外的内存开销,并且节点较多时,会产生大量的内存碎片

为了结合两者的优点,在 redis 3.2 之后,list 的底层实现变为快速列表 quicklist。
- 快速列表是 linkedlist 与 ziplist 的结合: quicklist 包含多个内存不连续的节点,但每个节点本身就是一个 ziplist。

hash 字典和压缩列表

  • dict:当散列表元素个数比较多,或元素不是小整数和短字符串时。
  • ziplis:当散列表元素比较少,且元素是小整数和短字符串时。 set 整型集合和字典
  • intset:当元素都是整数,并且在64位整数范围内。
  • dict:其他情况下。(hashtable) zset 压缩列表和跳跃表+字典
  • skiplist + dict 当元素的个数比较多或元素不是小整数或短字符串时。
  • ziplist 当散列表元素的个数比较少,且元素都是小整数或短字符串时。