redis基础数据结构源码浅析

基于redis 5.0.6

先列个表格

类型 实现
string sds
list quicklist
set intset hashtable
zset ziplist skiplist+hashtable
hash ziplist hashtable

string

redis的string(字符串)实现称为SDS(Simple Dynamic String,简单动态字符串)
相比于C字符串的优点:sds获取字符串长度的时间复杂度为 O(1) ;字符串不会溢出;减少修改字符串长度时的内存分配次数;二进制安全(可以保存各种格式的编码);兼容sting.h …

// SDS实现 --sds.h
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;            /* buf[]已使用的长度,也就是字符串长度 */
    uint8_t alloc;          /* buf[]的大小 */
    unsigned char flags;    /* 标志位,定义sdshdr类型 */
    char buf[];             /* 存储字符串的数组 */
};

画个sds的结构图:
画个sds的结构图

list

redis的list实现称为quicklist(快速列表),quicklist属于双端链表

使用quicklist的优点:保证性能的同时不会有太大的内存浪费

quicklist的节点包含了ziplist(压缩列表)。ziplist没有被定义为struct,而是char[],结构紧凑。

由于ziplist的结构紧凑,所以每新增元素都需要realloc扩展内存空间。当ziplist过大时realloc会重新分配内存空间,并进行拷贝,损耗性能。所以ziplist不适用于存储太大太多的元素。

// ziplist定义 --ziplist.c
/*
 * <zlbytes --ziplist占用字节数> <zltail --尾元素的位置> <zllen --元素个数> <entry --元素> <entry --元素> ... <entry --元素> <zlend --结束标志>
 */

ziplist节点中的prevlen指前节点的长度,能很方便地从当前节点定位到前一个节点

// ziplist节点定义 --ziplist.c
/*
 * <prevlen --前节点长度> <encoding --编码格式> <entry-data --存储数据>
 */

画个ziplist的结构图:
画个ziplist的结构图
quickList也可以看作是ziplist的链表

// quicklist定义 --quicklist.h
typedef struct quicklist {
    quicklistNode *head;        /* 头节点指针 */
    quicklistNode *tail;        /* 尾节点指针 */
    unsigned long count;        /* 元素总数(所有ziplist中的所有元素) */
    unsigned long len;          /* quicklist节点数 */
    int fill : 16;              /* 节点的填充因子 */
    unsigned int compress : 16; /* LZF算法的压缩深度; 0=off */
} quicklist;
// quicklist节点定义 --quicklist.h
typedef struct quicklistNode {
    struct quicklistNode *prev;     /* 前驱指针 */
    struct quicklistNode *next;     /* 后驱指针 */
    unsigned char *zl;              /* ziplist */
    unsigned int sz;                /* ziplist的bytes大小*/
    unsigned int count : 16;        /* ziplist中的元素数量 */
    unsigned int encoding : 2;      /* 是否进行LZF压缩 RAW==1 or LZF==2 */
    unsigned int container : 2;     /* 是否包含ziplist NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1;    /* 是否曾被压缩 */
    unsigned int attempted_compress : 1; /* 测试使用字段 */
    unsigned int extra : 10;        /* 预留内存空间 */
} quicklistNode;
// 经过LZF压缩的quicklist节点定义 --quicklist.h
typedef struct quicklistLZF {
    unsigned int sz;        /* 压缩后的char[]大小 */
    char compressed[];      /* 存储压缩后内容的数组 */
} quicklistLZF;
// 描述quicklist中的元素定义 --quicklist.h
typedef struct quicklistEntry {
    const quicklist *quicklist;     /* 元素所在的quicklist */
    quicklistNode *node;            /* 元素所在的quicklist节点 */
    unsigned char *zi;              /* 元素所在的ziplist */
    unsigned char *value;           /* 元素值 */
    long long longval;              /* 元素值 */
    unsigned int sz;                /* 元素值的bytes大小*/
    int offset;                     /* 当前元素在节点中的偏移量 */
} quicklistEntry;

画个quicklist的结构图:
画个quicklist的结构图

hash

redis的hash实现分为ziplist(压缩列表)和dict(字典)。ziplist在上文已经提到。

dict使用了hashtable哈希表的二维结构。redis数据库中的key/value组成了全局dict,包括带过期时间的key集合、zset中的value和score映射也使用了dict。

dict使用链地址法解决hash冲突,需要扩容/缩容时执行渐进式rehash。

