《Redis 设计与实现》图片集

你好, 欢迎光临《Redis 设计与实现》图片集!

这里列出了 《Redis 设计与实现》 一书包含的绝大多数图片, 并且所有图片的源码都可以在 github.com/huangz1990/redisbook1e-gallery 项目的相应文件中找到 (源码使用 dot 语言编写,由 Graphviz 生成)。

你可以在你的博客文章、笔记甚至是演讲稿里面使用这些图片, 或者通过阅读图片的源码来学习如何使用 dot 语言和 Graphviz 。

所有图片和源码都采用 CC BY-NC 4.0 协议 进行许可, 你只要说明这些材料来源于《Redis 设计与实现》(或者添加一个指向 RedisBook.com 的链接), 就可以以非商业目的随意地使用这些材料。

如果有任何疑问, 请通过 huangz.me 上面列出的联系方式来联系我。

黄健宏(huangz)
2014.7.31

第 2 章: 简单动态字符串

没有任何未使用空间的 SDS 示例。

digraph {

    label = "\n 图 2-1    SDS 示例";

    rankdir = LR;

    node [shape = record];

    //

    sdshdr [label = "sdshdr | free \n 0 | len \n 5 | <buf> buf"];

    buf [label = "{ 'R' | 'e' | 'd' | 'i' | 's' | '\\0' }"];

    //

    sdshdr:buf -> buf;

}


带有五字节使用空间的 SDS 示例。

digraph {

    label = "\n 图 2-2    带有未使用空间的 SDS 示例";

    rankdir = LR;

    node [shape = record];

    //

    sdshdr [label = "sdshdr | free \n 5 | len \n 5 | <buf> buf"];

    buf [label = "{ 'R' | 'e' | 'd' | 'i' | 's' | '\\0' | | | | | }"];

    //

    sdshdr:buf -> buf;

}


遍历 C 字符串并获取其长度的过程。

digraph {

    rankdir = TB;

    node [shape = record];

    str [label = " <1> 'R' | <2> 'e' | <3> 'd' | <4> 'i' | <5> 's' | <6> '\\0' "];

    node [shape = plaintext];

    p1 [label = "len = 1"];

    p1 -> str:1;

}

digraph {

    rankdir = TB;

    node [shape = record];

    str [label = " <1> 'R' | <2> 'e' | <3> 'd' | <4> 'i' | <5> 's' | <6> '\\0' "];

    node [shape = plaintext];

    p2 [label = "len = 2"];

    p2 -> str:2;

}

digraph {

    rankdir = TB;

    node [shape = record];

    str [label = " <1> 'R' | <2> 'e' | <3> 'd' | <4> 'i' | <5> 's' | <6> '\\0' "];

    node [shape = plaintext];

    p3 [label = "len = 3"];

    p3 -> str:3;

}

digraph {

    rankdir = TB;

    node [shape = record];

    str [label = " <1> 'R' | <2> 'e' | <3> 'd' | <4> 'i' | <5> 's' | <6> '\\0' "];

    node [shape = plaintext];

    p4 [label = "len = 4"];

    p4 -> str:4;

}

digraph {

    rankdir = TB;

    node [shape = record];

    str [label = " <1> 'R' | <2> 'e' | <3> 'd' | <4> 'i' | <5> 's' | <6> '\\0' "];

    node [shape = plaintext];

    p5 [label = "len = 5"];

    p5 -> str:5;

}

digraph {

    label = "\n 图 2-4    计算 C 字符串长度的过程";

    rankdir = TB;

    node [shape = record];

    str [label = " <1> 'R' | <2> 'e' | <3> 'd' | <4> 'i' | <5> 's' | <6> '\\0' "];

    node [shape = plaintext];

    p6 [label = "发现空字符 \n 停止计数 \n 字符串的长度为 5 字节"];

    p6 -> str:6;

}

第 3 章:链表

由多个链表节点 listNode 组成的链表。

digraph {

    label = "\n 图 3-1    由多个 listNode 组成的双端链表"

    rankdir = LR;

    node [shape = record];

    //

    more_prev [label = "...", shape = plaintext];
    x [label = "<head> listNode | value \n ..."];
    y [label = "<head> listNode | value \n ..."];
    z [label = "<head> listNode | value \n ..."];
    more_next [label = "...", shape = plaintext];

    //

    more_prev -> x [label = "next"];
    x -> more_prev [label = "prev"];


    x -> y [label = "next"];
    y -> x [label = "prev"];

    y -> z [label = "next"];
    z -> y [label = "prev"];

    z -> more_next [label = "next"];
    more_next -> z [label = "prev"];
}


list 结构和 listNode 节点组成的链表。

digraph {

    label = "\n 图 3-2    由 list 结构和 listNode 结构组成的链表"

    rankdir = LR;

    node [shape = record];

    //

    list [label = "list | <head> head | <tail> tail | <len> len \n 3 | <dup> dup | <free> free | <match> match ", width = 2.0];

    more_prev [label = "NULL", shape = plaintext];
    x [label = "<head> listNode | value \n ..."];
    y [label = "<head> listNode | value \n ..."];
    z [label = "<head> listNode | value \n ..."];
    more_next [label = "NULL", shape = plaintext];

    dup [label = "...", shape = plaintext];
    free [label = "...", shape = plaintext];
    match [label = "...", shape = plaintext];

    //

    list:head -> x;
    list:tail -> z;

    list:dup -> dup;
    list:free -> free;
    list:match -> match;

    x -> y;
    y -> x;

    y -> z;
    z -> y;

    //

    more_prev -> x [dir = back];
    z -> more_next;

}

第 4 章:字典

一个空的哈希表。

digraph {

    label = "\n 图 4-1    一个空的哈希表";

    rankdir = LR;

    //

    node [shape = record];

    dictht [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 0"];

    table [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    //

    node [shape = plaintext, label = "NULL"];

    null0;
    null1;
    null2;
    null3;

    //

    dictht:table -> table:head;

    table:0 -> null0;
    table:1 -> null1;
    table:2 -> null2;
    table:3 -> null3;

}


使用链地址法解决冲突的哈希表。

digraph {

    label = "\n 图 4-2    连接在一起的键 k1 和键 k0";

    rankdir = LR;

    //

    node [shape = record];

    dictht [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 2"];

    table [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    dictEntry0 [label = " <head> dictEntry | { k0 | v0 }"];
    dictEntry1 [label = " <head> dictEntry | { k1 | v1 }"];

    //

    node [shape = plaintext, label = "NULL"];

    null0;
    null1;
    null2;
    null3;

    //

    dictht:table -> table:head;

    table:0 -> null0;
    table:1 -> null1;
    table:2 -> dictEntry1;
    dictEntry1 -> dictEntry0 -> null2 [label = "next"];
    table:3 -> null3;

}


普通状态下(未在进行 rehash)的字典。

digraph {

    label = "\n 图 4-3    普通状态下的字典";

    rankdir = LR;

    //

    node [shape = record];

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 2"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 0 | <sizemask> sizemask \n 0 | <used> used \n 0"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];
    table1 [label = "NULL", shape = plaintext];

    dictEntry0 [label = " <head> dictEntry | { k0 | v0 }"];
    dictEntry1 [label = " <head> dictEntry | { k1 | v1 }"];

    //

    node [shape = plaintext, label = "NULL"];

    null0;
    null1;
    null2;
    null3;

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1;

    table0:0 -> null0;
    table0:1 -> dictEntry0:head -> null1;
    table0:2 -> null2;
    table0:3 -> dictEntry1:head -> null3;
}


包含了键值对的字典。

digraph {

    label = "\n 图 4-5    添加键值对 k0 和 v0 之后的字典";

    rankdir = LR;

    //

    node [shape = record];

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 1"];

    dictht1 [label = "...", shape = plaintext];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];
    //table1 [label = "NULL", shape = plaintext];

    dictEntry [label = " <head> dictEntry | { k0 | v0 } "];

    //

    node [shape = plaintext, label = "NULL"];

    null0;
    null1;
    null2;
    null3;

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    //dictht1:table -> table1;

    table0:0 -> dictEntry:head -> null0;
    table0:1 -> null1;
    table0:2 -> null2;
    table0:3 -> null3;

}


字典的 rehash 过程。

digraph {

    label = "\n 图 4-8    执行 rehash 之前的字典";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 4"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 0 | <sizemask> sizemask \n 0 | <used> used \n 0"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = "NULL", shape = plaintext];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    null0;
    null1;
    null2;
    null3;

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1;

    table0:0 -> kv2:head -> null0;
    table0:1 -> kv0:head -> null1;
    table0:2 -> kv3:head -> null2;
    table0:3 -> kv1:head -> null3;

}

digraph {

    label = "\n 图 4-9    为字典的 ht[1] 哈希表分配空间";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 4"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 0"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | <0> 0 | <1> 1 | <2> 2 | ... | <7> 7 "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> kv2:head -> null0;
    table0:1 -> kv0:head -> null1;
    table0:2 -> kv3:head -> null2;
    table0:3 -> kv1:head -> null3;

    table1:0 -> null10;
    table1:1 -> null11;
    table1:2 -> null12;
    table1:7 -> null17;

}

digraph {

    label = "\n 图 4-10    ht[0] 的所有键值对都已经被迁移到 ht[1]";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 0"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 4"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... | <7> 7 "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> null0;
    table0:1 -> null1;
    table0:2 -> null2;
    table0:3 -> null3;

    table1:1 -> kv3:head -> null11;
    table1:4 -> kv2:head -> null14;
    table1:5 -> kv0:head -> null15;
    table1:7 -> kv1:head -> null17;

}

digraph {

    label = "\n 图 4-11    完成 rehash 之后的字典";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 4"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 0 | <sizemask> sizemask \n 0 | <used> used \n 0"];

    table0 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... | <7> 7 "];

    table1 [label = "NULL", shape = plaintext];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1;

    table0:1 -> kv3:head -> null11;
    table0:4 -> kv2:head -> null14;
    table0:5 -> kv0:head -> null15;
    table0:7 -> kv1:head -> null17;

}


字典的渐进式 rehash 过程。

digraph {

    label = "\n 图 4-12    准备开始 rehash";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 4"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 0"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | <0> 0 | <1> 1 | <2> 2 | ... | <7> 7 "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> kv2:head -> null0;
    table0:1 -> kv0:head -> null1;
    table0:2 -> kv3:head -> null2;
    table0:3 -> kv1:head -> null3;

    table1:0 -> null10;
    table1:1 -> null11;
    table1:2 -> null12;
    table1:7 -> null17;

}

digraph {

    label = "\n 图 4-13    rehash 索引 0 上的键值对";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 0 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 3"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 1"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | ... | <4> 4 | ... "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> null0;
    table0:1 -> kv0:head -> null1;
    table0:2 -> kv3:head -> null2;
    table0:3 -> kv1:head -> null3;

    table1:4 -> kv2:head -> null14

}

digraph {

    label = "\n 图 4-14    rehash 索引 1 上的键值对";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 2"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 2"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | ... | <4> 4 | <5> 5 | ... "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> null0;
    table0:1 -> null1;
    table0:2 -> kv3:head -> null2;
    table0:3 -> kv1:head -> null3;

    table1:4 -> kv2:head -> null14
    table1:5 -> kv0:head -> null15;

}

digraph {

    label = "\n 图 4-15    rehash 索引 2 上的键值对";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 2 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 1"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 3"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> null0;
    table0:1 -> null1;
    table0:2 -> null2;
    table0:3 -> kv1:head -> null3;

    table1:1 -> kv3:head -> null11;
    table1:4 -> kv2:head -> null14
    table1:5 -> kv0:head -> null15;

}

digraph {

    label = "\n 图 4-16    rehash 索引 3 上的键值对";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 3 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 0"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 4"];

    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];

    table1 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... | <7> 7 "];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1:head;

    table0:0 -> null0;
    table0:1 -> null1;
    table0:2 -> null2;
    table0:3 -> null3;

    table1:1 -> kv3:head -> null11;
    table1:4 -> kv2:head -> null14
    table1:5 -> kv0:head -> null15;
    table1:7 -> kv1:head -> null17;

}

digraph {

    label = "\n 图 4-17    rehash 执行完毕";

    rankdir = LR;

    node [shape = record];

    // 字典

    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];

    // 哈希表

    dictht0 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 4"];

    dictht1 [label = " <head> dictht | <table> table | <size> size \n 0 | <sizemask> sizemask \n 0 | <used> used \n 0"];

    table0 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... | <7> 7 "];

    table1 [label = "NULL", shape = plaintext];

    // 哈希表节点

    kv0 [label = " <head> dictEntry | { k0 | v0 } "];
    kv1 [label = " <head> dictEntry | { k1 | v1 } "];
    kv2 [label = " <head> dictEntry | { k2 | v2 } "];
    kv3 [label = " <head> dictEntry | { k3 | v3 } "];

    //

    node [shape = plaintext, label = "NULL"];

    //

    dict:ht -> dictht0:head [label = "ht[0]"];
    dict:ht -> dictht1:head [label = "ht[1]"];

    dictht0:table -> table0:head;
    dictht1:table -> table1;

    table0:1 -> kv3:head -> null11;
    table0:4 -> kv2:head -> null14;
    table0:5 -> kv0:head -> null15;
    table0:7 -> kv1:head -> null17;

}

第 5 章:跳跃表

一个跳跃表示例。

digraph {

    rankdir = LR;

    node [shape = record, width = "0.5"];

    //

    l [label = " <header> header | <tail> tail | level \n 5 | length \n 3 "];

    subgraph cluster_nodes {

        style = invisible;

        header [label = " <l32> L32 | ... | <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 "];

        bw_null [label = "NULL", shape = plaintext];

        level_null [label = "NULL", shape = plaintext];

        A [label = " <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 1.0 | o1 "];

        B [label = " <l2> L2 | <l1> L1 | <backward> BW | 2.0 | o2 "];

        C [label = " <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 3.0 | o3 "];

    }

    subgraph cluster_nulls {

        style = invisible;

        n1 [label = "NULL", shape = plaintext];
        n2 [label = "NULL", shape = plaintext];
        n3 [label = "NULL", shape = plaintext];
        n4 [label = "NULL", shape = plaintext];
        n5 [label = "NULL", shape = plaintext];

    }

    //

    l:header -> header;
    l:tail -> C;

    header:l32 -> level_null [label = "0"];
    header:l5 -> C:l5 [label = "3"];
    header:l4 -> A:l4 [label = "1"];
    header:l3 -> A:l3 [label = "1"];
    header:l2 -> A:l2 [label = "1"];
    header:l1 -> A:l1 [label = "1"];

    A:l4 -> C:l4 [label = "2"];
    A:l3 -> C:l3 [label = "2"];
    A:l2 -> B:l2 [label = "1"];
    A:l1 -> B:l1 [label = "1"];

    B:l2 -> C:l2 [label = "1"];
    B:l1 -> C:l1 [label = "1"];

    C:l5 -> n5 [label = "0"];
    C:l4 -> n4 [label = "0"];
    C:l3 -> n3 [label = "0"];
    C:l2 -> n2 [label = "0"];
    C:l1 -> n1 [label = "0"];

    bw_null -> A:backward -> B:backward -> C:backward [dir = back];

    label = "\n 图 5-1    一个跳跃表";
}


从跳跃表的表头向表尾进行遍历。

digraph {


    rankdir = LR;

    node [shape = record, width = "0.5"];

    //

    l [label = " <header> header | <tail> tail | level \n 5 | length \n 3 "];

    subgraph cluster_nodes {

        style = invisible;

        header [label = " <l32> L32 | ... | <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 "];

        bw_null [label = "NULL", shape = plaintext];

        level_null [label = "NULL", shape = plaintext];

        A [label = " <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 1.0 | o1 "];

        B [label = " <l2> L2 | <l1> L1 | <backward> BW | 2.0 | o2 "];

        C [label = " <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 3.0 | o3 "];

    }

    subgraph cluster_nulls {

        style = invisible;

        n1 [label = "NULL", shape = plaintext];
        n2 [label = "NULL", shape = plaintext];
        n3 [label = "NULL", shape = plaintext];
        n4 [label = "NULL", shape = plaintext];
        n5 [label = "NULL", shape = plaintext];

    }

    //

    l:header -> header [style = dashed];
    l:tail -> C;

    header:l32 -> level_null [label = "0"];
    header:l5 -> C:l5 [label = "3"];
    header:l4 -> A:l4 [label = "1", style = dashed];
    header:l3 -> A:l3 [label = "1"];
    header:l2 -> A:l2 [label = "1"];
    header:l1 -> A:l1 [label = "1"];

    A:l4 -> C:l4 [label = "2"];
    A:l3 -> C:l3 [label = "2"];
    A:l2 -> B:l2 [label = "1", style = dashed];
    A:l1 -> B:l1 [label = "1"];

    B:l2 -> C:l2 [label = "1", style = dashed];
    B:l1 -> C:l1 [label = "1"];

    C:l5 -> n5 [label = "0"];
    C:l4 -> n4 [label = "0"];
    C:l3 -> n3 [label = "0"];
    C:l2 -> n2 [label = "0", style = dashed];
    C:l1 -> n1 [label = "0"];

    bw_null -> A:backward -> B:backward -> C:backward [dir = back];


    label = "\n 图 5-3    遍历整个跳跃表";
}


从跳跃表的表尾向表头进行遍历。

digraph {

    rankdir = LR;

    node [shape = record, width = "0.5"];

    //

    l [label = " <header> header | <tail> tail | level \n 5 | length \n 3 "];

    subgraph cluster_nodes {

        style = invisible;

        header [label = " <l32> L32 | ... | <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 "];

        bw_null [label = "NULL", shape = plaintext];

        level_null [label = "NULL", shape = plaintext];

        A [label = " <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 1.0 | o1 "];

        B [label = " <l2> L2 | <l1> L1 | <backward> BW | 2.0 | o2 "];

        C [label = " <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 3.0 | o3 "];

    }

    subgraph cluster_nulls {

        style = invisible;

        n1 [label = "NULL", shape = plaintext];
        n2 [label = "NULL", shape = plaintext];
        n3 [label = "NULL", shape = plaintext];
        n4 [label = "NULL", shape = plaintext];
        n5 [label = "NULL", shape = plaintext];

    }

    //

    l:header -> header;
    l:tail -> C [style = dashed];

    header:l32 -> level_null [label = "0"];
    header:l5 -> C:l5 [label = "3"];
    header:l4 -> A:l4 [label = "1"];
    header:l3 -> A:l3 [label = "1"];
    header:l2 -> A:l2 [label = "1"];
    header:l1 -> A:l1 [label = "1"];

    A:l4 -> C:l4 [label = "2"];
    A:l3 -> C:l3 [label = "2"];
    A:l2 -> B:l2 [label = "1"];
    A:l1 -> B:l1 [label = "1"];

    B:l2 -> C:l2 [label = "1"];
    B:l1 -> C:l1 [label = "1"];

    C:l5 -> n5 [label = "0"];
    C:l4 -> n4 [label = "0"];
    C:l3 -> n3 [label = "0"];
    C:l2 -> n2 [label = "0"];
    C:l1 -> n1 [label = "0"];

    bw_null -> A:backward -> B:backward -> C:backward [dir = back, style = dashed];

    label = "\n 图 5-6    从表尾向表头方向遍历跳跃表";
}

