redis基础数据结构源码浅析

摘要:
Ziplist未定义为struct,而是char[],结构紧凑。由于ziplist结构紧凑,每个新元素都需要realloc来扩展内存空间。当压缩列表太大时,realloc将重新分配内存空间和拷贝,这将消耗性能。所以ziplist不适合存储太多的元素。Ziplist已在上文中提及。Redis数据库中的键/值形成一个全局dict,包括带过期时间的键集合,以及zset中的值和分数映射。在重新刷新期间,对磁盘的写入操作将同时作用于ht[0]和ht[1]ht[0]的数据。当所有数据重新刷新到ht[1]时,重新刷新完成。
基于redis 5.0.6

先列个表格

类型实现
stringsds
listquicklist
setintsethashtable
zsetziplistskiplist+hashtable
hashziplisthashtable
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深度历险 核心原理与应用实践》

免责声明:文章转载自《redis基础数据结构源码浅析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇matplotlib的学习16-animation动画端口占用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Redis设置过期时间

一般添加值之后,不设置过期时间的话,ttl某个键会显示-1,表示此时并没有设置过期时间 当设置了过期时间之后,则会显示剩余过期秒数 设置过期时间的做法 #region 设置过期时间 //设置多少秒 client.Set<string>("name",...

YAML书写规则与数据结构

YAML 是一种简洁的非标记语言。YAML以数据为中心,使用空白,缩进,分行组织数据,从而使得表示更加简洁易读。 基本规则 YAML有以下基本规则:1、大小写敏感2、使用缩进表示层级关系3、禁止使用tab缩进,只能使用空格键4、缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。5、使用#表示注释6、字符串可以不用引号标注 三种数据结构 1、ma...

C/C++获取系统当前时间

C/C++获取系统当前时间   C库中与系统时间相关的函数定义在<time.h>头文件中, C++定义在<ctime>头文件中。 一、time(time_t*)函数 函数定义如下: time_t time (time_t* timer); 获取系统当前日历时间 UTC 1970-01-01 00:00:00开始的unix时间戳参数:...

K8S 上部署 Redis-cluster 三主三从 集群

介绍 Redis代表REmote DIctionary Server是一种开源的内存中数据存储,通常用作数据库,缓存或消息代理。它可以存储和操作高级数据类型,例如列表,地图,集合和排序集合。 由于Redis接受多种格式的密钥,因此可以在服务器上执行操作,从而减少了客户端的工作量。 它仅将磁盘用于持久性,而将数据库完全保存在内存中。Redis是一种流行的数据...

Redis——6.0集群安装部署

前言 redis集群化部署主要用于大型缓存架构,一般的小型架构,使用redis主从配置 + sentinel哨兵集群应付系统压力, 使用redis集群可以方便快捷地对集群进行动态扩容,动态的添加、删除节点,reshard、并带有自动故障恢复功能。 一般redis集群使用三主三从,并且尽量保证主服务器与从服务器不在同一台机器上,防止机器故障导致的集群瘫痪,每...

解析STM32的库函数

意法半导体在推出STM32微控制器之初,也同时提供了一套完整细致的固件开发包,里面包含了在STM32开发过程中所涉及到的所有底层操作。通过在程序开发中引入这样的固件开发包,可以使开发人员从复杂冗余的底层寄存器操作中解放出来,将精力专注应用程序的开发上,这便是ST推出这样一个开发包的初衷。 但这对于许多从51/AVR这类单片机的开发转到STM32平台的开发...