Redis数据结构内部编码

总体说明

Redis的每个键值都是使用一个redisObject结构体保存的:redis

typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj;

type字段表示数据类型:数组

/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

encoding表示内部编码方式:bash

#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */

数据结构与编码方式对应:数据结构

字符串类型

redis使用sdshdr类型的变量来存储字符串,redisObject的ptr字段指向sdshdr的地址,sdshdr的定义以下:性能

struct sdshdr {
  int len;   //buf已占用的空间长度
  int free; //buf中剩余的空间长度
  char but[];  //数据 真实存储c字符串
}

好比执行set key "hello"时,内存结构以下:优化

当存储字符串时,实际占用空间就如上图所示,字符串值存储在sdshdr结构中。ui

若是值的内容能够用一个64位符号整数表示,redis将会把值转换成long,内存结构以下:编码

能够看出若是值是long,那么将不会放到sdshdr结构中。spa

另外,redis会事先创建10000个key,分别存储0到9999这些数字,若是值是0-9999,那么能够直接使用这10000个数字而不需在建立redisObject了。3d

当配置了maxmemory设置了redis的最大空间大小时,redis不会使用共享对象,由于对于每个键值都要上会用一个redisObject来记录LRU信息。

当键值内容不超过39个字节时,reids会采用EMBSTR编码,这个编码的好处是把sdshdr结构与redisObject分配到连续的内存空间:

这么作的好处是无论分配存储仍是释放内存,所须要的操做都从2次减小到1次。

当对EMBSTR编码的字符串执行修改操做时(如APPEND操做),Redis会将其转换为RAW编码。

散列类型

散列类型内部使用HT或者ZIPLIST编码。

两种数据结构的切换配置在:

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

当散列类型字段个数少于hash-max-ziplist-entries个,或者每一个字段名和字段值的长度都小于hash-max-ziplist-value个字节,Redis会用ziplist编码存储数据,不然就使用ht存储数据,转换过程是透明的。

Redis的ZIPLIST,是一种紧凑的编码格式,它牺牲了部分读取性能换区极高的空间利用率,因此当entries在较少的个数而且value在较小的长度时使用。

具体实现不在此记录了,能够搜索相关文章。

列表类型

列表类型采用LINKEDLIST和ZIPLIST两种编码。

LINKEDLIST采用双向链表,链表中的元素都是redisObject存储的,此处与字符串优化方式相同。

两种数据结构的切换配置在:

list-max-ziplist-entries 512
list-max-ziplist-value 64

转换方式和Hash同样。

其实linkedlist已经趋向于淘汰了,新版本的redis提供了QUICKLIST编码方式:

将长列表分红若干个以链表形式组织的ziplist,达到减小空间占用的同时还能够提高ziplist编码性能的效果,具体实现能够在网上搜索,此处不记录了。

集合类型

内部实现方式是HT和INTSET。

当元素都是整型且元素的个数小于配置文件中set-max-intset-entries=512个时,Redis会使用INTSET,不然使用HT。

typedef struct intset {
    uint32_t encoding;  // 编码类型 int16_t、int32_t、int64_t
    uint32_t length;    // 长度 最大长度:2^32
    int8_t contents[];  // 柔性数组
} intset;

其中centents存储的就是集合中的元素,根据encoding不一样,每一个元素占用的大小也不一样。默认是INT16(2字节),当新增长的整数没法用2字节存储时,Redis会将该集合的encoding升级到INT32(4字节),以此类推到INT64(8字节)。INTSET以有序的方式存储元素,因此可用二分法查找,但添加或删除元素都须要调整元素的内存位置,因此元素太多时性能很差,故定义了entries的个数限制。

INTSET转成HT后,不会在自动转回INTSET。

有序集合

内部实现方式是SKIPLIST和ZIPLIST。

两种数据结构的切换配置在:

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

当使用ZIPLIST时,

每一个集合元素使用两个紧挨在一块儿的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。而且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。

当使用SKIPLIST时,有序集合对象使用 zet 结构做为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表。

关于SKIPLIST,能够在网上搜索redis zset skiplist数据结构,在此处不作详细记录了。

相关文章
相关标签/搜索