第 6 章:整数集合

一个整数集合示例,包含五个 int16_t 类型的整数值。

digraph {

    label = "\n 图 6-1    一个包含五个 int16_t 类型整数值的整数集合";

    rankdir = LR;

    node [shape = record];

    intset [label = " intset | encoding \n INTSET_ENC_INT16 | length \n 5 | <contents> contents "];

    //contents [label = " { { 0 位至 15 位 | <arrow> -6370 } | { 16 位至 31 位 | -5 } | { 32 位至 47 位 | 18 } | { 48 至 63 位 | 233 } | { 64 位至 79 位 | 14632 } } "];

    //intset:contents -> contents:arrow;

    contents [label = " { -6370 | -5 | 18 | 233 | 14632 } "];
    intset:contents -> contents;
}


另一个整数集合示例,包含四个 int64_t 类型的整数值。

digraph {

    label = "\n 图 6-2    一个包含四个 int64_t 类型整数值的整数集合";

    rankdir = LR;

    node [shape = record];

    intset [label = " intset | encoding \n INTSET_ENC_INT64 | length \n 4 | <contents> contents "];

    contents [label = " { -2675256175807981027 | 1 | 3 | 5 } "];

    intset:contents -> contents:arrow;

}


当向一个包含三个 int16_t 类型的值的整数集合添加一个 int32_t 类型的值时, 整数集合的扩展和转换过程。

digraph {

    label = "\n 图 6-4    contents 数组的各个元素,以及它们所在的位";

    node [shape = record];

    contents [label = " { 位 | 元素 } | { 0 至 15 位 | 1 } | { 16 至 31 位 | 2 } | { 32 至 47 位 | 3 } "];

}

digraph {

    label = "\n 图 6-5    进行空间重分配之后的数组";

    node [shape = record];

    contents [label = " { 位 | 元素 } | { 0 至 15 位 | 1 } | { 16 至 31 位 | 2 } | { 32 至 47 位 | 3 } | { 48 至 127 位 | (新分配空间)} "];

}

digraph {

    label = "\n 图 6-6    对元素 3 进行类型转换,并保存在适当的位上";

    node [shape = record];

    contents [label = " { 位 | 元素 } | { 0 至 15 位 | 1 } | { 16 至 31 位 | 2 } | { 32 至 47 位 | <old> 3 } | { 48 至 63 位 | (新分配空间) } | { 64 位至 95 位 | <new> 3 } | { 96 位至 127 位 | (新分配空间)} "];

    contents:old -> contents:new [label = "从 int16_t 类型转换为 int32_t 类型"];

}

digraph {

    label = "\n 图 6-7    对元素 2 进行类型转换,并保存在适当的位上";

    node [shape = record];

    contents [label = " { 位 | 元素 } | { 0 至 15 位 | 1 } | { 16 至 31 位 | <old> 2 } | { 32 至 63 位 | <new> 2 } | { 64 位至 95 位 | 3 } | { 96 位至 127 位 | (新分配空间)} "];

    contents:old -> contents:new [label = "从 int16_t 类型转换为 int32_t 类型"];

}

digraph {

    label = "\n 图 6-8    对元素 1 进行类型转换,并保存在适当的位上";

    node [shape = record];

    contents [label = " { 位 | 元素 } | { 0 至 31 位 | <old> 1 } | { 32 至 63 位 | 2 } | { 64 位至 95 位 | 3 } | { 96 位至 127 位 | (新分配空间)} "];

    //contents:old -> contents:new [label = "从 int16_t 类型转换为 int32_t 类型"];
    contents:old -> contents:old [label = "从 int16_t 类型转换为 int32_t 类型"];

}

digraph {

    label = "\n 图 6-9    添加 65535 到数组";

    rankdir = BT;

    node [shape = record];

    set [label = "添加新元素", shape = plaintext];

    contents [label = " { 位 | 元素 } | { 0 至 31 位 | 1 } | { 32 至 63 位 | 2 } | { 64 位至 95 位 | 3 } | { 96 位至 127 位 | <new> 65535 } "];


    set -> contents:new;

}

第 7 章:压缩列表

压缩列表的各个部分

digraph {

    label = "\n 图 7-1    压缩列表的各个组成部分";

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend "];

}


一个包含三个节点的压缩列表示例。

digraph {

    rankdir = BT;

    label = "\n 图 7-2    包含三个节点的压缩列表";

    node [shape = record];

    ziplist [label = " <zlbytes> zlbytes \n 0x50 | zltail \n 0x3c | zllen \n 0x3 | entry1 | entry2 | <entry3> entry3 | zlend \n 0xFF "];

    node [shape = plaintext];

    p [label = "p"];

    p -> ziplist:zlbytes;

    tail [label = "p + 60"];

    tail -> ziplist:entry3;

}


一个包含五个节点的压缩列表示例。

digraph {

    label = "\n 图 7-3    包含五个节点的压缩列表";

    rankdir = BT;

    node [shape = record];

    ziplist [label = " <zlbytes> zlbytes \n 0xd2 | zltail \n 0xb3 | zllen \n 0x5 | entry1 | entry2 | entry3 | entry4 | <entry5> entry5 | zlend \n 0xFF "];

    node [shape = plaintext];

    p [label = "p"];

    p -> ziplist:zlbytes;

    tail [label = "p + 179"];

    tail -> ziplist:entry5;

}


压缩列表节点的各个组成部分。

digraph {

    label = "\n 图 7-4    压缩列表节点的各个组成部分";

    node [shape = record];

    n [label = " previous_entry_length | encoding | content "];

}


压缩列表节点的 previous_entry_length 属性示例。

digraph {

    label = "\n 图 7-5    当前节点的前一节点的长度为 5 字节";

    node [shape = record];

    n [label = " previous_entry_length \n 0x05 | encoding \n ... | content \n ... "];

}


使用压缩列表节点的 previous_entry_length 属性遍历整个压缩列表的过程。

digraph {

    rankdir = BT;

    node [shape = record];

    entry1 [label = " zlbytes | zltail | zllen | <e1> entry1 | <e2> entry2 | <e3> entry3 | <e4> entry4 | zlend "];

    node [shape = plaintext];

    p1 -> entry1:e4;

}

digraph {

    rankdir = BT;

    node [shape = record];

    entry2 [label = " zlbytes | zltail | zllen | <e1> entry1 | <e2> entry2 | <e3> entry3 | <e4> entry4 | zlend "];

    node [shape = plaintext];

    p2 [label = "p2 = p1 - entry4.previous_entry_length"];
    p2 -> entry2:e3;

}

digraph {

    rankdir = BT;

    node [shape = record];

    entry3 [label = " zlbytes | zltail | zllen | <e1> entry1 | <e2> entry2 | <e3> entry3 | <e4> entry4 | zlend "];

    node [shape = plaintext];

    p3 [label = "p3 = p2 - entry3.previous_entry_length"];
    p3 -> entry3:e2;

}

digraph {

    label = "\n 图 7-8    一个从表尾向表头遍历的例子";

    rankdir = BT;

    node [shape = record];

    entry4 [label = " zlbytes | zltail | zllen | <e1> entry1 | <e2> entry2 | <e3> entry3 | <e4> entry4 | zlend "];

    node [shape = plaintext];

    p4 [label = "p4 = p3 - entry2.previous_entry_length"];
    p4 -> entry4:e1;

}


一个保存着 "hello world" 字符串的压缩列表节点示例。

digraph {

    label = "\n 图 7-9    保存着字节数组 \"hello world\" 的节点";

    node [shape = record];

    entry [label = " previous_entry_length \n ... | encoding \n 00001011 | content \n \"hello world\" "];

}


一个保存着整数 10086 的压缩列表节点示例。

digraph {

    label = "\n 图 7-10    保存着整数值 10086 的节点";

    node [shape = record];

    entry [label = " previous_entry_length \n ... | encoding \n 11000000 | content \n 10086 "];

}


向压缩列表添加新节点并引发连锁更新的过程。

digraph {

    label = "\n 图 7-11    包含节点 e1 至 eN 的压缩列表";

    node [shape = record];

    ziplist [label = " zlbytes| zltail | zllen | e1 | e2 | e3 | ... | eN | zlend "];

}

digraph {

    label = "\n 图 7-12    添加新节点到压缩列表";

    rankdir = BT;

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <new> new | e1 | e2 | e3 | ... | eN | zlend "];

    p [label = "添加新节点", shape = plaintext];

    p -> ziplist:new;

}

digraph {

    rankdir = BT;

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <new> new | <e1> e1 | <e2> e2 | <e3> e3 | ... | <en> eN | zlend "];

    p [label = "扩展 e1 \n并引发对 e2 的扩展", shape = plaintext];

    p -> ziplist:e1;

}

digraph {

    rankdir = BT;

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <new> new | <e1> e1 | <e2> e2 | <e3> e3 | ... | <en> eN | zlend "];

    p [label = "扩展 e2 \n并引发对 e3 的扩展", shape = plaintext];

    p -> ziplist:e2;

}

digraph {

    rankdir = BT;

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <new> new | <e1> e1 | <e2> e2 | <e3> e3 | ... | <en> eN | zlend "];

    p [label = "扩展 e3 \n并引发对 e4 的扩展", shape = plaintext];

    p -> ziplist:e3;

}

digraph {

    rankdir = BT;

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <new> new | <e1> e1 | <e2> e2 | <e3> e3 | <more> ... | <en> eN | zlend "];

    p [label = "一直扩展下去……", shape = plaintext];

    p -> ziplist:more;

}

digraph {

    label = "\n 图 7-13    连锁更新过程";

    rankdir = BT;

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <new> new | <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 | ... | <eN> eN | zlend "];

    p [label = "为 eN-1 扩展 eN 的 previous_entry_length 属性 \n 连锁更新到此结束", shape = plaintext];

    p -> ziplist:eN;

}

第 8 章:对象

字符串对象

int 编码的字符串对象

digraph {

    label = "\n 图 8-1    int 编码的字符串对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | ... "];

    node [shape = plaintext];

    number [label = "10086"]

    redisObject:ptr -> number;

}


raw 编码的字符串对象。

digraph {

    label = "\n 图 8-2    raw 编码的字符串对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_RAW | <ptr> ptr | ... "];

    sdshdr [label = " <head> sdshdr | free \n 0 | len \n 37 | <buf> buf"];

    buf [label = " { 'L' | 'o' | 'n' | 'g' | ... | 'k' | 'i' | 'n' | 'g' | ' ' | '.' | '.' | '.' | '\\0' } " ];

    //

    redisObject:ptr -> sdshdr:head;
    sdshdr:buf -> buf;

}


embstr 编码的字符串对象。

digraph {

    label = "\n 图 8-4    embstr 编码的字符串对象";

    node [shape = record];

    embstr [ label = " { redisObject | { type \n REDIS_STRING | encoding \n REDIS_ENCODING_EMBSTR | <ptr> ptr | ... } } |  { sdshdr | { free \n 0 | len \n 5 | { buf | { <buf> 'h' | 'e' | 'l' | 'l' | 'o' | '\\0'}} }} " ];

    embstr:ptr -> embstr:buf;

}

列表对象

ziplist 编码的列表对象。

digraph {

    label = "\n 图 8-5    ziplist 编码的 numbers 列表对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_LIST | encoding \n REDIS_ENCODING_ZIPLIST | <ptr> ptr | ... "];

    ziplist [label = " { zlbytes | zltail | zllen | 1 | \"three\" | 5 | zlend } "];

    redisObject:ptr -> ziplist;

}


linkedlist 编码的列表对象。

digraph {

    label = "\n 图 8-6    linkedlist 编码的 numbers 列表对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_LIST | encoding \n REDIS_ENCODING_LINKEDLIST | <ptr> ptr | ... "];

    subgraph cluster_linked_list {

        label = "链表";

        style = dashed;

        node1 [label = "StringObject \n 1 "];
        node2 [label = "StringObject \n \"three\""];
        node3 [label = "StringObject \n 5 "];

        node1 -> node2 -> node3;

    }

    redisObject:ptr -> node1;

}

哈希对象

ziplist 编码的哈希对象。

digraph {

    label = "\n 图 8-9    ziplist 编码的 profile 哈希对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_HASH | encoding \n REDIS_ENCODING_ZIPLIST | <ptr> ptr | ... "];

    ziplist [label = " 压缩列表 ", width = 4.0];

    redisObject:ptr -> ziplist;

}

digraph {

    label = "\n 图 8-10    profile 哈希对象的压缩列表底层实现";

    //

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <key1> \"name\" | <value1> \"Tom\" | <key2> \"age\" | <value2> 25 | <key3> \"career\" | <value3> \"Programmer\" | zlend "];

    node [shape = plaintext];

    edge [style = dashed];

    kv1 [label = "第一个添加的键值对"];
    kv1 -> ziplist:key1 [label = "键"];
    kv1 -> ziplist:value1 [label = "值"];

    kv2 [label = "第二个添加的键值对"];
    kv2 -> ziplist:key2;
    kv2 -> ziplist:value2;

    kvN [label = "最新添加的键值对"];
    kvN -> ziplist:key3;
    kvN -> ziplist:value3;

}


hashtable 编码的哈希对象。

digraph {

    label = "\n 图 8-11    hashtable 编码的 profile 哈希对象";

    rankdir = LR;

    //

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_HASH | encoding \n REDIS_ENCODING_HT | <ptr> ptr | ... "];

    dict [label = " <head> dict | <key1> StringObject \n \"age\" | <key2> StringObject \n \"career\" | <key3> StringObject \n \"name\" ", width = 1.5];

    age_value [label = "StringObject \n 25"];
    career_value [label = "StringObject \n \"Programmer\""];
    name_value [label = "StringObject \n \"Tom\""];

    //

    redisObject:ptr -> dict:head;

    dict:key1 -> age_value;
    dict:key2 -> career_value;
    dict:key3 -> name_value;

}

集合对象

intset 编码的集合对象。

digraph {

    label = "\n 图 8-12    intset 编码的 numbers 集合对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_SET | encoding \n REDIS_ENCODING_INTSET | <ptr> ptr | ... "];
    intset [label = " <head> intset | encoding \n INTSET_ENC_INT16 | length \n 3 | <contents> contents "];

    contents [label = " { 1 | 3 | 5 } "];

    redisObject:ptr -> intset:head;
    intset:contents -> contents;

}


hashtable 编码的集合对象。

digraph {

    label = "\n 图 8-13    hashtable 编码的 fruits 集合对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_SET | encoding \n REDIS_ENCODING_HT | <ptr> ptr | ... "];

    dict [label = " <head> dict | <cherry> StringObject \n \"cherry\" | <apple> StringObject \n \"apple\" | <banana> StringObject \n \"banana\" ", width = 1.5];

    redisObject:ptr -> dict:head;

    node [shape = plaintext, label = "NULL"];

    dict:apple -> nullX;
    dict:banana -> nullY;
    dict:cherry -> nullZ;

}

有序集合对象

ziplist 编码的有序集合对象。

digraph {

    label = "\n 图 8-14    ziplist 编码的有序集合对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_ZSET | encoding \n REDIS_ENCODING_ZIPLIST | <ptr> ptr | ... "];

    ziplist [label = "压缩列表", width = 4.0];

    redisObject:ptr -> ziplist;

}

digraph {

    label = "\n 图 8-15    有序集合元素在压缩列表中按分值从小到大排列";

    //

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | <banana> \"banana\" | <banana_price> 5.0 | <cherry> \"cherry\" | <cherry_price> 6.0 | <apple> \"apple\" | <apple_price> 8.5 | zlend  "];

    node [shape = plaintext];

    banana [label = "分值最少的元素"];
    cherry [label = "分值排第二的元素"];
    apple [label = "分值最大的元素"];

    //

    banana -> ziplist:banana [label = "成员"];
    banana -> ziplist:banana_price [label = "分值"];

    cherry -> ziplist:cherry;
    cherry -> ziplist:cherry_price;

    apple -> ziplist:apple;
    apple -> ziplist:apple_price;

}


skiplist 编码的有序集合对象。

digraph {

    label = "\n 图 8-16    skiplist 编码的有序集合对象";

    rankdir = LR;

    node [shape = record];

    redisObject [label = " redisObject | type \n REDIS_ZSET | encoding \n REDIS_ENCODING_SKIPLIST | <ptr> ptr | ... "];

    zset [label = " <head> zset | <dict> dict | <zsl> zsl "];

    node [shape = plaintext];

    dict [label = "..."];

    zsl [label = "..."];

    redisObject:ptr -> zset:head;
    zset:dict -> dict;
    zset:zsl -> zsl;

}

digraph {

    rankdir = LR;

    //

    node [shape = record];

    zset [label = " <head> zset | <dict> dict | <zsl> zsl "];

    dict [label = " <head> dict | ... | <ht0> ht[0] | ... "];

    ht0 [label = " <head> dictht | ... | <table> table | ... "];

    table [label = " <banana> StringObject \n \"banana\" | <apple> StringObject \n \"apple\" | <cherry> StringObject \n \"cherry\" "];

    node [shape = plaintext];

    apple_price [label = "8.5"];
    banana_price [label = "5.0"];
    cherry_price [label = "6.0"];

    //

    zset:dict -> dict:head;
    dict:ht0 -> ht0:head;
    ht0:table -> table:head;

    table:apple -> apple_price;
    table:banana -> banana_price;
    table:cherry -> cherry_price;

    //

    node [shape = record, width = "0.5"];

    //

    l [label = " <header> header | <tail> tail | level \n 5 | length \n 3 "];

    subgraph cluster_nodes {

        style = invisible;

        header [label = " <l32> L32 | ... | <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 "];

        bw_null [label = "NULL", shape = plaintext];

        level_null [label = "NULL", shape = plaintext];

        A [label = " <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 5.0 | StringObject \n \"banana\" "];

        B [label = " <l2> L2 | <l1> L1 | <backward> BW | 6.0 | StringObject \n \"cherry\" "];

        C [label = " <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 8.5 | StringObject \n \"apple\" "];

    }

    subgraph cluster_nulls {

        style = invisible;

        n1 [label = "NULL", shape = plaintext];
        n2 [label = "NULL", shape = plaintext];
        n3 [label = "NULL", shape = plaintext];
        n4 [label = "NULL", shape = plaintext];
        n5 [label = "NULL", shape = plaintext];

    }

    //

    l:header -> header;
    l:tail -> C;

    header:l32 -> level_null;
    header:l5 -> C:l5;
    header:l4 -> A:l4;
    header:l3 -> A:l3;
    header:l2 -> A:l2;
    header:l1 -> A:l1;

    A:l4 -> C:l4;
    A:l3 -> C:l3;
    A:l2 -> B:l2;
    A:l1 -> B:l1;

    B:l2 -> C:l2;
    B:l1 -> C:l1;

    C:l5 -> n5;
    C:l4 -> n4;
    C:l3 -> n3;
    C:l2 -> n2;
    C:l1 -> n1;

    bw_null -> A:backward -> B:backward -> C:backward [dir = back];

    zset:zsl -> l:header;

    // HACK: 放在开头的话 NULL 指针的长度会有异样
    label = "\n 图 8-17    有序集合元素同时被保存在字典和跳跃表中";

}

