Post

Redis Data Structure

Redis Data Structure

Redis Data Structure

  • string
  • list
  • hash
  • set
  • zset (sorted set)
  • bitmap

string

  • sds (simple dynamic string)

源码

1
2
3
4
5
6
7
8
9
typedef char *sds;

// flag 表示使用的类型, x可选值为8,16,32,64, 依次节省空间
struct __attribute__ ((__packed__)) sdshdr_x {
    uint_x_t len; /* used */
    uint_x_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

list

  • 双向链表
  • list经过版本演变, list node不断在更新
    • adlist (val: void*) -> quicklist (val: ziplist) -> quicklist (val: listpack)

源码

adlist

1
2
3
4
5
6
7
8
9
10
11
12
// adlist
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct list {
    listNode *head; // 头指针
    listNode *tail; // 尾指针
    unsigned long len;
} list;

quicklist (val: ziplist)

1
2
3
4
5
6
7
// quicklist
typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;           // 元素类型是ziplist
    unsigned int sz;             /* ziplist size in bytes */
} quicklistNode;

listpack (val: listpack)

1
2
3
4
5
6
typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *entry;  // 元素类型是listpack
    size_t sz;             /* entry size in bytes */
} quicklistNode;

hash/set

  • dict (hash table)
  • 链地址法解决hash冲突(dictEntry **table)
  • rehash采用渐进式扩容, dictRehash一次只搬n个桶 (int dictRehash(dict *d, int n))
  • 两张hash table交替使用进行rehash
  • rehash触发条件
    • 负载因子>=1, 并且此时没有进行aof重新或rdb快照
      • d->ht[0].used >= d->ht[0].size && dict_can_resize
    • 负载因子>=5, 无论有没有进行aof重新或rdb快照, 都rehash
      • d->ht[0].used/d->ht[0].size > dict_force_resize_ratio (dict_force_resize_ratio = 5)

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;                        /* 通过union节省空间 */
    struct dictEntry *next;     /* Next entry in the same hash bucket. */
} dictEntry;

typedef struct dictht {
    dictEntry **table; // dictEntty 二维结构, 第一维表示桶, 第二维表示这个桶下挂的元素
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    dictht ht[2]; // ht[0], ht[1]交替使用, rehash时旧元素从ht[0]搬到ht[1], 新元素直接放到ht[1], rehash完成后, ht[0], ht[1] = ht[1], ht[0]
} dict;

zset

  • 跳表 + hash table
    • 核心操作由跳表完成

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

typedef struct zset {
    dict *dict;     // hash table 只是用于以常数复杂度获取元素权重
    zskiplist *zsl; // 跳表
} zset;

ziplist/listpack

  • ziplist
    • ...
  • 都是使用一块连续的内存空间来紧凑的保存数据
    • 类似于一个数组+head头信息, 但是数组中的元素不是固定类型长度, 而是根据具体的内容确定空间
  • ziplist的节点保存了前一节点的长度用于向前遍历
    • 但是这也导致了连锁更新
  • listpack的引入就是为了解决ziplist的连锁更新问题
    • listpack的节点中不保存前一节点长度, 而是只记录了当前节点长度
This post is licensed under CC BY 4.0 by the author.