// dict中的节点定义 --dict.h
typedef struct dictEntry {
    void *key;                  /* 节点的键 */
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;                        /* 节点的值 */
    struct dictEntry *next;     /* 指针链接到下一个节点 */
} dictEntry;
// dict具体结构定义 --dict.h
typedef struct dictht {
    dictEntry **table;                  /* 二维结构 数组+链表 */
    unsigned long size;                 /* table[]大小 */
    unsigned long sizemask;             /* table[]大小的掩码 size-1(用以计算索引值) */
    unsigned long used;                 /* 节点个数 */
} dictht;

redis在执行rehash时:

  1. 为ht[1]开辟空间,rehashidx赋值0,开始从*table[0]开始将ht[0]的数据rehash至ht[1]
  2. 随着rehashidx自增,对table[rehashidx]进行rehash。在rehash期间,对disc的写操作会同时作用于ht[0]和ht[1]
  3. ht[0]的数据全部rehash至ht[1]时,rehash完成。rehashidx赋值-1
  4. 将ht[1]赋值给ht[0],清空ht[1]
// dict定义 --dict.h
typedef struct dict {
   dictType *type;                 /* 保存私有方法的对象指针 */
   void *privdata;                 /* 私有数据 */
   dictht ht[2];                   /* 每个dict包含两个dictht,用来rehash */
   long rehashidx;                 /* 执行rehash的索引,没有rehash时为-1 */
   unsigned long iterators;        /* 运行时的迭代器数 */
} dict;

画个dict的结构图:
画个dict的结构图

set

redis的set实现分为intset(整数集合)和dict(字典)。dict在上文已经提到。当使用dict实现set时,字典的field为元素,value为NULL。

intset数据结构比较简单。contents[]存储了所有int元素,它的真正类型取决于encoding,而不是申明的int8_t。

当添加的新元素int类型位宽大于原intset中的元素时,redis会对intset升级操作,如将encoding的INTSET_ENC_INT32变为INTSET_ENC_INT64,并且需要重新分布contents[]中元素的位置。

// intset定义 --intset.h
typedef struct intset {
    uint32_t encoding;    /* contents[]的编码,决定int的位宽:16位、32位、64位 */
    uint32_t length;      /* intset元素个数 */
    int8_t contents[];    /* 存储元素的int8_t数组,可以存储16/32/64位的int */
} intset;

画个intset的结构图:
画个intset的结构图


zset

redis的zset实现分为ziplist(压缩列表)和skiplist(跳跃列表)+dict(字典)。ziplist在上文已经提到。当有zset的元素少于zset-max-ziplist-entries(默认128),同时每个元素的值都小于zset-max-ziplist-value(默认64字节)时,redis会使用ziplist。当使用ziplist实现zset时,每个元素使用两个ziplist节点保存,第一个保存member,第二个保存score。
画个zset使用ziplist的图:
画个zset使用ziplist的图

skiplist是一种简单、高效、动态、随机化算法的搜索结构。它的平均搜索时间复杂度O(lgN),最坏O(N)。关于skiplist的数据结构建议参考MIT算法导论公开课第12节。

// skiplist节点定义 --server.h
typedef struct zskiplistNode {
    sds ele;                        /* 元素的值 */
    double score;                   /* 元素的分数 */
    struct zskiplistNode *backward; /* 后退指针 */
    struct zskiplistLevel {
        struct zskiplistNode *forward;          /* 链接层与层的前驱指针 */
        unsigned long span;                     /* 路径跨度,搜索元素时累加span可得到rank排名 */
    } level[];                      /* skiplist的层 */
} zskiplistNode;
// skiplist定义 --server.h
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;        /* skiplist的头、尾节点 */
    unsigned long length;                       /* 跳跃表长度/节点数(不包括头节点)、也就是元素数量 */
    int level;                                  /* skiplist的层数 */
} zskiplist;

skiplist保证范围型查找的性能 O(lgN),dict保证成员分值查找的性能 O(1)

// skiplist实现的zset定义 --server.h
typedef struct zset {
    dict *dict;             /* dict指针 */
    zskiplist *zsl;         /* skiplist头节点指针 */
} zset;


##### 参考 * redis-5.0.6 源码 * 《Redis设计与实现》 * 《Redis深度历险 核心原理与应用实践》
posted @ 2019-10-27 13:30  傅晓芸  阅读(280)  评论(0编辑  收藏  举报