类型检查与命令多态

LLEN 命令执行时的类型检查过程。

digraph {

    label = "\n 图 8-18    LLEN 命令执行时的类型检查过程";

    //

    call_command [label = "客户端发送 LLEN <key> 命令", shape = box];

    check_type [label = "服务器检查 \n 键 key 的值对象\n是否列表对象", shape = diamond];

    execute_command [label = "对键 key 执行 LLEN 命令", shape = box];

    type_error [label = "返回一个类型错误", shape = box];

    //

    call_command -> check_type;

    check_type -> execute_command [label = "是"];

    check_type -> type_error [label = "否"];

}


LLEN 命令的多态行为。

digraph {

    label = "\n 图 8-19    LLEN 命令的执行过程";

    //

    node [shape = box];

    call_command [label = "客户端发送 LLEN <key> 命令"];

    check_type [label = "服务器检查 \n 键 key 的值对象\n是否列表对象", shape = diamond];

    //execute_command [label = "对键 key 执行 LLEN 命令"];

    select_encoding [label = "对象的编码是 \n ziplist 还是 linkedlist ?", shape = diamond];

    ziplist [label = "调用 ziplistLen 函数 \n 返回压缩列表的长度"];

    linkedlist [label = "调用 listLength 函数 \n 返回双端链表的长度"];

    type_error [label = "返回一个类型错误"];

    //

    call_command -> check_type;

    //check_type -> execute_command [label = "是"];

    check_type -> type_error [label = "否"];

    //execute_command -> select_encoding;

    check_type -> select_encoding [label = "是"];

    select_encoding -> ziplist [label = "ziplist \n 编码"];

    select_encoding -> linkedlist [label = "linkedlist \n 编码"];

}

对象共享

对象共享的示例。

digraph {

    label = "\n 图 8-23    引用数为 3 的共享对象";

    rankdir = LR;

    server [label = "服务器程序", shape = box, width = 1.5];
    key_a [label = "键 A", shape = box, width = 1.5];
    key_b [label = "键 B", shape = box, width = 1.5];

    redisObject [label = " <head> redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | refcount \n 3 | ... ", shape = record];

    node [shape = plaintext];

    number [label = "100"]

    redisObject:ptr -> number;

    key_a -> redisObject:head;
    key_b -> redisObject:head;
    server -> redisObject:head;

}

第 9 章:数据库

数据库示例。

digraph {

    label = "\n 图 9-1    服务器数据库示例";

    rankdir = LR;

    node [shape = record];

    //

    redisServer [label = "redisServer | ... | <db> db | ... | dbnum \n 16 | ..."];

    db [label = "{ <0> db[0] | <1> db[1] | <2> db[2] | ... | <15> db[15] }"];

    //

    redisServer:db -> db;

}


数据库键空间示例。

digraph {

    label = "\n图 9-4    数据库键空间例子";

    rankdir = LR;

    node [shape = record];

    //

    redisDb [label = "redisDb | ... | <dict> dict | ..."];

    dict [label = "<dict> dict | <alphabet> StringObject \n \"alphabet\" | <book> StringObject \n \"book\" | <message> StringObject \n \"message\""];

    subgraph cluster_alphabet {

        a [label = " StringObject \n \"a\" "];
        b [label = " StringObject \n \"b\" "];
        c [label = " StringObject \n \"c\" "];

        a -> b -> c;

        label = "ListObject";

    }

    //alphabet [label = "<head> ListObject | { StringObject \n \"a\" | \"b\" | \"c\" }"];

    book [label = "<head> HashObject | <name> StringObject \n \"name\" | <author> StringObject \n \"author\" | <publisher> StringObject \n \"publisher\""];

    //name [label = " StringObject \n \"Redis in Action\""];
    name [label = " StringObject \n \"Redis in Action\""];

    author [label = " StringObject \n \"Josiah L. Carlson\""];

    publisher [label = " StringObject \n \"Manning\""];

    message [label = " StringObject \n \"hello world\""];

    //

    redisDb:dict -> dict:dict;

    dict:alphabet -> a;
    dict:book -> book:head;
    dict:message -> message;

    book:name -> name;
    book:publisher -> publisher;
    book:author -> author;

}


带有过期时间的字典示例。

digraph {

    label = "\n图 9-12    带有过期字典的数据库例子";

    rankdir = LR;

    node [shape = record];

    //

    redisDb [label = "redisDb | ... | <dict> dict | <expires> expires | ..."];

    dict [label = "<dict> dict | <alphabet> StringObject \n \"alphabet\" | <book> StringObject \n \"book\" | <message> StringObject \n \"message\""];

    subgraph cluster_alphabet {

        a [label = " StringObject \n \"a\" "];
        b [label = " StringObject \n \"b\" "];
        c [label = " StringObject \n \"c\" "];

        a -> b -> c;

        label = "ListObject";

    }

    book [label = "<head> HashObject | <name> StringObject \n \"name\" | <author> StringObject \n \"author\" | <publisher> StringObject \n \"publisher\""];

    name [label = "<head> StringObject \n \"Redis in Action\""];

    author [label = "<head> StringObject \n \"Josiah L. Carlson\""];

    publisher [label = "<head> StringObject \n \"Manning\""];

    message [label = "<head> StringObject \n \"hello world\""];

    //

    expires [label = "<head> dict | <alphabet> StringObject \n \"alphabet\" | <book> StringObject \n \"book\""];

    redisDb:expires -> expires:head;

    alphabet_expire [label = "<head> long long | 1385877600000"];
    book_expire [label = "<head> long long | 1388556000000"];

    expires:alphabet -> alphabet_expire:head;
    expires:book -> book_expire:head;

    //

    redisDb:dict -> dict:dict;

    dict:alphabet -> a;
    dict:book -> book:head;
    dict:message -> message:head;

    book:name -> name:head;
    book:publisher -> publisher:head;
    book:author -> author:head;

}


GET 命令判断是否需要删除过期键的过程。

digraph {

    label = "\n图 9-16    GET 命令的执行过程";

    node [shape = box];

    //

    get [label = "GET <key>"];

    key_exists_or_not [label = "键不存在?", shape = diamond];

    key_expired_or_not [label = "键已过期?", shape = diamond];

    return_nil [label = "返回空回复"];

    return_value [label = "返回键 key 的值"];

    //

    get -> key_exists_or_not;

    key_exists_or_not -> return_nil [label = "是"];

    key_exists_or_not -> key_expired_or_not [label = "否"];

    key_expired_or_not -> return_nil [label = "是\n删除过期键"];

    key_expired_or_not -> return_value [label = "否"];

}


当 Redis 运行在复制模式时, 主从服务器处理过期键的流程。

digraph {

    label = "\n图 9-17    主从服务器删除过期键 (1)";

    rankdir = LR;

    //

    node [shape = record];

    subgraph cluster_master {

        label = "主服务器";

        master_db [label = " 数据库 | message \n (已过期) | xxx | yyy "];

    }

    subgraph cluster_slave {

        label = "从服务器";

        slave_db [label = " 数据库 | message \n (已过期) | xxx | yyy "];

    }

    //

    master_db -> slave_db [style = invis, minlen = 2];

}

digraph {

    label = "\n图 9-18    主从服务器删除过期键 (2)";

    rankdir = LR;

    //

    node [shape = record];

    subgraph cluster_master {

        label = "主服务器";

        master_db [label = " 数据库 | message \n (已过期) | xxx | yyy "];

    }

    subgraph cluster_slave {

        label = "从服务器";

        slave_db [label = " 数据库 | <message> message \n (已过期) | xxx | yyy "];

    }

    client [label = "客户端", shape = circle];

    //
    splines = polyline;

    master_db -> slave_db [style = invis, minlen = 2];

    slave_db -> client [dir = back, label = "GET message"];

    slave_db -> client [label = "(nil)"];

}

digraph {

    label = "\n图 9-19    主从服务器删除过期键 (3)";

    rankdir = LR;

    //

    node [shape = record];

    subgraph cluster_master {

        label = "主服务器";

        master_db [label = " 数据库 | xxx | yyy ", width = 1.25, height = 1.75];

    }

    subgraph cluster_slave {

        label = "从服务器";

        slave_db [label = " 数据库 | <message> message \n (已过期) | xxx | yyy "];

    }

    client [label = "客户端", shape = circle];

    //

    splines = polyline;

    master_db -> slave_db [label = "DEL message"];

    client -> master_db [label = "GET message"];
    client -> master_db [dir = back, label = "(nil)"];

}

digraph {

    label = "\n图 9-20    主从服务器删除过期键 (4)";

    rankdir = LR;

    //

    node [shape = record, width = 1.25, height = 1.75];

    subgraph cluster_master {

        label = "主服务器";

        master_db [label = " 数据库 | xxx | yyy "];

    }

    subgraph cluster_slave {

        label = "从服务器";

        slave_db [label = " 数据库 | xxx | yyy "];

    }

    //

    master_db -> slave_db [style = invis, minlen = 2];

}

第 10 章:RDB 持久化

Redis 服务器数据库示例。

digraph {

    rankdir = LR;

    node [shape = record];

    label = "\n图 10-1    数据库状态示例";

    subgraph cluster_server {

        label = "Redis 服务器";

        db0 [label = "数据库 0 | { k1 | v1} | { k2 | v2 } | { k3 | v3 }"];

        db1 [label = "数据库 1 | { k1 | v1} | { k2 | v2 } | { k3 | v3 }"];

        db2 [label = "数据库 2 | { k1 | v1} | { k2 | v2 } | { k3 | v3 }"];

        db0->db1 -> db2 [style = invis];

    }

}


Redis 保存和载入 RDB 文件的流程。

digraph {

    rankdir = LR;

    label = "\n图 10-2    将数据库状态保存为 RDB 文件";

    //

    state [label = "数据库状态", shape = circle];

    rdb [label = "RDB 文件", shape = note, height = 1.8, width = 1.4];

    //

    state -> rdb [label = "保存为", minlen = 2.5];

}

digraph {

    rankdir = LR;

    label = "\n图 10-3    用 RDB 文件来还原数据库状态";

    //

    state [label = "数据库状态", shape = circle];

    rdb [label = "RDB 文件", shape = note, height = 1.8, width = 1.4];

    //

    state -> rdb [dir = back, label = "还原", minlen = 2.5];

}

RDB 文件的创建与载入

服务器判断是载入 RDB 文件还是载入 AOF 文件的流程。

digraph {

    label = "\n图 10-4    服务器载入文件时的判断流程";

    node [shape = box];

    //

    server_star [label = "服务器启动", width = 3]

    start_load [label = "执行载入程序", width = 3];

    aof_or_not [label = "已开启 AOF 持久化功能?", shape = diamond];

    load_by_aof [label = "载入 AOF 文件"];

    load_by_rdb [label = "载入 RDB 文件"];

    //

    server_star -> start_load -> aof_or_not;

    aof_or_not -> load_by_aof [label = "是"];

    aof_or_not -> load_by_rdb [label = "否"];

}


负责创建和载入 RDB 文件的两个函数之间的关系。

digraph {

    label = "\n图 10-5    创建和载入 RDB 文件";

    rankdir = LR;

    splines = polyline

    //

    node [shape = circle, width = 1.3, height = 1.3];

    state [label = "数据库状态"];

    rdb [label = "RDB 文件"];

    //

    edge [minlen = 2.5];

    state -> rdb [label = "rdbSave"];

    rdb -> state [label = "\nrdbLoad"];

}

自动间隔性保存

记录了服务器触发自动 BGSAVE 条件的 saveparams 属性。

digraph {

    label = "\n图 10-6    服务器状态中的保存条件";

    rankdir = LR;

    node [shape = record];

    //

    redisServer [label = " redisServer | ... | <saveparams> saveparams | ... "];

    saveparams [label = " { { saveparams[0] | seconds \n 900 | changes \n 1 } | { saveparams[1] | seconds \n 300 | changes \n 10 } | { saveparams[2] | seconds \n 60 | changes \n 10000 } } "];

    //

    redisServer:saveparams -> saveparams;

}


记录服务器最后一次执行 SAVE 或者 BGSAVE 的时间, 以及自最后一次保存 RDB 文件以来, 服务器进行了多少次写入的 lastsave 属性和 dirty 属性。

digraph {

    label = "\n图 10-7    服务器状态示例";

    rankdir = LR;

    node [shape = record];

    //

    redisServer [label = " redisServer | ... | dirty \n 123 | lastsave \n 1378270800 | ... "];

}


用于记录和检查服务器是否需要自动执行 BGSAVE 的相关属性和数据结构的示例。

digraph {

    label = "\n图 10-8    服务器状态";

    rankdir = LR;

    node [shape = record];

    //

    redisServer [label = " redisServer | ... | <saveparams> saveparams | ... | dirty \n 123 | lastsave \n 1378270800 | ... "];

    saveparams [label = " { { saveparams[0] | seconds \n 900 | changes \n 1 } | { saveparams[1] | seconds \n 300 | changes \n 10 } | { saveparams[2] | seconds \n 60 | changes \n 10000 } } "];

    //

    redisServer:saveparams -> saveparams;

}

RDB 文件结构

RDB 文件的总体结构。

digraph {

    label = "\n图 10-10    RDB 文件结构";

    node [shape = record];

    rdb [label = " REDIS | db_version | databases | EOF | check_sum "];

}

包含数据库 0 和数据库 3 的非空 RDB 文件结构示例。

digraph {

    label = "\n图 10-12    带有两个非空数据库的 RDB 文件示例";

    node [shape = record];

    rdb [label = " REDIS | db_version | database 0 | database 3 | EOF | check_sum "];

}


RDB 文件中的数据库结构。

digraph {

    label = "\n图 10-13    RDB 文件中的数据库结构";

    node [shape = record];

    database [label = " SELECTDB | db_number | key_value_pairs "];

}

示例。

digraph {

    label = "\n图 10-14    数据库结构示例";

    node [shape = record];

    value [label = " SELECTDB | 0 | key_value_pairs "];

}

包含了数据库部分的 RDB 文件示例。

digraph {

    label = "\n图 10-15    RDB 文件中的数据库结构示例";

    node [shape = record];

    v [label = " REDIS | db_version | SELECTDB | 0 | pairs | SELECTDB | 3 | pairs | EOF | check_sum"];

}


不带过期时间的键值对结构。

digraph {

    label = "\n图 10-16    不带过期时间的键值对";

    node [shape = record];

    kvp [label = " TYPE | key | value "];

}

示例。

digraph {

    label = "\n图 10-18    无过期时间的字符串键值对示例";

    node [shape = record];

    string [label = " REDIS_RDB_TYPE_STRING | key | value "];

}


带有过期时间的键值对结构。

digraph {

    label = "\n图 10-17    带有过期时间的键值对";

    node [shape = record];

    kvp [label = " EXPIRETIME_MS | ms | TYPE | key | value "];

}

示例。

digraph {

    label = "\n图 10-19    带有过期时间的集合键值对示例";

    node [shape = record];

    set [label = " EXPIRETIME_MS | 1388556000000 | REDIS_RDB_TYPE_SET | key | value "];

}


int 编码的字符串对象的结构。

digraph {

    label = "\n图 10-20    INT 编码字符串对象的保存结构";

    node [shape = record];

    v [label = " ENCODING | integer "];

}

示例。

digraph {

    label = "\n图 10-21    用 8 位来保存整数的例子";

    node [shape = record];

    v [label = " REDIS_RDB_ENC_INT8 | 123 "];

}


内容没有被压缩的字符串对象的结构。

digraph {

    label = "\n图 10-22    无压缩字符串的保存结构";

    node [shape = record];

    value [ label = " len | string "];

}

示例。

digraph {

    label = "\n图 10-24    无压缩的字符串";

    node [shape = record];

    value [ label = " 5 | \"hello\" "];

}


内容被压缩后的字符串对象的结构。

digraph {

    label = "\n图 10-23    压缩后字符串的保存结构";

    node [shape = record];

    value [ label = " REDIS_RDB_ENC_LZF | compressed_len | origin_len | compressed_string "];

}

示例, 其中 ? 代表的是无法用字符串形式打印出来的字节。

digraph {

    label = "\n图 10-25    压缩后的字符串";

    node [shape = record];

    value [label = " REDIS_RDB_ENC_LZF | 6 | 21 | \"?aa???\" "];

}


列表对象的结构。

digraph {

    label = "\n图 10-26    LINKEDLIST 编码列表对象的保存结构";

    node [shape = record];

    value [label = " list_length | item1 | item2 | ... | itemN "];

}

示例。

digraph {

    label = "\n图 10-27    保存 LINKEDLIST 编码列表的例子";

    node [shape = record];

    list [label = " 3 | 5 | \"hello\" | 5 | \"world\"  |  1 | \"!\"  "];

}


集合对象的结构。

digraph {

    label = "\n图 10-28    HT 编码集合对象的保存结构";

    node [shape = record];

    value [ label = " set_size | elem1 | elem2 | ... | elemN "];

}

示例。

digraph {

    label = "\n图 10-29    保存 HT 编码集合的例子";

    node [shape = record];

    set [label = " 4 | 5 | \"apple\" | 6 | \"banana\" | 3 | \"cat\" | 3 | \"dog\" "];

}


哈希对象的结构。

digraph {

    label = "\n图 10-30    HT 编码哈希表对象的保存结构";

    node [shape = record];

    hash [label = " hash_size | key_value_pair 1 | key_value_pair 2 | ... | key_value_pair N "];

}

更详细的哈希对象结构。

digraph {

    label = "\n图 10-32    更详细的 HT 编码哈希表对象的保存结构";

    node [shape = record];

    hash [label = " hash_size | key1 | value1 | key2 | value2 | ... | keyN | valueN "];
}

示例。

digraph {

    label = "\n图 10-33    保存 HT 编码哈希表的例子";

    node [shape = record];

    hash [label = " 2 | 1 | \"a\" | 5 | \"apple\" | 1 | \"b\" | 6 | \"banana\" "];
}


有序集合对象结构。

digraph {

    label = "\n图 10-34    SKIPLIST 编码有序集合对象的保存结构";

    node [shape = record];

    zset [label = " sorted_set_size | element1 | element2 | ... | elementN "];

}

更详细的有序集合对象结构。

digraph {

    label = "\n图 10-36    更详细的 SKIPLIST 编码有序集合对象的保存结构";

    node [shape = record];

    sorted_set [label = " sorted_set_size | member1 | score1 | member2 | score2 | ... | memberN | scoreN "];

}

示例。

