/images/avatar.png

redis源码阅读-压缩列表ziplist

压缩列表时列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

压缩列表的构成

压缩列表是redis为了节约内存而开发的,由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
《redis设计与实现》
空白 ziplist 示例图

area        |<---- ziplist header ---->|<-- end -->|

size          4 bytes   4 bytes 2 bytes  1 byte
            +---------+--------+-------+-----------+
component   | zlbytes | zltail | zllen | zlend     |
            |         |        |       |           |
value       |  1011   |  1010  |   0   | 1111 1111 |
            +---------+--------+-------+-----------+
                                       ^
                                       |
                               ZIPLIST_ENTRY_HEAD
                                       &
address                        ZIPLIST_ENTRY_TAIL
                                       &
                               ZIPLIST_ENTRY_END

非空 ziplist 示例图

area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|

size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
            +---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
            +---------+--------+-------+--------+--------+--------+--------+-------+
                                       ^                          ^        ^
address                                |                          |        |
                                ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                  |
                                                        ZIPLIST_ENTRY_TAIL

zlbytes记录整个压缩列表占用的字节数。

zltail记录压缩列表尾节点距离压缩列表的起始地址有多少字节。

zzlen记录压缩列表包含的节点数量。

entryX列表节点,数量不定。

zlend特殊值0xff,标记压缩列表末端。

redis源码阅读-整数集合

创建一个只包含整数值元素的集合,同时元素数量不多时,redis会使用整数集合作为键的底层实现。

整数集合的实现

1
2
3
4
5
typedef struct intset {
    uint32_t encoding; // 编码方式
    uint32_t length; // 元素数量
    int8_t contents[]; // 保存元素的数组
} intset;

encoding表示整数集合的编码模式,目前提供三种模式

1
2
3
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

redis源码阅读-跳跃表

跳跃表一种有序数据结构,每个节点维护多个指向其他节点的指针,达到快速访问节点的目的。

大部分情况下跳跃表的效率可以和平衡数媲美,实现比平衡树简单,不少程序使用跳跃表来代替平衡树。

跳跃表有时会作为有序集合的实现。以分值排序。

结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 跳跃表节点
typedef struct zskiplistNode {
    robj *obj; // 保存的对象
    double score; // 分值 跳跃表按照分值进行排序
    struct zskiplistNode *backward; // 上一节点
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 前进指针
        unsigned int span; // 跨度 记录两个节点之间的距离
    } level[]; // 层
} zskiplistNode;

// 跳跃表
typedef struct zskiplist {
    struct zskiplistNode *header, *tail; // 头、尾指针
    PORT_ULONG length; // 跳跃表长度
    int level; // 层数最大节点层数
} zskiplist;

跳跃表的创建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 创建节点
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
    // 申请内存
    zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    zn->score = score; // 赋值分数
    zn->obj = obj; // 设定成员对象
    return zn;
}

// 创建跳跃表
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;

    // 申请内存
    zsl = zmalloc(sizeof(*zsl));
    zsl->level = 1; // 设置层数初始为1
    zsl->length = 0; // 设置长度初始为0

    // 创建头节点 层数为32 分数为0
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);

    // 将每层的forward指针指向null,跨度0
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    // 设定backward指向null
    zsl->header->backward = NULL;
    zsl->tail = NULL;
    return zsl;
}

redis源码阅读-字典

Redis字典由哈希表实现的保存键值对的抽象数据结构。

实现文件在dict.h\dict.c中。

哈希表

Redis字典结构体定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
typedef struct dictEntry {
    void *key;// key 键
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;// 值,支持多种类型,使用联合。
    struct dictEntry *next;// 下一个点 采用链式来解决索引冲突问题
} dictEntry;

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);// hash函数
    void *(*keyDup)(void *privdata, const void *key);// key复制函数
    void *(*valDup)(void *privdata, const void *obj);// value复制函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);// key比较函数
    void (*keyDestructor)(void *privdata, void *key);// key释放函数
    void (*valDestructor)(void *privdata, void *obj);// value释放函数
} dictType;

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;// 指针的数组头的指针
    unsigned long size;// 大小
    unsigned long sizemask;// 大小的掩码 总是等于size-1
    unsigned long used;// 被使用的节点数
} dictht;// hash表

typedef struct dict {
    dictType *type;// 绑定的函数
    void *privdata;// 私有数据
    dictht ht[2];// hash表,一般只使用[0],在rehash的时候使用[1]
    long rehashidx; // 记录rehash的进度,不进行rehash的时候为-1
    int iterators; /* number of iterators currently running */
} dict;// 字典

/* If safe is set to 1 this is a safe iterator, that means, you can call
 * dictAdd, dictFind, and other functions against the dictionary even while
 * iterating. Otherwise it is a non safe iterator, and only dictNext()
 * should be called while iterating. */