digraph {

    label = "\n图 10-37    保存 SKIPLIST 编码有序集合的例子";

    node [shape = record];

    sorted_set [label = " 2 | 2 | \"pi\" | 4 | \"3.14\" | 1 | \"e\" | 3 | \"2.7\" "];

}

第 11 章: AOF 持久化

Redis 服务器在进行 AOF 持久化时, 客户端、服务器与 AOF 文件之间的互动。

digraph {

    label = "\n图 11-1    AOF 持久化";

    rankdir = LR;

    //

    client [label = "客户端", shape = circle, height = 1.3, width = 1.3];

    server [label = "服务器", shape = box, width = 1.0, height = 2, width = 1.5];

    aof [label = "AOF 文件", shape = note, height = 1.3];

    //

    client -> server [label = "发送写命令"];

    server -> aof [label = "保存被执行的写命令"];

}


AOF 文件的载入过程。

digraph {

    label = "\n 图 11-2    AOF 文件载入过程"

    //

    node [shape = box]

    start_loading [label = "服务器启动载入程序", width = 4]

    create_fake_client [label = "创建伪客户端", width = 4]

    load_a_command_from_aof [label = "从 AOF 文件中分析并读取出一条写命令", width = 4]

    exec_command_via_fake_client [label = "使用伪客户端执行写命令"]

    all_command_exec_or_not [shape = diamond, label = "AOF 文件中的所有写命令\n都已经被执行完毕?"]

    done [label = "载入完毕", width = 4]

    //

    start_loading -> create_fake_client -> load_a_command_from_aof -> exec_command_via_fake_client -> all_command_exec_or_not;

    all_command_exec_or_not -> done [label = "是"]

    all_command_exec_or_not -> load_a_command_from_aof [label = "否"]

}


在进行 AOF 重写时, 服务器会同时将命令发送给 AOF 文件和 AOF 重写缓冲区。

digraph {

    label = "图 11-4    服务器同时将命令发送给 AOF 文件和 AOF 重写缓冲区";

    rankdir = LR;

    client [label = "客户端", shape = circle, height = 1.5];

    subgraph cluster_server {

        label = "服务器";

    command_handler [label = "命\n令\n处\n理\n器", shape = box, height = 2.0];

    aof_rewrite_buf [label = "AOF 重写缓冲区", shape = box];
    aof_buf [label = "AOF 缓冲区", shape = box];

    command_handler -> aof_rewrite_buf [label = "追加命令"];
    command_handler -> aof_buf [label = "追加命令"];


    }

    client -> command_handler [label = "发送命令"];
}

第 12 章: 事件

Redis 的文件事件处理器的四个组成部分。

digraph {

    label = "\n 图 12-1    文件事件处理器的四个组成部分";

    rankdir = LR;

    node [shape = box];

    subgraph cluster_sockets {

        style = dashed

        label = "套接字";

        c1 [label = "s1", shape = circle];
        c2 [label = "s2", shape = circle];
        other_client [label = "...", width = 1.1, shape = plaintext];
        c3 [label = "sN", shape = circle];

    }

    io_multiplexing [label = "I\n/\nO\n多\n路\n复\n用\n程\n序"];

    file_event_processor [label = "文\n件\n事\n件\n分\n派\n器"];

    subgraph cluster_handlers {

        style = dashed

        label = "事件处理器";

        write_handler [label = "命令请求处理器"];

        read_handler [label = "命令回复处理器"];

        connect_handler [label = "连接应答处理器"];

        other_handlers [label = "...", width = 1.6];

    }

    c1 -> io_multiplexing;
    c2 -> io_multiplexing;
    other_client -> io_multiplexing [style = invis];
    c3 -> io_multiplexing;

    io_multiplexing -> file_event_processor;

    file_event_processor -> write_handler;
    file_event_processor -> read_handler;
    file_event_processor -> connect_handler;
    file_event_processor -> other_handlers;
}


I/O 多路复用程序通过队列向文件事件分派器传送套接字的过程。

digraph {

    rankdir = LR;

    node [shape = record];

    label = "\n图 12-2    I/O 多路复用程序通过队列向文件事件分派器传送套接字";

    //

    subgraph cluster_io_multiplexing {

        //style = dashed

        label = "队列";

        queue [label = " { 套接字 sN | 套接字 sN-1 | ... | 套接字 s3 | 套接字 s2 } "];

    }

    file_event_processor [label = "文\n件\n事\n件\n分\n派\n器"];

    //

    queue -> file_event_processor [label = "传送\n 套接字 s1", style = dashed];
}


Redis 的 I/O 多路复用程序有多个 I/O 多路复用库可选。

digraph {

    label = "图 12-3    Redis 的 I/O 多路复用程序有多个 I/O 多路复用库实现可选";

    node [shape = box];

    io_multiplexing [label = "I/O 多路复用程序"];

    subgraph cluster_imp {

        style = dashed

        label = "底层实现";
        labelloc = "b";

        kqueue [label = "kqueue"];
        evport [label = "evport"];
        epoll [label = "epoll"];
        select [label = "select"];
    }

    //

    edge [dir = back];

    io_multiplexing -> select;
    io_multiplexing -> epoll;
    io_multiplexing -> evport;
    io_multiplexing -> kqueue;

}


客户端与服务器通过事件应答来进行通信的过程。

digraph {

    label = "\n图 12-7    客户端和服务器的通讯过程";

    splines = polyline;

    rankdir = LR;

    node [shape = box, height = 3.0];

    client [label = "客\n户\n端"];

    server [label = "服\n务\n器"];

    client -> server [label = "客户端向服务器发送连接请求\n服务器执行连接应答处理器"];

    client -> server [label = "\n\n客户端向服务器发送命令请求\n服务器执行命令请求处理器"];

    server -> client [label = "\n\n服务器向客户端发送命令回复\n服务器执行命令回复处理器"];

}


时间事件示例。

digraph {

    label = "\n图 12-8    用链表连接起来的三个时间事件";

    rankdir = LR;

    node [shape = record];

    time_events [shape = plaintext];

    te3 [label = "time_event | id\n3 | when\n1385877600030\n(2013年12月1日\n零时之后30毫秒) | timeProc\nhandler_3"];
    te2 [label = "time_event | id\n2 | when\n1385877600000\n(2013年12月1日\n零时) | timeProc\nhandler_2"];
    te1 [label = "time_event | id\n1 | when\n1385877600010\n(2013年12月1日\n零时之后10毫秒) | timeProc\nhandler_1"];

    time_events -> te3 -> te2 -> te1;

}


事件处理角度下的 Redis 服务器运行流程。

digraph {

    label = "\n图 12-10    事件处理角度下的服务器运行流程";

    start_server [label = "启动服务器", shape = box];

    server_close_or_not [label = "是否关闭服务器?", shape = diamond];

    start_server -> server_close_or_not;

    close_server [label = "关闭服务器", shape = box];

    wait_for_file_event [label = "等待文件事件产生", shape = box];

    handler_file_event [label = "处理已产生的文件事件", shape = box];

    handler_time_event [label = "处理已达到的时间事件", shape = box];

    wait_for_file_event -> handler_file_event -> handler_time_event;

    //

    server_close_or_not -> close_server [label = "是"];

    server_close_or_not -> wait_for_file_event [label = "否"];

    handler_time_event -> server_close_or_not [label = "开始新的事件循环"];

}

第 13 章: 客户端

三个连接到服务器的客户端。

digraph {

    label = "\n 13-1    客户端与服务器";

    rankdir = LR;

    node [shape = circle, width = 1.2];

    c1 [label = "客户端 1"];
    c2 [label = "客户端 2"];
    c3 [label = "客户端 3"];

    node [shape = box, height = 4.0, width = 1.5];

    server [label = "服\n务\n器"];

    //

    edge [dir = none, minlen = 2];

    c1 -> server;
    c2 -> server;
    c3 -> server;
}

这三个客户端在服务器状态 clients 属性中的样子。

digraph {

    label = "\n 13-2    clients 链表";

    rankdir = LR;

    //

    node [shape = record];

    redisServer [label = " redisServer | ... | <clients> clients | ... ", width = 2];

    c1 [label = " redisClient\n(客户端 1) "];
    c2 [label = " redisClient\n(客户端 2) "];
    c3 [label = " redisClient\n(客户端 3) "];

    //

    redisServer:clients -> c1;
    c1 -> c2;
    c2 -> c3;

}


Redis 客户端的输入缓冲区示例。

digraph {

    label = "\n 图 13-4    querybuf 属性示例";

    rankdir = LR;

    //

    node [shape = record];

    client [label = " redisClient | ... | <querybuf> querybuf | ... ", width = 2];

    sdshdr [label = " <head> sdshdr | free \n 0 | len \n 33 | <buf> buf "];

    buf [label = " { '*' | '3' | '\\r' | '\\n' | ... | 'v' | 'a' | 'l' | 'u' | 'e' | '\\r' | '\\n' | '\\0' } "];

    //

    client:querybuf -> sdshdr:head;

    sdshdr:buf -> buf;

}


Redis 客户端的 argv 属性和 argc 属性示例。

digraph {

    label = "\n 图 13-5    argv 属性和 argc 属性示例";

    rankdir = LR;

    node [shape = record];

    redisClient [label = " redisClient | ... | <argv> argv | argc \n 3 | ... ", width = 2];

    argv [label = " { { <head> argv[0] | StringObject \n \"SET\" } | { argv[1] | StringObject \n \"key\" } | { argv[1] | StringObject \n \"value\" } } "];

    redisClient:argv -> argv:head;

}


Redis 客户端的固定大小输出缓冲区示例。

digraph {

    label = "\n 图 13-8    固定大小缓冲区示例";

    rankdir = LR;

    node [shape = record];

    redisClient [label = " redisClient | ... | <buf> buf | bufpos \n 5 | ... "];

    buf [label = " { '+' | 'O' | 'K' | '\\r' | '\\n' | '\\0' | ... } "];

    redisClient:buf -> buf;

}

Redis 客户端的可变大小输出缓冲区示例。

digraph {

    label = "\n 图 13-9    可变大小缓冲区示例";

    rankdir = LR;

    node [shape = record];

    redisClient [label = " redisClient | ... | <reply> reply | ... ", width = 2];

    node [label = " <head> StringObject \n ... "];

    redisClient:reply -> s1:head -> s2:head -> s3:head;

}

第 14 章: 服务器

用户通过客户端向服务器发送命令请求的过程。

digraph {

    label = "\n\n 图 14-1    客户端接收并发送命令请求的过程";

    rankdir = LR;

    node [shape = plaintext];

    user [label = "用户"];

    client [label = "客户端"];

    server [label = "服务器"];

    //

    user -> client [label = "键入命令请求"];

    client -> server [label = "将命令请求转换成协议格式\n然后发送"];

}


服务器向用户返回命令回复的过程。

digraph {

    label = "\n\n 图 14-8    客户端接收并打印命令回复的过程";

    rankdir = LR;

    node [shape = plaintext];

    user [label = "用户"];

    client [label = "客户端"];

    server [label = "服务器"];

    //

    server -> client [label = "回复处理器将协议格式的\n命令回复返回给客户端"];

    client -> user [label = "将回复格式化成人类可读格式 \n 然后打印显示"];

}


Redis 的命令表示例。

digraph {

    label = "\n 图 14-4    命令表";

    rankdir = LR;

    node [shape = record];

    commands [label = " 命令表 | ... | <set> \"set\" | <get> \"get\" | ... | <sadd> \"sadd\" | ... | <rpush> \"rpush\" | ... | <publish> \"publish\" | ... ", width = 2.0];

    set [label = " <head> redisCommand | name \n \"set\" | <proc> proc | arity \n -3 | sflags \n \"wm\" | ... "];
    get [label = " <head> redisCommand | name \n \"get\" | <proc> proc | arity \n 2 | sflags \n \"r\" | ... "];
    //sadd [label = " <head> redisCommand | name \n \"sadd\" | <proc> proc | arity \n -3 | sflags \n \"wm\" | ... "];
    //rpush [label = " <head> redisCommand | name \n \"rpush\" | <proc> proc | arity \n -3 | sflags \n \"wm\" | ... "];
    //publish [label = " <head> redisCommand | name \n \"publish\" | <proc> proc | arity \n 3 | sflags \n \"pltr\" | ... "];

    node [shape = plaintext];

    setCommand [label = "void setCommand(redisClient *c);"];
    getCommand [label = "void getCommand(redisClient *c);"];
    //saddCommand;
    //rpushCommand;
    //publishCommand;

    //

    commands:set -> set:head; set:proc -> setCommand;
    commands:get -> get:head; get:proc -> getCommand;
    //commands:sadd -> sadd:head; sadd:proc -> saddCommand;
    //commands:rpush -> rpush:head; rpush:proc -> rpushCommand;
    //commands:publish -> publish:head; publish:proc -> publishCommand;

    //* fix editor highlight

}


服务器判断是否需要执行持久化操作的流程。

digraph {

    label = "\n 图 14-9    判断是否需要执行持久化操作";

    //

    node [shape = box]

    start [label = "服务器没有在执行任何持久化操作"]

    delay_bgrewriteaof_exists_or_not [shape = diamond, label = "有 BGREWRITEAOF 被延迟?"]

    go_bgrewriteaof [label = "执行 BGREWRITEAOF"]

    bgsave_satisfied_or_not [shape = diamond, label = "自动保存的条件已经满足?"]

    go_bgsave [label = "执行 BGSAVE"]

    bgrewriteaof_satisfied_or_not [shape = diamond, label = "AOF 重写的条件已经满足?"]

    go_do_nothing [label = "不做动作"]

    //

    start -> delay_bgrewriteaof_exists_or_not

    delay_bgrewriteaof_exists_or_not -> go_bgrewriteaof [label = "是"]

    delay_bgrewriteaof_exists_or_not -> bgsave_satisfied_or_not [label = "否"]

    bgsave_satisfied_or_not -> go_bgsave [label = "是"]

    bgsave_satisfied_or_not -> bgrewriteaof_satisfied_or_not [label = "否"]

    bgrewriteaof_satisfied_or_not -> go_bgrewriteaof [label = "是"]

    bgrewriteaof_satisfied_or_not -> go_do_nothing [label = "否"]

}

第 15 章: 复制

SYNC 命令执行期间, 主从服务器的通信过程。

digraph {

    label = "\n 图 15-2    主从服务器在执行 SYNC 命令期间的通信过程"

    rankdir = LR

    splines = polyline

    node [shape = box, height = 2]

    master [label = "主\n服\n务\n器"]

    slave [label = "从\n服\n务\n器"]

    master -> slave [dir = back,  label = "发送 SYNC 命令"]

    master -> slave [label = "\n 发送 RDB 文件"]

    master -> slave [label = "\n 发送缓冲区保存的所有写命令"]

}


Redis 的命令传播过程示例。

digraph {

    label = "\n 图 15-3    处于一致状态的主从服务器"

    rankdir = LR

    node [shape = record, width = 2]

    subgraph cluster_master {

        label = "主服务器"

        master_db [label = " <head> 数据库 | <k1> k1 | <k2> k2 | <k3> k3 | <k4> k4 | <k5> k5 "];

    }


    subgraph cluster_slave {

        label = "从服务器"

        slave_db [label = " <head> 数据库 | <k1> k1 | <k2> k2 | <k3> k3 | <k4> k4 | <k5> k5 "];

    }

    master_db -> slave_db [style = invis]
}

如果这时, 客户端向主服务器发送命令 DEL k3 , 那么主服务器在执行完这个 DEL 命令之后, 主从服务器的数据库将出现不一致: 主服务器的数据库已经不再包含键 k3 , 但这个键却仍然包含在从服务器的数据库里面, 如图 15-4 所示。

digraph {

    label = "\n 图 15-4    处于不一致状态的主从服务器"

    rankdir = LR

    node [shape = circle]

    client [label = "客户端"]

    node [shape = record, width = 2]

    subgraph cluster_master {

        label = "主服务器"

        master_db [label = " <head> 数据库 | <k1> k1 | <k2> k2 | <k4> k4 | <k5> k5 "];

    }


    subgraph cluster_slave {

        label = "从服务器"

        slave_db [label = " <head> 数据库 | <k1> k1 | <k2> k2 | <k3> k3 | <k4> k4 | <k5> k5 "];

    }

    master_db -> slave_db [style = invis]

    client -> master_db [label = "发送命令 \n DEL k3"]
}

在上面的例子中, 主服务器因为执行了命令 DEL k3 而导致主从服务器不一致, 所以主服务器将向从服务器发送相同的命令 DEL k3 : 当从服务器执行完这个命令之后, 主从服务器将再次回到一致状态 —— 现在主从服务器两者的数据库都不再包含键 k3 了, 如图 15-5 所示。

digraph {

    label = "\n 图 15-5    主服务器向从服务器发送命令"

    rankdir = LR

    node [shape = record, width = 2]

    subgraph cluster_master {

        label = "主服务器"

        master_db [label = " <head> 数据库 | <k1> k1 | <k2> k2 | <k4> k4 | <k5> k5 "];

    }

    subgraph cluster_slave {

        label = "从服务器"

        slave_db [label = " <head> 数据库 | <k1> k1 | <k2> k2 | <k4> k4 | <k5> k5 "];

    }

    master_db -> slave_db [label = "发送命令 \n DEL k3"]
}


PSYNC 命令执行部分重同步时的过程。

digraph {

    rankdir = LR;

    node [shape = record, height = 2];

    splines = polyline;

    master [label = "主\n服\n务\n器"];

    slave [label = "从\n服\n务\n器"];

    master -> slave [dir = back, label = "PSYNC"];

    master -> slave [label = "\n+CONTINUE"];

    master -> slave [label = "\n发送主从服务器断线期间\n主服务器执行的写命令"];

    label = "\n图 15-6    主从服务器执行部分重同步的过程";

}


Redis 的复制积压缓冲区的结构图。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_master {

        label = "主服务器";

        propagater [label = "命令传播程序", height = 3.0];

        backlog [label = "复制积压缓冲区"];
        propagater -> backlog [label = "将写命令放入队列"];


    }

    node [height = 1.0];

    subgraph cluster_slaves {


        slave1 [label = "从服务器 A"];

        slave2 [label = "从服务器 B"];

        slave3 [label = "从服务器 C"];

        style = invis;

    }

    edge [label = "发送写命令"]

    backlog -> slave1 [style = invis];
    propagater -> slave1;
    propagater -> slave2;
    propagater -> slave3;

    label = "\n图 15-10    主服务器向复制积压缓冲区和所有从服务器传播写命令数据";
}


Redis 使用复制积压缓冲区来为断线的从服务器发送缺失数据的过程。

digraph {

    rankdir = LR;

    node [shape = box, height = 1.0];

    //

    master [label = "主服务器\n\noffset = 10086"];

    slave1 [label = "从服务器 A\n\noffset = 10086"];

    slave2 [label = "从服务器 B\n\noffset = 10086"];

    slave3 [label = "从服务器 C\n\noffset = 10086"];

    //

    edge [style = invis];

    master -> slave1;
    master -> slave2;
    master -> slave3;

    label = "\n图 15-7    拥有相同偏移量的主服务器和它的三个从服务器";

}

digraph {

    rankdir = LR;

    node [shape = box, height = 1.0];

    //

    master [label = "主服务器\n\noffset = 10119"];

    slave1 [label = "从服务器 A\n\noffset = 10119"];

    slave2 [label = "从服务器 B\n\noffset = 10119"];

    slave3 [label = "从服务器 C\n\noffset = 10119"];

    //

    edge [label = "传播 33 字节数据"];

    master -> slave1;
    master -> slave2;
    master -> slave3;

    label = "\n图 15-8    更新偏移量之后的主从服务器";
}

digraph {

    rankdir = LR;

    node [shape = box, height = 1.0];

    //

    master [label = "主服务器\n\noffset = 10119"];

    slave1 [label = "从服务器 A\n\noffset = 10086"];

    slave2 [label = "从服务器 B\n\noffset = 10119"];

    slave3 [label = "从服务器 C\n\noffset = 10119"];

    //

    edge [label = "传播 33 字节数据"];

    master -> slave1 [style = dotted, dir = none, label = "(断线)"];
    master -> slave2;
    master -> slave3;

    label = "\n图 15-9    因为断线而处于不一致状态的从服务器 A";
}

digraph {

    rankdir = LR;

    node [shape = box, height = 1.0];

    //

    master [label = "主服务器\n\noffset = 10119"];

    slave1 [label = "从服务器 A\n\noffset = 10119"];

    slave2 [label = "从服务器 B\n\noffset = 10119"];

    slave3 [label = "从服务器 C\n\noffset = 10119"];

    //

    master -> slave1 [label = "发送断线时缺失的\n 33 字节数据"];
    master -> slave2 [style = invis, dir = none];
    master -> slave3 [style = invis, dir = none];

    label = "\n图 15-11    主服务器向从服务器发送缺失的数据";

}


PSYNC 命令判断是执行部分重同步还是完整重同步的流程。

digraph {

    node [shape = box];

    data_sync [label = "从服务器接到客户端发来的 SLAVEOF 命令"];

    have_cache_or_not [label = "这是从服务器第一次执行复制?", shape = diamond];

    data_sync -> have_cache_or_not;

    force_full_resync [label = "向主服务器发送\nPSYNC ? -1"];

    try_partial_resync [label = "向主服务器发送\nPSYNC <runid> <offset>"];

    have_cache_or_not -> force_full_resync [label = "是"];

    have_cache_or_not -> try_partial_resync [label = "否"];

    full_resync [label = "主服务器返回\n+FULLRESYNC <runid> <offset>\n执行完整重同步"];

    force_full_resync -> full_resync;

    master_return_continue_or_not [label = "主服务器返回 +CONTINUE ?", shape = diamond];

    try_partial_resync -> master_return_continue_or_not;

    master_return_continue_or_not -> full_resync [label = "否"];

    partial_resync [label = "执行部分重同步"];

    master_return_continue_or_not -> partial_resync [label = "是"];

    label = "\n图 15-12    PSYNC 执行完整重同步和部分重同步时可能遇上的情况";

}


Redis 服务器监测命令缺失,并补发缺失命令的过程。

digraph {

    label = "\n 图 15-23    主从服务器处于一致状态"

    rankdir = LR;

    node [shape = box, height = 2]

    master [label = "主服务器\n复制偏移量为 200"]

    slave [label = "从服务器\n复制偏移量为 200"]

    master -> slave [style = invis]

}

如果这时主服务器执行了命令 SET key value (协议格式的长度为 33 字节), 将自己的复制偏移量更新到了 233 , 并尝试向从服务器传播命令 SET key value , 但这条命令却因为网络故障而在传播的途中丢失, 那么主从服务器之间的复制偏移量就会出现不一致: 主服务器的复制偏移量会被更新为 233 , 而从服务器的复制偏移量仍然为 200 , 如图 15-24 所示。

digraph {

    label = "\n 图 15-24    主从服务器处于不一致状态"

    rankdir = LR;

    node [shape = box]

    master [label = "主服务器\n复制偏移量为 233", height = 2]

    stop [shape = point]

    slave [label = "从服务器\n复制偏移量为 200", height = 2]

    master -> stop [label = "SET key value", style = dashed]

    stop -> slave [style = invis]

}

在这之后, 当从服务器向主服务器发送 REPLCONF ACK 命令的时候, 主服务器会察觉从服务器的复制偏移量依然为 200 , 而自己的复制偏移量为 233 , 这说明复制积压缓冲区里面复制偏移量为 201233 的数据(也即是命令 SET key value )在传播过程中丢失了, 于是主服务器会再次向从服务器传播命令 SET key value , 从服务器通过接收并执行这个命令可以将自己更新至主服务器当前所处的状态, 如图 15-25 所示。

digraph {

    label = "\n 图 15-25    主服务器向从服务器补发缺失的数据"

    rankdir = LR

    splines = polyline

    node [shape = box, height = 2]

    master [label = "主服务器\n复制偏移量为 233"]

    slave [label = "从服务器\n复制偏移量为 233"]

    master -> slave [dir = back, label = "REPLCONF ACK 200"]

    master -> slave [label = "\nSET key value"]

}

第 16 章: Sentinel

Sentinel 状态以及已连接的两个 Sentinel 实例结构。

digraph {

    label = "\n 图 16-5    master1 的实例结构";

    rankdir = LR;

    node [shape = record];

    //

    master1 [label = " <head> sentinelRedisInstance | flags \n SRI_MASTER | name \n \"master1\" | runid \n \"ee07959afc9d061233191c0f5bfe29580dfad0f4\" | config_epoch \n 0 | <addr> addr | down_after_period \n 30000 | quorum \n 2 | parallel_syncs \n 1 | failover_timeout \n 900000 | ... "];

    addr [label = " <head> sentinelAddr | ip \n \"127.0.0.1\" | port \n 6379 "];

    //

    master1:addr -> addr:head;

}

digraph {

    label = "\n 图 16-6    master2 的实例结构";

    rankdir = LR;

    node [shape = record];

    //

    master2 [label = " <head> sentinelRedisInstance | flags \n SRI_MASTER | name \n \"master2\" | runid \n \"a68408b775438a5dee54a638b3a6f3461920158a\" | config_epoch \n 0 | <addr> addr | down_after_period \n 50000 | quorum \n 5 | parallel_syncs \n 5 | failover_timeout \n 450000 | ... "];

    addr [label = " <head> sentinelAddr | ip \n \"127.0.0.1\" | port \n 12345 "];

    //

    master2:addr -> addr:head;

}

digraph {

    label = "\n 图 16-7    Sentinel 状态以及 masters 字典";

    rankdir = LR;

    node [shape = record];

    //

    sentinelState [label = " sentinelState | <masters> masters | ... "];

    masters [label = " <head> dict | <master1> \"master1\" | <master2> \"master2\" "];

    master1 [label = " <head> sentinelRedisInstance | ... | name \n \"master1\" | ... "];

    master2 [label = " <head> sentinelRedisInstance | ... | name \n \"master2\" | ... "];

    //

    sentinelState:masters -> masters:head;

    masters:master1 -> master1:head;
    masters:master2 -> master2:head;

}


Sentinel 通过向主服务器发送 INFO 命令来获得从服务器的信息。

digraph {

    label = "\n 图 16-9    Sentinel 向带有三个从服务器的主服务器发送 INFO 命令";

    sentinel [label = "Sentinel", shape = box, width = 4.5, height = 1.0];

    master [shape = doublecircle];

    node [shape = circle];
    edge [dir = none];

    master -> slave0;
    master -> slave1;
    master -> slave2;

    edge [dir = forward];

    sentinel -> master [label = "INFO"];

}

以及 Sentinel 为这个主服务器以及三个从服务器创建的信息结构。

digraph {

    label = "\n 图 16-10    主服务器和它的三个从服务器";

    rankdir = LR;

    node [shape = record];

    master [label = " sentinelRedisInstance | flags \n SRI_MASTER | run_id \n \"7611c59dc3a29aa6fa0609f841bb6a1019008a9c\" | name \n \"master\" | ... | <slaves> slaves | ... "];

    slaves [label = " <head> dict | <slave0> \"127.0.0.1:11111\" | <slave1> \"127.0.0.1:22222\" | <slave2> \"127.0.0.1:33333\" "];

    slave0 [label = " <head> sentinelRedisInstance | flags \n SRI_SLAVE | name \n \"127.0.0.1:11111\" | ... "];
    slave1 [label = " <head> sentinelRedisInstance | flags \n SRI_SLAVE | name \n \"127.0.0.1:22222\" | ... "];
    slave2 [label = " <head> sentinelRedisInstance | flags \n SRI_SLAVE | name \n \"127.0.0.1:33333\" | ... "];

    master:slaves -> slaves:head;
    slaves:slave0 -> slave0:head;
    slaves:slave1 -> slave1:head;
    slaves:slave2 -> slave2:head;

}


Sentinel 通过命令连接向频道发送信息, 并通过订阅连接从频道中获取信息。

digraph {

    label = "\n 图 16-13    Sentinel 同时向服务器发送和接收信息";

    rankdir = LR;

    sentinel [label = "Sentinel", shape = box, height = 1.8];

    master [label = "服务器", shape = circle];

    sentinel -> master [label = "通过命令连接\n发送信息到频道"];

    sentinel -> master [dir = back, label = "通过订阅连接\n从频道中接收信息"];

}


示例:一个 Sentinel 向包括它自己在内的三个 Sentinel 发送信息。

digraph {

    label = "\n 图 16-14    向服务器发送信息";

    node [shape = box];

    sentinel1 [label = "sentinel1", width = 2.0];

    sentinel2 [label = "sentinel2", width = 2.0];

    sentinel3 [label = "sentinel3", width = 2.0];

    server [label = "服务器", shape = circle];


    edge [label = "发送信息\n(命令连接)"];
    sentinel1 -> server;

    edge [style = dashed];

    edge [dir = back, label = "接收信息\n(订阅连接)"];
    sentinel1 -> server;
    sentinel2 -> server;
    sentinel3 -> server;
}


Redis Sentinel 执行故障转移的整个过程。

digraph {

    label = "\n 图 16-22    将 server2 升级为主服务器";

    subgraph cluster_servers {

        style = invis;

        node [shape = circle, width = 1.2];
        edge [dir = none, style = dashed];

        server1 [label = "server1", shape = doublecircle, style = dashed];

        server2 [label = "server2"];
        server3 [label = "server3"];
        server4 [label = "server4"];

        server1 -> server2;
        server1 -> server3;
        server1 -> server4;

    }

    sentinel_system [label = "领头 Sentinel", shape = box, width = 5.0, height = 1.0];

    edge [label = "监视"];

    sentinel_system -> server1 [style = dashed, label = "已下线", dir = none];
    sentinel_system -> server2 [label = "发送命令\nSLAVEOF no one"];
    sentinel_system -> server3 [dir = none];
    sentinel_system -> server4 [dir = none];

}

digraph {

    label = "\n 图 16-23    server2 成功升级为主服务器";

    subgraph cluster_servers {

        style = invis;

        node [shape = circle, width = 1.2];
        edge [dir = none, style = dashed];

        server1 [label = "server1", shape = doublecircle, style = dashed];

        server2 [label = "server2", shape = doublecircle];
        server3 [label = "server3"];
        server4 [label = "server4"];

        server1 -> server3;
        server1 -> server4;

    }

    sentinel_system [label = "领头 Sentinel", shape = box, width = 5.0, height = 1.0];

    edge [label = "监视", dir = none];

    sentinel_system -> server1 [style = dashed, label = "已下线"];
    sentinel_system -> server2;
    sentinel_system -> server3;
    sentinel_system -> server4;

}

digraph {

    label = "\n 图 16-24    让从服务器复制新的主服务器";

    subgraph cluster_servers {

        style = invis;

        node [shape = circle, width = 1.2];
        edge [dir = none, style = dashed];

        server1 [label = "server1", shape = doublecircle, style = dashed];

        server2 [label = "server2", shape = doublecircle];
        server3 [label = "server3"];
        server4 [label = "server4"];

        server1 -> server3;
        server1 -> server4;

    }

    sentinel_system [label = "领头 Sentinel", shape = box, width = 5.0, height = 1.0];

    sentinel_system -> server1 [style = dashed, label = "已下线", dir = none];
    sentinel_system -> server2 [label = "监视", dir = none];

    edge  [label = "发送命令\nSLAVEOF\n<server2_ip>\n<server2_port>"];
    sentinel_system -> server3;
    sentinel_system -> server4;

}

digraph {

    label = "\n 图 16-25    server3 和 server4 成为 server2 的从服务器";

    subgraph cluster_servers {

        style = invis;

        node [shape = circle, width = 1.2];
        edge [dir = none];

        server1 [label = "server1", shape = doublecircle, style = dashed];

        server2 [label = "server2", shape = doublecircle];
        server3 [label = "server3"];
        server4 [label = "server4"];

        server2 -> server3;
        server2 -> server4;

    }

    sentinel_system [label = "领头 Sentinel", shape = box, width = 5.0, height = 1.0];

    sentinel_system -> server1 [style = dashed, label = "已下线", dir = none];

    edge [label = "监视", dir = none];

    sentinel_system -> server2;
    sentinel_system -> server3;
    sentinel_system -> server4;

}

digraph {

    label = "\n 图 16-26    server1 被设置为新主服务器的从服务器";

    subgraph cluster_servers {

        style = invis;

        node [shape = circle, width = 1.2];
        edge [dir = none];

        server1 [label = "server1", shape = circle, style = dashed];

        server2 [label = "server2", shape = doublecircle];
        server3 [label = "server3"];
        server4 [label = "server4"];

        server2 -> server1 [style = dashed];
        server2 -> server3;
        server2 -> server4;

    }

    sentinel_system [label = "Sentinel 系统", shape = box, width = 5.0, height = 1.0];

    edge [dir = none];

    sentinel_system -> server1 [style = dashed, label = "已下线"];

    edge [label = "监视"];

    sentinel_system -> server2;
    sentinel_system -> server3;
    sentinel_system -> server4;

}

digraph {

    label = "\n 图 16-27    server1 重新上线并成为 server2 的从服务器";

    subgraph cluster_servers {

        style = invis;

        node [shape = circle, width = 1.2];
        edge [dir = none];

        server1 [label = "server1"];

        server2 [label = "server2", shape = doublecircle];
        server3 [label = "server3"];
        server4 [label = "server4"];

        server2 -> server1;
        server2 -> server3;
        server2 -> server4;

    }

    sentinel_system [label = "Sentinel 系统", shape = box, width = 5.0, height = 1.0];


    edge [label = "监视", dir = none];

    sentinel_system -> server1;
    sentinel_system -> server2;
    sentinel_system -> server3;
    sentinel_system -> server4;

}

第 17 章: 集群

节点

三个节点的握手过程。

digraph {

    label = "\n 图 17-1    三个独立的节点";

    node [shape = circle];

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        7000;

    }

    subgraph cluster_b {

        label = "集群";

        style = dashed;

        7001;

    }

    subgraph cluster_c {

        label = "集群";

        style = dashed;

        7002;

    }

}

digraph {

    label = "\n 图 17-2    节点 7000 和 7001 进行握手";

    rankdir = LR;

    node [shape = circle];

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        7000;

    }

    subgraph cluster_b {

        label = "集群";

        style = dashed;

        7001;

    }

    subgraph cluster_c {

        label = "集群";

        style = dashed;

        7002;

    }

    7000 -> 7001 [label = "握手"];

    7000 -> 7001 [dir = back, label = "响应握手"];

}

digraph {

    label = "\n 图 17-3    握手成功的 7000 与 7001 处于同一个集群";

    rankdir = LR;

    node [shape = circle];

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        7000;

        7001;

        7000 -> 7001 [style = invis];

    }

    subgraph cluster_c {

        label = "集群";

        style = dashed;

        7002;

    }

}

digraph {

    label = "\n 图 17-4    节点 7000 与节点 7002 进行握手";

    rankdir = LR;

    node [shape = circle];

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        7000;

        7001;

        7000 -> 7001 [style = invis];

    }

    subgraph cluster_c {

        label = "集群";

        style = dashed;

        7002;

    }

    7000 -> 7002 [label = "握手"];

    7000 -> 7002 [dir = back, label = "响应握手"];

}

digraph {

    label = "\n 图 17-5    握手成功的三个节点处于同一个集群";

    rankdir = LR;

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = circle];

        7000;

        7002;

        7001;

        edge [style = invis];

        7000 -> 7001;

        7000 -> 7002;

    }

}


clusterNode 结构的示例。

digraph {

    label = "\n 图 17-7    节点 7000 创建的 clusterState 结构";

    rankdir = LR;

    //

    node [shape = record];

    clusterState [label = " <head> clusterState | <myself> myself | currentEpoch \n 0 | state \n REDIS_CLUSTER_FAIL | size \n 0 | <nodes> nodes | ... "];

    nodes [label = " <head> nodes | <0> \"5154...2939\" | <1> \"68ee...f2ff\" | <2> \"9dfb...5c26\" "];

    node7000 [label = " <head> clusterNode | name \n \"5154...2939\" | flags \n REDIS_NODE_MASTER | configEpoch \n 0 | ip \n \"127.0.0.1\" | port \n 7000 | ... "];
    node7001 [label = " <head> clusterNode | name \n \"68ee...f2ff\"| flags \n REDIS_NODE_MASTER | configEpoch \n 0 | ip \n \"127.0.0.1\" | port \n 7001 | ... "];
    node7002 [label = " <head> clusterNode | name \n \"9dfb...5c26\"| flags \n REDIS_NODE_MASTER | configEpoch \n 0 | ip \n \"127.0.0.1\" | port \n 7002 | ... "];

    //link7000 [label = " <head> clusterLink | ctime | fd | sndbuf | rcvbuf "];

    clusterState:myself ->  node7000:head;

    clusterState:nodes -> nodes:head;

    nodes:0 -> node7000:head;
    nodes:1 -> node7001:head;
    nodes:2 -> node7002:head;

}


节点之间进行握手时的通信过程。

digraph {

    label = "\n 图 17-8    节点的握手过程";

    rankdir = LR;

    splines = polyline;

    //

    node [shape = box, height = 2];

    client [label = "客\n户\n端"];

    A [label = "节\n点\nA"];

    B [label = "节\n点\nB"];

    //

    client -> A [label = "发送命令 \n CLUSTER MEET \n <B_ip> <B_port>"];

    A -> B [label = "发送 MEET 消息"];

    A -> B [dir = back, label = "\n返回 PONG 消息"];

    A -> B [label = "\n返回 PING 消息"];

}

槽指派

图 17-9 展示了一个 slots 数组示例: 这个数组索引 0 至索引 7 上的二进制位的值都为 1 , 其余所有二进制位的值都为 0 , 这表示节点负责处理槽 0 至槽 7