typedef struct dictIterator {
    dict *d;
    long index;
    int table, safe;
    dictEntry *entry, *nextEntry;
    /* unsafe iterator fingerprint for misuse detection. */
    long long fingerprint;
} dictIterator;// 迭代器

读书摘录-编程大师访谈录

【美】Susan Lammers

第1篇 查尔斯•西蒙尼 2017-04-30 采访者:编程是一种技巧或技能吗? 西蒙尼:什么是编程?人们对此一直各持己见。有人说它是科学,有人说它是艺术,还有人称之为技能或手艺。我认为这三方面兼而有之。我们喜欢说它蕴含大量艺术成分,但是我们都知道它里面更多的是科学。 孩子们在学校里学习数学,高中毕业时,他们会以为数学就是加法和乘法,甚或代数和微积分。其实,算术,即使简单如加法的运算,背后也有令人难以置信的科学理论作支持。 计算机编程背后也有大量科学理论作支持。例如,哥德尔定理的数学证明冗长而复杂,但是如果借用计算机科学的图灵定理,证明起来不费吹灰之力。信息理论和计算机科学其他领域对数学影响巨大,反之亦然。 编程包含有大量科学,同时,它也有点像手艺。实际上,在许多人看来,编程是一项复杂的技能,这跟工具制造很像,需要精雕细琢。我认为,只要将科学、艺术和技能这三者拿捏得恰到好处,你就能取得一些引人瞩目的成绩。 2017-04-30 别人读几段你的源代码,有没有可能断定“这代码是查尔斯·西蒙尼写的”? 西蒙尼:噢,是的,毫无疑问。是不是我本人写的可能很难分辨,但有一点是确定无疑的:只要看了代码,你就能知道它是不是我的团队写的,或者是不是受我的影响写的。这是因为我从1972年起写的代码都遵循特定的命名规范,许多人称之为“匈牙利命名法”。你一眼就能分辨出哪些代码是受我的影响写出来的,包括Microsoft Word、Multiplan和Bravo,以及其他许多遵循这些规范写成的程序。 註: 原来是他 2017-04-30 进入公司第一天,程序员就会拿到几本书。其中一本是数学家乔治·波利亚写的《怎样解题》。(西蒙尼边说边从他办公桌旁的书柜里取出那本书,翻到某一页。)这两页很重要。这本书的其余内容就是基于这两页展开的。这就像一张问题求解的检查单。这是起飞前、起飞和着陆检查单。它不会教你如何飞行,但是如果不照做,即使你已经懂得怎么飞行,也有可能会坠机身亡。 求解问题时,我们遵循以下四个步骤:首先理解问题,然后拟定计划,接着执行计划,最后回顾整个过程。这样的书我们大概有四本,我觉得我们能使程序员比刚加入公司时变得更加优秀。

读书摘录-调试九法:软硬件错误的排查之道

【美】阿甘斯

第3章 理解系统 2017-04-07 你必须掌握系统的工作原理以及它是如何设计的。在某些情况下,还要知道为什么这样设计。如果你没有理解系统中的某个部分,那么这通常就是出问题的地方。(这不仅仅是“墨菲定律”的问题,如果你不能理解你所设计的系统,你的工作可能会变得一团糟。)

3.1 阅读手册 2017-04-07 如果你是一位工程师,正在调试自己公司的产品,那么你需要读一读内部手册。工程师们设计它是用来做什么的?读一下功能说明以及所有的设计规范,研究一下图表、时序图和状态机。分析它们的代码,还要读一下注释。(是的,读一下注释,这非常重要。)一定要检查产品的设计。查明构建它的工程师们打算用它来做什么(除了用它来赚钱买辆宝马车以外)。 注意,手册上的信息也不可全信。手册(以及那些只想着赚钱买宝马车的工程师们)可能也是错的,很多难以发现的bug就出现在这里。但你仍需要了解他们的想法,哪怕其中有些信息是很难接受的。

3.2 逐字逐句阅读整个手册 2017-04-07 参考设计和样本程序给出了产品的一种使用方式,有时这些就是能获得的全部文档了。但是,在使用这些设计时一定要注意,创建它们的人往往只了解他们的产品,而没有遵循好的设计实践,或者不是为真实应用而设计的(最常见的缺点是不能进行错误恢复)。不要照搬这些设计,如果你没有在开始的时候发现bug,那么将来也会发现。此外,即使是最好的参考设计可能也不会完全符合应用程序的特定需求,而不符合的地方可能就是出问题的地方。当我照搬了朋友的微处理器设计时,就发生了问题,因为他的设计无法处理中