digraph {

    label = "\n 图 17-9    一个 slots 数组示例";

    node [shape = record];

    slots [label = " { 字节 | 索引 | 值 } | { slots[0] | {{ 0 | 1} | { 1 | 1} | { 2 | 1 } | { 3 | 1} | { 4 | 1 } | { 5 | 1 } | { 6 | 1 } | { 7 | 1 } }} | { slots[1] ~ slots[2047] | {{ 8 | 0 } | { 9 | 0 } | { 10 | 0 } | { 11 | 0 } | { 12 | 0 } | { ... | ... } | { 16381 | 0 } | { 16382 | 0 } | { 16383 | 0 } }} "];

}

图 17-10 展示了另一个 slots 数组示例: 这个数组索引 1358910 上的二进制位的值都为 1 , 而其余所有二进制位的值都为 0 , 这表示节点负责处理槽 1358910

digraph {

    label = "\n 图 17-10    另一个 slots 数组示例";

    node [shape = record];

    slots [label = " { 字节 | 索引 | 值 } | { slots[0] | {{ 0 | 0} | { 1 | 1} | { 2 | 0 } | { 3 | 1} | { 4 | 0 } | { 5 | 1 } | { 6 | 0 } | { 7 | 0 } }} | { slots[1] | {{ 8 | 1} | { 9 | 1 } | { 10 | 1 } | { 11 | 0 } | { 12 | 0 } | { 13 | 0 } | { 14 | 0 } | { 15 | 0 }}} | { ... | ... | ... } | { slots[2047] | { { ... | ... } | { 16382 | 0 } | { 16383 | 0 }} } "];

}


节点之间互相传递槽指派信息的过程。

digraph {

    label = "\n 图 17-11    7000 告知 7001 和 7002 自己负责处理的槽";

    rankdir = LR;

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = circle];

        7000;

        7002;

        7001;

        edge [style = dashed, label = "\n我负责处理\n槽 0 至槽 5000"];

        7000 -> 7001;

        7000 -> 7002;

    }

}

digraph {

    label = "\n 图 17-12    7001 告知 7000 和 7002 自己负责处理的槽";

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = circle];

        7000;

        7002;

        7001;

        edge [style = dashed, label = "我负责处理\n槽 5001 至槽 10000"];

        7001 -> 7000;
        7001 -> 7002;

    }

}

digraph {

    label = "\n 图 17-13    7002 告知 7000 和 7001 自己负责处理的槽";

    rankdir = BT;

    subgraph cluster_a {

        label = "集群";
        labelloc = "b";

        style = dashed;

        node [shape = circle];

        7000;

        7002;

        7001;

        edge [style = dashed, label = "我负责处理\n槽 10001 至槽 16383"];

        7002 -> 7000;
        7002 -> 7001;

    }

}


clusterState 结构的 slots 数组示例。

digraph {

    label = "\n 图 17-14    clusterState 结构的 slots 数组";

    rankdir = LR;

    node [shape = record];

    clusterState [label = " clusterState | ... | <slots> slots | ... "];

    slots [label = " <head> clusterNode*[16384] | <0> 0 | <1> 1 | <2> ... | <5000> 5000 | <5001> 5001 | <5002> 5002 | <5003> ... | <10000> 10000 | <10001> 10001 | <10002> 10002 | <10003> ... | <16383> 16383 "];

    node7000 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7000 | ... "];
    node7001 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7001 | ... "];
    node7002 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7002 | ... "];

    slots:0 -> node7000:head;
    slots:1 -> node7000:head;
    slots:2 -> node7000:head;
    slots:5000 -> node7000:head;

    slots:5001 -> node7001:head;
    slots:5002 -> node7001:head;
    slots:5003 -> node7001:head;
    slots:10000 -> node7001:head;

    slots:10001 -> node7002:head;
    slots:10002 -> node7002:head;
    slots:10003 -> node7002:head;
    slots:16383 -> node7002:head;

    clusterState:slots -> slots:head;

}

在集群中执行命令

在集群中执行命令时的判断过程。

digraph {

    label = "\n 图 17-18    判断客户端是否需要转向的流程";

    node [shape = box];

    client_send_command [label = "客户端向节点发送数据库键命令", width = 7];

    get_slot_number [label = "节点计算键属于哪个槽"];

    node_handle_right_slot_or_not [label = "当前节点就是\n负责处理键所在槽的节点?", shape = diamond];

    execute_command [label = "节点执行命令"];

    return_moved [label = "节点向客户端返回一个\n MOVED 错误"];

    client_redirect [label = "客户端根据 MOVED 错误提供的信息\n转向至正确的节点"];

    //

    client_send_command -> get_slot_number;

    get_slot_number -> node_handle_right_slot_or_not;

    node_handle_right_slot_or_not -> execute_command [label = "是"];

    node_handle_right_slot_or_not -> return_moved [label = "不是"];

    return_moved -> client_redirect;

    client_redirect -> client_send_command [label = "重试"];

}


MOVED 错误引发的转向过程。

digraph {

    label = "\n 图 17-20    节点 7000 向客户端返回 MOVED 错误";

    rankdir = LR;

    splines = polyline;

    node [shape = box];

    client [label = "客户端", height = 2.5];

    node7000 [label = "节点 7000", height = 2.5];

    //

    client -> node7000 [label = "\nSET msg \"happy new year!\""];

    client -> node7000 [dir = back, label = "\nMOVED 6257 127.0.0.1:7001"];

}

digraph {

    label = "\n 图 17-21    客户端根据 MOVED 错误的指示转向至节点 7001";

    rankdir = LR;

    splines = polyline;

    node [shape = box];

    client [label = "客户端", height = 2.5];

    node7001 [label = "节点 7001", height = 2.5];

    //

    client -> node7001 [label = "\n转向"];

    client -> node7001 [label = "\nSET msg \"happy new year!\""];

    client -> node7001 [dir = back, label = "\nOK"];

}


集群节点为记录键所在槽而创建的跳跃表。

digraph {

    rankdir = LR;

    node [shape = record, width = "0.5"];

    //

    l [label = " <header> header | <tail> tail | level \n 5 | length \n 3 "];

    subgraph cluster_nodes {

        style = invisible;

        header [label = " <l32> L32 | ... | <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 "];

        bw_null [label = "NULL", shape = plaintext];

        level_null [label = "NULL", shape = plaintext];

        A [label = " <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 1337.0 | StringObject \n \"book\" "];

        B [label = " <l2> L2 | <l1> L1 | <backward> BW | 2022.0 | StringObject \n \"date\" "];

        C [label = " <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 3347.0 | StringObject \n \"lst\" "];

    }

    subgraph cluster_nulls {

        style = invisible;

        n1 [label = "NULL", shape = plaintext];
        n2 [label = "NULL", shape = plaintext];
        n3 [label = "NULL", shape = plaintext];
        n4 [label = "NULL", shape = plaintext];
        n5 [label = "NULL", shape = plaintext];

    }

    //

    l:header -> header;
    l:tail -> C;

    /*
    // 移除连接线的 span 分值
    header:l32 -> level_null [label = "0"];
    header:l5 -> C:l5 [label = "3"];
    header:l4 -> A:l4 [label = "1"];
    header:l3 -> A:l3 [label = "1"];
    header:l2 -> A:l2 [label = "1"];
    header:l1 -> A:l1 [label = "1"];

    A:l4 -> C:l4 [label = "2"];
    A:l3 -> C:l3 [label = "2"];
    A:l2 -> B:l2 [label = "1"];
    A:l1 -> B:l1 [label = "1"];

    B:l2 -> C:l2 [label = "1"];
    B:l1 -> C:l1 [label = "1"];

    C:l5 -> n5 [label = "0"];
    C:l4 -> n4 [label = "0"];
    C:l3 -> n3 [label = "0"];
    C:l2 -> n2 [label = "0"];
    C:l1 -> n1 [label = "0"];
    */

    header:l32 -> level_null;
    header:l5 -> C:l5;
    header:l4 -> A:l4;
    header:l3 -> A:l3;
    header:l2 -> A:l2;
    header:l1 -> A:l1;

    A:l4 -> C:l4;
    A:l3 -> C:l3;
    A:l2 -> B:l2;
    A:l1 -> B:l1;

    B:l2 -> C:l2;
    B:l1 -> C:l1;

    C:l5 -> n5;
    C:l4 -> n4;
    C:l3 -> n3;
    C:l2 -> n2;
    C:l1 -> n1;
    bw_null -> A:backward -> B:backward -> C:backward [dir = back];

    label = "\n 图 17-23    节点 7000 的 slots_to_keys 跳跃表";
}

重新分片

将一个键从一个节点迁移到另一个节点的过程。

digraph {

    label = "\n 图 17-24    迁移键的过程";

    rankdir = LR;

    splines = polyline;

    //

    node [shape = box, height = 3];

    client [label = "redis-trib"];

    source [label = "源\n节\n点"];

    target [label = "目\n标\n节\n点"];

    //

    client -> source [label = "1) 发送命令 \n CLUSTER GETKEYSINSLOT <slot> <count>"];

    client -> source [dir = back, label = "\n 2) 返回最多 count 个属于槽 slot 的键"];

    client -> source [label = "\n 3) 对于每个返回键\n向源节点发送一个 MIGRATE 命令"];

    source -> target [label = "\n 4) 根据 MIGRATE 命令的指示 \n 将键迁移至目标节点"];

}


重新分片的整个过程。

digraph {

    label = "\n 图 17-25    对槽 slot 进行重新分片的过程"

    node [shape = box]

    start [label = "开始对槽 slot 进行重新分片"]

    send_importing_to_target [label = "目标节点准备导入槽 slot 的键值对"];

    send_migrating_to_source [label = "源节点准备迁移槽 slot 的键值对"];

    start -> send_importing_to_target -> send_migrating_to_source;

    contain_slot_key_or_not [shape = diamond, label = "源节点是否保存了\n属于槽 slot 的键?"]

    migrate [label = "将这些键\n全部迁移至目标节点"]

    assign [label = "将槽 slot 指派给目标节点"]

    done [label = "完成对槽 slot 的重新分片"]


    send_migrating_to_source -> contain_slot_key_or_not

    contain_slot_key_or_not -> migrate [label = "是"]

    migrate -> assign

    contain_slot_key_or_not -> assign [label = "否"]

    assign -> done



}

ASK 错误

源节点判断是否需要向客户端发送 ASK 错误的整个过程。

digraph {

    label = "\n 图 17-26    判断是否发送 ASK 错误的过程";

    //

    node [shape = box];

    send_command [label = "客户端向源节点发送关于键 key 的命令"];

    key_in_or_not [label = "键 key 是否存在于\n源节点的数据库?", shape = diamond];

    execute_command [label = "节点执行客户端发送的命令"];

    reshaing_or_not [label = "节点正在迁移槽 i ?", shape = diamond];

    ask_redirect [label = "向客户端返回 ASK 错误"];

    //

    send_command -> key_in_or_not;

    key_in_or_not -> execute_command [label = "是"];

    key_in_or_not -> reshaing_or_not [label = "否"];

    reshaing_or_not -> ask_redirect [label = "是\n键 key 有可能在目标节点"];

    reshaing_or_not -> execute_command [label = "否\n键 key 不存在"];

}


importing_slots_from 结构以及 migrating_slots_to 结构的示例。

digraph {

    label = "\n 图 17-27    节点 7003 的 importing_slots_from 数组";

    rankdir = LR;

    //

    node [shape = record];

    clusterState [label = " clusterState | ... | <slots> importing_slots_from | ... "];

    slots [label = " <head> clusterNode*[16384] | <0> 0 | <1> 1 | <2> 2 | <3> ... | <16198> 16198 | <15002> ... | <16381> 16381 | <16382> 16382 | <16383> 16383 "];

    node7002 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7002 | ... "];

    node [shape = plaintext, label = "NULL"];

    //

    slots:0 -> null0;
    slots:1 -> null1;
    slots:2 -> null2;
    slots:16198 -> node7002:head [minlen = 2];
    slots:16381 -> null16381;
    slots:16382 -> null16382;
    slots:16383 -> null16383;

    clusterState:slots -> slots:head;

}

digraph {

    label = "\n 图 17-28    节点 7002 的 migrating_slots_to 数组";

    rankdir = LR;

    //

    node [shape = record];

    clusterState [label = " clusterState | ... | <slots> migrating_slots_to | ... "];

    slots [label = " <head> clusterNode*[16384] | <0> 0 | <1> 1 | <2> 2 | <3> ... | <16198> 16198 | <15002> ... | <16381> 16381 | <16382> 16382 | <16383> 16383 "];

    node7003 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7003 | ... "];

    node [shape = plaintext, label = "NULL"];

    //

    slots:0 -> null0;
    slots:1 -> null1;
    slots:2 -> null2;
    slots:16198 -> node7003:head [minlen = 2];
    slots:16381 -> null16381;
    slots:16382 -> null16382;
    slots:16383 -> null16383;

    clusterState:slots -> slots:head;

}


客户端根据 ASK 错误进行转向的过程。

digraph {

    label = "\n 图 17-29    客户端接收到节点 7002 返回的 ASK 错误";

    rankdir = LR;

    splines = polyline;

    //

    node [shape = box, height = 2];

    client [label = "客户端"];

    node7002 [label = "节点 7002"];

    //

    client -> node7002 [label = "GET \"love\""];

    client -> node7002 [dir = back, label = "\nASK 16198 127.0.0.1:7003"];

}

digraph {

    label = "\n 图 17-30    客户端转向至节点 7003";

    rankdir = LR;

    splines = polyline

    //

    node [shape = box, height = 3];

    client [label = "客户端"];

    node7003 [label = "节点 7003"];

    //

    client -> node7003 [label = "转向"];

    client -> node7003 [label = "ASKING"];

    client -> node7003 [label = "\n GET \"love\""];

    client -> node7003 [dir = back, label = "\n\"you get the key 'love'\""];

}


节点判断是否执行客户端命令的过程。

digraph {

    label = "\n 图 17-31    节点判断是否执行客户端命令的过程";

    node [shape = box];

    //

    send_command [label = "客户端向节点发送关于槽 i 的命令"];

    node_handle_slot_or_not [shape = diamond, label = "槽 i 是否指派给了节点?"];

    exec_command [label = "节点执行客户端发送的命令"];

    importing_slot_or_not [shape = diamond, label = "节点是否正在导入槽 i ?"];

    client_get_asking_flag_or_not [shape = diamond, label = "客户端是否带有 \n ASKING 标识?"];

    return_moved [label = "节点向客户端返回 MOVED 命令"];

    //

    send_command -> node_handle_slot_or_not;

    node_handle_slot_or_not -> exec_command [label = "是"];

    node_handle_slot_or_not -> importing_slot_or_not [label = "否"];

    importing_slot_or_not -> client_get_asking_flag_or_not [label = "是"];

    client_get_asking_flag_or_not -> exec_command [label = "是"];

    importing_slot_or_not -> return_moved [label = "否"];

    client_get_asking_flag_or_not -> return_moved [label = "否"];

}

复制与故障转移

Redis 集群对失效节点进行故障转移的过程。

digraph {

    label = "\n 图 17-32    设置节点 7004 和节点 7005 成为节点 7000 的从节点";

    rankdir = LR;

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = doublecircle]

        7000;

        7001;

        7002;

        7003;

        node [shape = circle]

        7005;

        7004;

        edge [dir = back, label = "复制"]

        7000 -> 7004

        7000 -> 7005

        edge [style = invis, label = ""]

        7002 -> 7003

    }

}

digraph {

    label = "\n 图 17-33    节点 7004 成为新的主节点";

    rankdir = LR;

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = doublecircle];

        7000 [style = dashed]

        7001;

        7002;

        7003;

        7004;

        node [shape = circle]

        7005;

        edge [dir = back, label = "复制"]

        7004 -> 7005

        edge [style = invis, label = ""]

        7000 -> 7001

        7002 -> 7003

    }

}

digraph {

    label = "\n 图 17-34    重新上线的节点 7000 成为节点 7004 的从节点";

    rankdir = LR;

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = doublecircle];

        7001;

        7002;

        7003;

        7004;

        node [shape = circle]

        7000

        7005;

        edge [dir = back, label = "复制"]

        7004 -> 7005

        7004 -> 7000

        edge [style = invis, label = ""]

        7001 -> 7002 -> 7003

    }

}


节点的下线报告示例。

digraph {

    label = "\n 图 17-38    节点 7000 的下线报告"

    rankdir = LR

    node [shape = record]

    node7000 [label = " <head> clusterNode | ... | flags \n REDIS_NODE_MASTER \n & \n REDIS_NODE_PFAIL | ip \n \"127.0.0.1\" | port \n 7000 | <fail_reports> fail_reports | ... "]

    node7002 [label = " <head> clusterNode | ... | flags \n REDIS_NODE_MASTER | ip \n \"127.0.0.1\" | port \n 7002 | ... "]

    node7003 [label = " <head> clusterNode | ... | flags \n REDIS_NODE_MASTER | ip \n \"127.0.0.1\" | port \n 7003 | ... "]

    report7002 [label = " <head> clusterNodeFailReport | <pnode> node | time \n 1390525039000"]

    report7003 [label = " <head> clusterNodeFailReport | <pnode> node | time \n 1390525039321"]

    node7000:fail_reports -> report7002:head -> report7003:head

    report7002:pnode -> node7002:head
    report7003:pnode -> node7003:head

}


节点向集群广播 FAIL 消息。

digraph {

    label = "\n 图 17-39    节点 7001 向集群广播 FAIL 消息"

    rankdir = LR;

    subgraph cluster_a {

        label = "集群";

        style = dashed;

        node [shape = doublecircle]

        7000 [style = dashed]

        7001;

        7002;

        7003;

        node [shape = circle]

        7004;

        7005;

        edge [dir = back, label = "复制"]

        7000 -> 7004

        7000 -> 7005

        edge [dir = forward, label = "发送 FAIL 消息"]

        7001 -> 7002
        7001 -> 7003
        7001 -> 7004
        7001 -> 7005

    }

}

消息

集群中的节点通过发送消息来将一个节点标记为下线的过程。

digraph {

    label = "\n 图 17-42    节点 7001 将节点 7000 标记为已下线";

    node [shape = circle];

    subgraph cluster_cluster {

        label = "集群";

        node7000 [label = "7000", style = dashed];
        node7001 [label = "7001"];
        node7002 [label = "7002"];
        node7003 [label = "7003"];

        node7001 -> node7000 [label = "标记为\n已下线"];

        edge [style = invis];
        node7001 -> node7002 [label = "发送 FAIL 消息"];
        node7001 -> node7003 [label = "发送 FAIL 消息"];

        style = dashed;

    }

}

digraph {

    label = "\n 图 17-43    节点 7001 向集群广播 FAIL 消息";

    node [shape = circle];

    subgraph cluster_cluster {

        label = "集群";

        node7000 [label = "7000", style = dashed];
        node7001 [label = "7001"];
        node7002 [label = "7002"];
        node7003 [label = "7003"];

        node7001 -> node7000 [style = invis];

        edge [label = "发送 \nFAIL 消息"];

        node7001 -> node7002;
        node7001 -> node7003;

        style = dashed;

    }

}

digraph {

    label = "\n 图 17-44    节点 7002 和节点 7003 也将节点 7000 标记为已下线";

    node [shape = circle];

    subgraph cluster_cluster {

        label = "集群";

        node7003 [label = "7003"];
        node7002 [label = "7002"];
        node7001 [label = "7001"];
        node7000 [label = "7000", style = dashed];

        node7001 -> node7000 [style = invis];

        edge [label = "标记为\n已下线"];

        node7002 -> node7000;
        node7003 -> node7000;

        style = dashed;

    }

}


接收到 PUBLISH 命令的节点向集群中的其他节点发送 PUBLISH 消息。

digraph {

    label = "\n 图 17-45    接收到 PUBLISH 命令的节点 7000 向集群广播 PUBLISH 消息";

    node [shape = circle];

    rankdir = LR;

    client [label = "客户端"];

    subgraph cluster_cluster {

        label = "集群";

        node7003 [label = "7003"];
        node7002 [label = "7002"];
        node7001 [label = "7001"];
        node7000 [label = "7000"];


        edge [label = "发送 \n PUBLISH \n 消息"];

        node7000 -> node7001;
        node7000 -> node7002;
        node7000 -> node7003;

        style = dashed;

    }

        client -> node7000 [label = "发送 \n PUBLISH \n 命令"];
}


PUBLISH 消息中包含的 clusterMsgDataPublish 结构示例。

digraph {

    label = "\n 图 17-46    clusterMsgDataPublish 结构示例";

    node [shape = record];

    rankdir = LR;

    clusterMsgDataPublish [label = " clusterMsgDataPublish | channel_len \n 7 | message_len \n 5 | <bulk_data> bulk_data "];

    bulk_data [label = " { 'n' | 'e' | 'w' | 's' | '.' | 'i' | 't' | 'h' | 'e' | 'l' | 'l' | 'o' } "];

    clusterMsgDataPublish:bulk_data -> bulk_data;

}

第 18 章: 发布与订阅

客户端订阅频道。

digraph {

    label = "\n 图 18-1    news.it 频道和它的三个订阅者";

    rankdir = BT;

    //

    news_it [label = "news.it 频道", shape = box, width = 3.0];

    client_1 [label = "客户端 A"];
    client_2 [label = "客户端 B"];
    client_3 [label = "客户端 C"];

    //

    edge [label = "订阅"];

    client_1 -> news_it;
    client_2 -> news_it;
    client_3 -> news_it;
}

客户端向频道发送消息, 消息被传递至各个订阅者。

digraph {

    label = "\n 图 18-2    向 news.it 频道发送消息";

    //

    publish [label = "PUBLISH \"news.it\" \"hello\"", shape = plaintext];

    news_it [label = "news.it 频道", shape = box, width = 3.0];

    client_1 [label = "客户端 A"];
    client_2 [label = "客户端 B"];
    client_3 [label = "客户端 C"];

    //

    edge [style = dashed];

    publish -> news_it;

    edge [label = "\"hello\""];

    news_it -> client_1;
    news_it -> client_2;
    news_it -> client_3;

}


客户端订阅模式。

digraph {

    label = "\n 图 18-3     频道和模式的订阅状态";

    rankdir = BT;

    //

    news_et [label = "news.et 频道", shape = box, width = 3.0];

    news_it [label = "news.it 频道", shape = box, width = 3.0];

    news_iet [label = "news.[ie]t 模式", shape = hexagon, width = 3.0];

    node [shape = ellipse];

    client_1 [label = "客户端 A"];
    client_2 [label = "客户端 B"];
    client_3 [label = "客户端 C"];
    client_4 [label = "客户端 D"];

    //

    client_1 -> news_it [label = "订阅"];

    client_2 -> news_et [label = "订阅"];

    news_iet -> news_et [dir = back, label = "匹配"];
    news_iet -> news_it [dir = back, label = "匹配"];

    edge [label = "订阅"];
    client_3 -> news_iet;
    client_4 -> news_iet;
}

客户端向频道发送消息, 消息被传递给正在订阅匹配模式的订阅者。

digraph {

    label = "\n 图 18-4    将消息发送给频道的订阅者和匹配模式的订阅者";

    rankdir = BT;

    //

    news_et [label = "news.et 频道", shape = box, width = 3.0];

    news_it [label = "news.it 频道", shape = box, width = 3.0];

    news_iet [label = "news.[ie]t 模式", shape = hexagon, width = 3.0];

    publish [label = "PUBLISH \"news.it\" \"hello\"", shape = plaintext];

    node [shape = ellipse];

    client_1 [label = "客户端 A"];
    client_2 [label = "客户端 B"];
    client_3 [label = "客户端 C"];
    client_4 [label = "客户端 D"];

    //


    client_1 -> news_it [dir = back, label = "\"hello\"", style = dashed];

    client_2 -> news_et [label = "订阅"];

    news_iet -> news_et [dir = back, label = "匹配"];
    news_iet -> news_it [dir = back, label = "匹配", style = dashed];

    edge [dir = back, label = "\"hello\"", style = dashed];
    client_3 -> news_iet;
    client_4 -> news_iet;

    news_it -> publish [dir = back, style = dashed, label =""];
}

另一个模式被匹配的例子。

digraph {

    label = "\n 图 18-5    将消息发送给频道的订阅者和匹配模式的订阅者";

    rankdir = BT;

    //

    news_et [label = "news.et 频道", shape = box, width = 3.0];

    news_it [label = "news.it 频道", shape = box, width = 3.0];

    news_iet [label = "news.[ie]t 模式", shape = hexagon, width = 3.0];

    publish [label = "PUBLISH \"news.et\" \"world\"", shape = plaintext];

    node [shape = ellipse];

    client_1 [label = "客户端 A"];
    client_2 [label = "客户端 B"];
    client_3 [label = "客户端 C"];
    client_4 [label = "客户端 D"];

    //


    client_1 -> news_it [label = "订阅"];

    client_2 -> news_et [dir = back, label = "\"world\"", style = dashed];

    news_iet -> news_et [dir = back, label = "匹配", style = dashed];
    news_iet -> news_it [dir = back, label = "匹配"];

    edge [dir = back, label = "\"world\"", style = dashed];
    client_3 -> news_iet;
    client_4 -> news_iet;

    news_et -> publish [dir = back, style = dashed, label =""];
}


pubsub_channels 字典示例。

digraph {

    label = "\n 图 18-6    一个 pubsub_channels 字典示例";

    rankdir = LR;

    //

    node [shape = record];

    pubsub_channels [label = " pubsub_channels | <news_it> \"news.it\" | <news_sport> \"news.sport\" | <news_business> \"news.business\" ", height = 3, width = 2.2];

    client_1 [label = "client-1"];
    client_2 [label = "client-2"];
    client_3 [label = "client-3"];
    client_4 [label = "client-4"];
    client5 [label = "client-5"];
    client6 [label = "client-6"];

    //

    pubsub_channels:news_it -> client_1; client_1 -> client_2; client_2 -> client_3;

    pubsub_channels:news_sport -> client_4;

    pubsub_channels:news_business -> client5 -> client6;

}


pubsubPattern 结构示例。

digraph {

    label = "\n 图 18-10    pubsubPattern 结构示例";

    rankdir = LR;

    //

    node [shape =record];

    pubsubPattern [label = " pubsubPattern | client \n client-9 | pattern \n \"news.*\" "];
}

pubsub_patterns 链表的示例。

digraph {

    label = "\n 图 18-11    pubsub_patterns 链表示例";

    rankdir = LR;

    //

    node [shape = record];

    redisServer [label = "redisServer | ... | <pubsub_patterns> pubsub_patterns | ...", height = 2.2, width = 2.2];

    all_music [label = " pubsubPattern | client \n client-7 | pattern \n \"music.*\" "];

    all_book [label = " pubsubPattern | client \n client-8 | pattern \n \"book.*\" "];

    all_news [label = " pubsubPattern | client \n client-9 | pattern \n \"news.*\" "];

    //

    redisServer:pubsub_patterns -> all_music;

    all_music -> all_book;

    all_book -> all_news;
}

第 19 章: 事务

在一个事务开始之后, 服务器判断命令是该入队还是该执行的过程。

digraph enque_or_execute {

    label = "\n图 19-1    服务器判断命令是该入队还是该执行的过程";

    node [shape = box];

    //

    command_in [label = "服务器接到来自客户端的命令"];

    in_transaction_or_not [label = "这个客户端正处于事务状态?", shape = diamond];

    not_exec_and_discard [label = "这个命令是否\nEXEC 、 DISCARD 、\nWATCH 或 MULTI ?", shape = diamond];

    enqueu_command [label = "将命令放入事务队列"];

    return_enqueued [label = "向客户端返回 QUEUED"];

    exec_command [label = "执行这个命令"];

    return_command_result [label = "向客户端返回命令的执行结果"];

    //

    command_in -> in_transaction_or_not;

    in_transaction_or_not -> not_exec_and_discard [label = "是"];

    not_exec_and_discard -> enqueu_command [label = "否"];

    not_exec_and_discard -> exec_command [label = "是"];

    in_transaction_or_not -> exec_command [label = "否"];

    exec_command -> return_command_result;

    enqueu_command -> return_enqueued;
}


事务状态。

digraph {

    label = "\n 图 19-2    事务状态";

    rankdir = LR;

    node [shape = record];

    //redisClient [label = " <head> redisClient | ... | <mstate> mstate | ... "];

    multiState [label = " <head> multiState | <commands> commands | count \n 4 "];

    commands [label = " <head> multiCmd[4] | <0> [0] | <1> [1] | <2> [2] | <3> [3] "];

    multiCmd0 [label = " <head> multiCmd | <argv> argv | argc \n 3 | <cmd> cmd "];

    multiCmd1 [label = " <head> multiCmd | <argv> argv | argc \n 2 | <cmd> cmd "];

    multiCmd2 [label = " <head> multiCmd | <argv> argv | argc \n 3 | <cmd> cmd "];

    multiCmd3 [label = " <head> multiCmd | <argv> argv | argc \n 2 | <cmd> cmd "];

    //redisClient:mstate -> multiState:head;

    multiState:commands -> commands:head;

    commands:0 -> multiCmd0:head;
    commands:1 -> multiCmd1:head;
    commands:2 -> multiCmd2:head;
    commands:3 -> multiCmd3:head;

    argv0 [label = " robj*[3] | { StringObject \n \"SET\" | StringObject \n \"name\" | StringObject \n \"Practical Common Lisp\" } "];
    cmd0 [label = " setCommand ", shape = plaintext];

    multiCmd0:argv -> argv0;
    multiCmd0:cmd -> cmd0;

    argv1 [label = " robj*[2] | { StringObject \n \"GET\" | StringObject \n \"name\" } "];
    cmd1 [label = " getCommand ", shape = plaintext];

    multiCmd1:argv -> argv1;
    multiCmd1:cmd -> cmd1;

    argv2 [label = " robj*[3] | { StringObject \n \"SET\" | StringObject \n \"author\" | StringObject \n \"Peter Seibel\" } "];
    cmd2 [label = " setCommand ", shape = plaintext];

    multiCmd2:argv -> argv2;
    multiCmd2:cmd -> cmd2;

    argv3 [label = " robj*[2] | { StringObject \n \"GET\" | StringObject \n \"author\" } "];
    cmd3 [label = " getCommand ", shape = plaintext];

    multiCmd3:argv -> argv3;
    multiCmd3:cmd -> cmd3;

}


watched_keys 字典示例。

digraph {

    label = "\n 图 19-5    watched_keys 字典";

    rankdir = LR;

    node [shape = record];

    //

    watched_keys [label = "watched_keys | <name> \"name\" | <age> \"age\" | <address> \"address\" ", width = 2.0, height = 3.0];

    //

    name_c2 [label = "c2"];
    address_c2 [label = "c2"];
    name_c10086 [label = "c10086"];
    age_c10086 [label = "c10086"];

    //

    watched_keys:name -> c1 -> name_c2 -> name_c10086;
    watched_keys:age -> c3 -> age_c10086;
    watched_keys:address -> address_c2 -> c4;

}


服务器判断是否执行事务的过程。

digraph {

    label = "\n 图 19-6    服务器判断是否执行事务的过程";

    node [shape = box];

    exec [label = "客户端向服务器发送 EXEC 命令"];

    dirty_cas_or_not [label = "客户端的 \n REDIS_DIRTY_CAS \n 标识是否已经打开?", shape = diamond];

    reject [label = "拒绝执行客户端提交的事务"];

    ok [label = "执行客户端提交的事务"];

    exec -> dirty_cas_or_not;

    dirty_cas_or_not -> reject [label = "是"];

    dirty_cas_or_not -> ok [label = "否"];

}

第 20 章: Lua 脚本

创建并修改 Lua 环境

服务器与 Lua 环境。

digraph {

    label = "\n图 20-1    服务器状态中的 Lua 环境";

    rankdir = LR;

    node [shape = record];

    server [label = "redisServer | ... | <lua> lua | ...", width = 2.0, height = 2.0];

    lua [label = "Lua 环境", shape = circle];

    server:lua -> lua;

}

Lua 环境协作组件

Lua 脚本执行 Redis 命令时的通讯步骤。

digraph {

    label = "\n图 20-2    Lua 脚本执行 Redis 命令时的通讯步骤";

    rankdir = LR;

    node [shape = record, height = 2.0];

    splines = polyline;

    //

    lua [label = "L\nu\na\n环\n境"];
    fake_client [label = "伪\n客\n户\n端"];
    eval [label = "命\n令\n执\n行\n器"];

    lua -> fake_client [label = "1) 传送 redis.call 函数\n想要执行的 Redis 命令"]
    fake_client -> eval [label = "2) 将命令传给执行器执行"];
    lua -> fake_client [dir = back, label = "\n4) 将命令结果传回给 Lua 环境"];
    fake_client -> eval [dir = back, label = "\n3) 返回命令的执行结果"];
}

作为例子, 图 20-3 展示了 Lua 脚本在执行以下命令时:

redis> EVAL "return redis.call('DBSIZE')" 0
(integer) 10086

Lua 环境、伪客户端、命令执行器三者之间的通讯过程。

digraph {

    label = "\n图 20-3    Lua 脚本执行 DBSIZE 命令时的通讯步骤";

    rankdir = LR;

    node [shape = record, height = 2.0];

    splines = polyline;

    //

    lua [label = "L\nu\na\n环\n境"];
    fake_client [label = "伪\n客\n户\n端"];
    eval [label = "命\n令\n执\n行\n器"];

    lua -> fake_client [label = "1) 传送 DBSIZE 请求"]
    fake_client -> eval [label = "2) 将 DBSIZE 命令传给执行器执行"];
    lua -> fake_client [dir = back, label = "\n4) 将命令结果 10086 传回给 Lua 环境"];
    fake_client -> eval [dir = back, label = "\n3) 返回命令的执行结果 10086"];
}


lua_scripts 字典示例。

digraph {

    label = "\n 图 20-4    lua_scripts 字典示例";

    rankdir = LR;

    node [shape = record];

    //

    lua_scripts [label = "lua_scripts | ... | <1> \"2f31ba2bb6d6a0f42cc159d2e2dad55440778de3\" | <2> \"a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9\" | <3> \"4475bfb5919b5ad16424cb50f74d4724ae833e72\" | ... "];

    node [shape = plaintext];

    one [label = "\"return 'hi'\""];
    two [label = "\"return 1+1\""];
    three [label = "\"return 2*2\""];

    lua_scripts:1 -> one;
    lua_scripts:2 -> two;
    lua_scripts:3 -> three;
}

脚本管理命令的实现

带有超时处理钩子的脚本的运行过程。

digraph {

    label = "\n图 20-8    带有超时处理钩子的脚本的执行过程";

    node [shape = box];

    start_script [label = "开始执行脚本", width = 3.5];

    finish_or_not [label = "脚本执行完毕?", shape = diamond];

    finish [label = "返回执行结果"];

    overtime_or_not [label = "定期调用钩子\n检查脚本\n是否已超时运行?", shape = diamond];

    kill_or_shutdown_arrive_or_not [label = "有 SCRIPT KILL\n或者\nSHUTDOWN NOSAVE\n到达?", shape = diamond];

    stop_script [label = "执行 SCRIPT KILL \n 或者 SHUTDOWN"];

    keep_running [label = "继续执行脚本"];

    //

    start_script -> finish_or_not;

    finish_or_not -> finish [label = "是"];

    finish_or_not -> overtime_or_not [label = "否"];

    overtime_or_not -> kill_or_shutdown_arrive_or_not [label = "是"];

    overtime_or_not -> keep_running [label = "否"];

    kill_or_shutdown_arrive_or_not -> stop_script [label = "是"];

    kill_or_shutdown_arrive_or_not -> keep_running [label = "否"];

    keep_running -> finish_or_not;

}

脚本复制

将脚本命令传播给从服务器。

digraph {

    label = "\n 图 20-9    将脚本命令传播给从服务器";

    rankdir = LR;

    //

    node [shape = circle, width = 1.0];

    client [label = "客户端", width = 1.3];

    master [label = "主服务器", width = 1.3];

    slave1 [label = "从服务器 1"];

    slave2 [label = "从服务器 2"];

    more [label = "...", shape = plaintext];

    slaveN [label = "从服务器 N"];

    //

    edge [label = "EVAL \n 或者 \n SCRIPT FLUSH \n 或者 \n SCRIPT LOAD"];

    client -> master;
    master -> slave1;
    master -> slave2;
    master -> more;
    master -> slaveN;

}


主服务器判断该传播 EVAL 命令还是 EVALSHA 命令的流程。

digraph {

    label = "\n 图 20-12    主服务器判断传播 EVAL 还是 EVALSHA 的过程";

    node [shape = box];

    command [label = " 主服务器在本机执行完命令 \n EVALSHA <sha1> <numkeys> [key ...] [arg ...] "];

    sha1_exists_in_scriptcache_or_not [label = "校验和 sha1 是否存在于 \n repl_scriptcache_dict 字典?", shape = diamond];

    propagate_evalsha [label = "传播 \n EVALSHA <sha1> <numkeys> [key ...] [arg ...]"];

    convert_evalsha_to_eval [label = "将 EVALSHA 命令转换成等价的 EVAL 命令"];

    propagate_eval [label = "传播 \n EVAL <script> <numkeys> [key ...] [arg ...]"];

    add_sha1_to_scriptcache [label = "将 sha1 添加到 \n repl_scriptcache_dict 字典"];

    //

    command -> sha1_exists_in_scriptcache_or_not;

    sha1_exists_in_scriptcache_or_not -> propagate_evalsha [label = "是"];

    sha1_exists_in_scriptcache_or_not -> convert_evalsha_to_eval [label = "否"];

    convert_evalsha_to_eval -> propagate_eval;

    propagate_eval -> add_sha1_to_scriptcache;

}

第 21 章: 排序

SORT <key> 命令的实现

Redis 服务器在执行 SORT number 时创建的数据结构。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u } } "];
    }

   label = "\n 图 21-1    命令为排序 numbers 列表而创建的数组";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_numbers {

        label = "numbers 链表"

        style = dashed;

        one [label = "StringObject \n \"1\""];
        two [label = "StringObject \n \"2\""];
        three [label = "StringObject \n \"3\""];

        three -> one -> two;

    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u } } "];
    }
   array:obj0 -> three;
   array:obj1 -> one;
   array:obj2 -> two;

   label = "\n 图 21-2    将 obj 指针指向列表的各个项";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_numbers {

        label = "numbers 链表"

        style = dashed;

        one [label = "StringObject \n \"1\""];
        two [label = "StringObject \n \"2\""];
        three [label = "StringObject \n \"3\""];

        three -> one -> two;

    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u.score \n 3.0 } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u.score \n 1.0 } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u.score \n 2.0 } } "];
    }
   array:obj0 -> three;
   array:obj1 -> one;
   array:obj2 -> two;

   label = "\n 图 21-3    设置数组项的 u.score 属性";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_numbers {

        label = "numbers 链表"

        style = dashed;

        one [label = "StringObject \n \"1\""];
        two [label = "StringObject \n \"2\""];
        three [label = "StringObject \n \"3\""];

        three -> one -> two;

    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u.score \n 1.0 } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u.score \n 2.0 } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u.score \n 3.0 } } "];
    }
   array:obj0 -> one;
   array:obj1 -> two;
   array:obj2 -> three;

   label = "\n 图 21-4    排序后的数组";

}

ALPHA 选项的实现

服务器在执行 SORT fruits ALPHA 时创建的数据结构。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u } } "];
    }
   array:obj0 -> apple;
   array:obj1 -> cherry;
   array:obj2 -> banana;

   label = "\n 图 21-5    将 obj 指针指向集合的各个元素";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u } } "];
    }
   array:obj0 -> apple;
   array:obj1 -> banana;
   array:obj2 -> cherry;

   label = "\n 图 21-6    按集合元素进行排序后的数组";

}

ASC 选项和 DESC 选项的实现

图 21-7 展示了 SORT 命令在对 numbers 列表执行升序排序时所创建的数组。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_numbers {

        label = "numbers 链表"

        style = dashed

        one [label = "StringObject \n \"1\""];
        two [label = "StringObject \n \"2\""];
        three [label = "StringObject \n \"3\""];

        three -> one -> two;

    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u.score \n 1.0 } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u.score \n 2.0 } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u.score \n 3.0 } } "];
    }
   array:obj0 -> one;
   array:obj1 -> two;
   array:obj2 -> three;

   label = "\n 图 21-7    执行升序排序的数组";

}

图 21-8 展示了 SORT 命令在对 numbers 列表执行降序排序时所创建的数组。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_numbers {

        label = "numbers 链表"

        style = dashed;

        one [label = "StringObject \n \"1\""];
        two [label = "StringObject \n \"2\""];
        three [label = "StringObject \n \"3\""];

        three -> one -> two;

    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u.score \n 3.0 } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u.score \n 2.0 } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u.score \n 1.0 } } "];
    }
   array:obj0 -> three;
   array:obj1 -> two;
   array:obj2 -> one;

   label = "\n 图 21-8    执行降序排序的数组";

}

BY 选项的实现

服务器在执行 SORT fruits BY *-price 时创建的数据结构。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u } } "];
    }
   array:obj0 -> apple;
   array:obj1 -> cherry;
   array:obj2 -> banana;

   label = "\n 图 21-9    将 obj 指针指向集合的各个元素";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u.score \n 8.0 } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u.score \n 7.0 } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u.score \n 5.5 } } "];
    }
   array:obj0 -> apple;
   array:obj1 -> cherry;
   array:obj2 -> banana;

   label = "\n 图 21-10    根据权重键的值设置数组项的 u.score 属性";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u.score \n 5.5 } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u.score \n 7.0 } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u.score \n 8.0 } } "];
    }
   array:obj0 -> banana;
   array:obj1 -> cherry;
   array:obj2 -> apple;

   label = "\n 图 21-11    根据 u.score 属性进行排序之后的数组";

}

带有 ALPHA 选项的 BY 选项的实现

服务器执行 SORT fruits BY *-id ALPHA 时创建的数据结构。

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | u } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | u } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | u } } "];
    }
   array:obj0 -> apple;
   array:obj1 -> cherry;
   array:obj2 -> banana;

   label = "\n 图 21-12    将 obj 指针指向集合的各个元素";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | <cmpobj0> u.cmpobj } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | <cmpobj1> u.cmpobj } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | <cmpobj2> u.cmpobj } } "];
    }
   array:obj0 -> apple;
   array:obj1 -> cherry;
   array:obj2 -> banana;

   apple_id [label = "StringObject \n \"FRUIT-25\""];
   banana_id [label = "StringObject \n \"FRUIT-79\""];
   cherry_id [label = "StringObject \n \"FRUIT-13\""];

   array:cmpobj0 -> apple_id;
   array:cmpobj1 -> cherry_id;
   array:cmpobj2 -> banana_id;


   label = "\n 图 21-13    将 u.cmpobj 指针指向权重键";

}

digraph {

    rankdir = LR;

    node [shape = record];

    subgraph cluster_fruits {

        label = "fruits 集合";

        style = dashed;

        apple [label = "StringObject \n \"apple\""];
        banana [label = "StringObject \n \"banana\""];
        cherry [label = "StringObject \n \"cherry\""];

        apple -> cherry -> banana [style = invis];
    }

    subgraph cluster_array {

        style = invis;

        array [label = " array | { <array0> array[0] \n redisSortObject | { <obj0> obj | <cmpobj0> u.cmpobj } } | { <array1> array[1] \n redisSortObject | { <obj1> obj | <cmpobj1> u.cmpobj } } | { <array2> array[2] \n redisSortObject | { <obj2> obj | <cmpobj2> u.cmpobj } } "];
    }
   array:obj0 -> cherry;
   array:obj1 -> apple;
   array:obj2 -> banana;

   apple_id [label = "StringObject \n \"FRUIT-25\""];
   banana_id [label = "StringObject \n \"FRUIT-79\""];
   cherry_id [label = "StringObject \n \"FRUIT-13\""];

   array:cmpobj0 -> cherry_id;
   array:cmpobj1 -> apple_id;
   array:cmpobj2 -> banana_id;


   label = "\n 图 21-14    按 u.cmpobj 所指向的字符串对象进行排序之后的数组";

}

LIMIT 选项的实现

服务器在执行 SORT alphabet ALPHA LIMIT 0 4 时创建的数据结构。

digraph {

    rankdir = LR;

    subgraph cluster_alphabet {

        label = "alphabet 集合\n";

        style = dashed;

        node [shape = box];

        a [label = "StringObject \n \"a\""];
        b [label = "StringObject \n \"b\""];
        c [label = "StringObject \n \"c\""];
        d [label = "StringObject \n \"d\""];
        e [label = "StringObject \n \"e\""];
        f [label = "StringObject \n \"f\""];

        edge [style = invis];

        d -> c -> a;
        b -> f -> e;
    }

    array [label = " array | { array[0] \n redisSortObject | { <obj0> obj | u } } | { array[1] \n redisSortObject | { <obj1> obj | u } } | { array[2] \n redisSortObject | { <obj2> obj | u } } | { array[3] \n redisSortObject | { <obj3> obj | u } } | { array[4] \n redisSortObject | { <obj4> obj | u } } | { array[5] \n redisSortObject | { <obj5> obj | u } } ", shape = record];

    edge [minlen = 2.0];

    array:obj0 -> d;
    array:obj1 -> c;
    array:obj2 -> a;
    array:obj3 -> b;
    array:obj4 -> f;
    array:obj5 -> e;

    label = "\n 图 21-15    将 obj 指针指向集合的各个元素";

}

digraph {

    rankdir = LR;

    subgraph cluster_alphabet {

        label = "alphabet 集合\n\n";

        style = dashed;

        node [shape = box];

        a [label = "StringObject \n \"a\""];
        b [label = "StringObject \n \"b\""];
        c [label = "StringObject \n \"c\""];
        d [label = "StringObject \n \"d\""];
        e [label = "StringObject \n \"e\""];
        f [label = "StringObject \n \"f\""];

        edge [style = invis];

        d -> c -> a;
        b -> f -> e;
    }

    array [label = " array | { array[0] \n redisSortObject | { <obj0> obj | u } } | { array[1] \n redisSortObject | { <obj1> obj | u } } | { array[2] \n redisSortObject | { <obj2> obj | u } } | { array[3] \n redisSortObject | { <obj3> obj | u } } | { array[4] \n redisSortObject | { <obj4> obj | u } } | { array[5] \n redisSortObject | { <obj5> obj | u } } ", shape = record];

    edge [minlen = 2.0];

    array:obj0 -> a;
    array:obj1 -> b;
    array:obj2 -> c;
    array:obj3 -> d;
    array:obj4 -> e;
    array:obj5 -> f;

    label = "\n 图 21-16    排序后的数组";

}

GET 选项的实现

服务器在执行 SORT students ALPHA GET *-name 时创建的数据结构。

digraph {

    rankdir = LR;

    subgraph cluster_students {

        label = "students 集合";

        style = dashed;

        node [shape = box];

        peter [label = "StringObject \n \"peter\""];

        jack [label = "StringObject \n \"jack\""];

        tom [label = "StringObject \n \"tom\""];

        peter -> jack -> tom [style = invis];

    }

    node [shape = record];

    array [label = " array | { array[0] \n redisSortObject | { <obj0> obj | u } } | { array[1] \n redisSortObject | { <obj1> obj | u } } | { array[2] \n redisSortObject | { <obj2> obj | u } } "];

    array:obj0 -> peter;
    array:obj1 -> jack;
    array:obj2 -> tom;

    label = "\n 图 21-17    排序之前的数组";

}

digraph {

    rankdir = LR;

    subgraph cluster_students {

        label = "students 集合";

        style = dashed;

        node [shape = box];

        peter [label = "StringObject \n \"peter\""];

        jack [label = "StringObject \n \"jack\""];

        tom [label = "StringObject \n \"tom\""];

        peter -> jack -> tom [style = invis];

    }

    node [shape = record];

    array [label = " array | { array[0] \n redisSortObject | { <obj0> obj | u } } | { array[1] \n redisSortObject | { <obj1> obj | u } } | { array[2] \n redisSortObject | { <obj2> obj | u } } "];

    array:obj0 -> jack;
    array:obj1 -> peter;
    array:obj2 -> tom;

    label = "\n 图 21-18    排序之后的数组";

}

第 22 章: 二进制位数组

位数组的表示

digraph {

    label = "\n 图 22-1    SDS 表示的位数组";

    rankdir = LR;

    //

    node [shape = record];

    redisObject [label = " redisObject | ... | type \n REDIS_STRING | ... | <ptr> ptr "];

    sds [label = " <head> sdshdr | free \n 0 | len \n 1 | <buf> buf "];

    buf [label = " { buf[0] | buf[1] (空字符) } "];

    //

    redisObject:ptr -> sds:head;

    sds:buf -> buf;

}

digraph {

    label = "\n 图 22-2    一字节长的位数组的 SDS 表示";

    rankdir = LR;

    //

    node [shape = record];

    redisObject [label = " redisObject | ... | type \n REDIS_STRING | ... | <ptr> ptr "];

    sds [label = " <head> sdshdr | free \n 0 | len \n 1 | <buf> buf "];

    buf [label = " { buf[0] | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 } | { buf[1] (空字符) } "];

    //

    redisObject:ptr -> sds:head;

    sds:buf -> buf;

}

digraph {

    label = "\n 图 22-3    三字节长的位数组的 SDS 表示";

    rankdir = LR;

    //

    node [shape = record];

    redisObject [label = " redisObject | ... | type \n REDIS_STRING | ... | <ptr> ptr "];

    sds [label = " <head> sdshdr | free \n 0 | len \n 3 | <buf> buf "];

    buf [label = " { buf[0] | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 } | { buf[1] | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 } | { buf[2] | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 } | { buf[3] (空字符) } "];

    //

    redisObject:ptr -> sds:head;

    sds:buf -> buf;

}

GETBIT 命令的实现

digraph {

    label = "\n 图 22-4    查找并返回 offset 为 3 的二进制位的过程";

    //

    rankdir = LR;

    point_to_buf0 [label = "1) 定位到 buf[0] 字节", shape = plaintext];

    point_to_idx3 [label = "2) 返回第 4 个二进制位的值", shape = plaintext];

    buf [label = " { <buf0> buf[0] | 1 | 0 | 1 | <idx3> 1 | 0 | 0 | 1 | 0 } | { buf[1] (空字符) } ", shape = record];

    //

    edge [style = dashed];

    point_to_buf0 -> buf:buf0;
    point_to_idx3 -> buf:idx3;

}

digraph {

    label = "\n 图 22-5    查找并返回 offset 为 10 的二进制位的过程";

    rankdir = LR;

    //

    node [shape = record];

    buf [label = " { buf[0] | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 } | { <buf1> buf[1] | 1 | 1 | <bit> 0 | 0 | 0 | 0 | 1 | 1 } | { buf[2] | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 } | { buf[3] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 } "];

    node [shape = plaintext];

    point_to_buf [label = "1) 定位到 buf[1] 字节"];
    point_to_bit [label = "2) 返回第 3 个二进制位的值"];

    //

    edge [style = dashed];
    point_to_buf -> buf:buf1;
    point_to_bit -> buf:bit;

}

SETBIT 命令的实现

无须扩展的 SETBIT 命令。

digraph {

    label = "\n 图 22-6    SETBIT 命令的执行过程";

    //

    node [shape = plaintext];

    point_to_byte [label = "1) 定位到 buf[0] 字节"];

    point_to_bit [label = "2)定位到 buf[0] 字节的第 2 个二进制位 \n 将位现在的值 0 保存到 oldvalue 变量 \n 然后将位的值设置为 1 "];

    buf [label = " { { <byte> buf[0] | 1 | <bit> 0 | 1 | 1 | 0 | 0 | 1 | 0 } | { buf[1] (空字符) } } ", shape = record];

    //

    edge [style = dashed];

    point_to_byte -> buf:byte;
    point_to_bit -> buf:bit;
}

需要进行扩展的 SETBIT 命令。

digraph {

    label = "\n 图 22-8    扩展空间之后的位数组";

    rankdir = LR;

    //

    node [shape = record];

    redisObject [label = " redisObject | ... | type \n REDIS_STRING | ... | <ptr> ptr "];

    sds [label = " <head> sdshdr | free \n 2 | len \n 2 | <buf> buf "];

    buf [label = " { buf[0] | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 } | { buf[1] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 } | { buf[2] (空字符) } | { buf[3] (未使用) } | { buf[4] (未使用) } "];

    //

    redisObject:ptr -> sds:head;

    sds:buf -> buf;

}

digraph {

    label = "\n 图 22-9    SETBIT 命令的执行过程";

    //

    node [shape = plaintext];

    point_to_byte [label = "1)定位到 buf[1] 字节"];
    point_yo_bit [label = "2)定位到 buf[1] 字节的第 5 个二进制位 \n 首先将位现在的值 0 保存到 oldvalue 变量 \n 然后将位的值设置为 1 "];

    node [shape = record];

    buf [label = " { { buf[0] | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 } | { <byte> buf[1] | 0 | 0 | 0 | 0 | <bit> 0 | 0 | 0 | 0 } | { buf[2] (空字符) } | { buf[3] (未使用) } | { buf[4] (未使用) } } "];

    //

    edge [style = dashed];

    point_to_byte -> buf:byte;
    point_yo_bit -> buf:bit;
}

digraph {

    label = "\n 图 22-10    执行 SETBIT 命令之后的位数组";

    //

    node [shape = record];

    buf [label = " { { buf[0] | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 } | { <byte> buf[1] | 0 | 0 | 0 | 0 | <bit> 1 | 0 | 0 | 0 } | { buf[2] (空字符) } | { buf[3] (未使用) } | { buf[4] (未使用) } } "];

}

第 23 章: 慢查询日志

slowlogEntry 结构。

digraph {

     label = "\n 图 23-1    slowlogEntry 结构示例";

     rankdir = LR;

     node [shape = record];

     slowlogEntry [label = " slowlogEntry | id \n 3 | time \n 1378781439 | duration \n 10 | <argv> argv | argc \n 3 "];

     argv [label = " { { argv[0] | StringObject \n \"SET\" } | { argv[1] | StringObject \n \"number\" } | { argv[2] | StringObject \n \"10086\" } } "];

     slowlogEntry:argv -> argv;

}

服务器状态的 slowlog 属性。

digraph {

    label = "\n 图 23-2    redisServer 结构示例";

    rankdir = LR;

    node [shape = record];

    redisServer [label = " redisServer | ... | slowlog_entry_id \n 6 | <slowlog> slowlog | slowlog_log_slower_than \n 0 | slowlog_max_len \n 5 | ... "];

    slowlogEntry_5 [label = " slowlogEntry | id \n 5 | time \n 1378781521 | duration \n 61 | <argv> argv | argc \n 2 "];

    slowlogEntry_1 [label = " slowlogEntry | id \n 1 | time \n 1378781425 | duration \n 11 | <argv> argv | argc \n 4 "];

    more [label = "...", shape = plaintext]

    redisServer:slowlog -> slowlogEntry_5 -> more -> slowlogEntry_1;

}

第 24 章: 监视器

digraph {

    label = "\n 图 24-1    命令的接收和信息的发送";

    rankdir = LR;

    server [label = "服\n务\n器", shape = box, height = 3.0, width = 1.0];

    node [shape = circle, width = 1.3];

    client [label = "客户端"];

    m1 [label = "监视器 1"];
    m2 [label = "监视器 2"];
    m3 [label = "监视器 3"];

    client -> server [label = "发送命令请求"];

    edge [label = "发送命令信息"];

    server -> m1;
    server -> m2;
    server -> m3;

}


digraph {

    label = "\n 图 24-3    客户端 c10086 执行 MONITOR 命令之后的 monitors 链表";

    rankdir = LR;

    node [shape = record];

    server [label = " redisServer | ... | <monitors> monitors | ... "];

    c128 [label = "c128"];

    c256 [label = "c256"];

    c512 [label = "c512"];

    c10086 [label = "c10086"];

    server:monitors -> c128 -> c256 -> c512 -> c10086;

}


digraph {

    label = "\n 图 24-4    服务器将信息发送给各个监视器";

    rankdir = LR;

    server [label = "服\n务\n器", shape = box, height = 4.0, width = 1.0];

    node [shape = circle, width = 1.3];

    c128;
    c256;
    c512;
    c10086;

    edge [label = "1378822257.329412\n[0 127.0.0.1:56604]\n\"KEYS\" \"*\""];

    server -> c128;
    server -> c256;
    server -> c512;
    server -> c10086;

}