字典(dict.h/dict.c)

Redis字典具有以下特点:

  • Redis字典的底层实现为哈希表,
  • 每个字典使用两个哈希表, 一般情况下只使用 0 号哈希表, 只有在 rehash 进行时, 才会同时使用 0 号和 1 号哈希表。
  • 哈希表使用链地址法来解决键冲突的问题。
  • 自动 Rehash 扩展或收缩哈希表。
  • 对哈希表的 rehash 是分多次、渐进式地进行的。

数据结构

/*
 * hash节点
 */

typedef struct dictEntry {

    //键
    void *key;

    //值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    //指向下一个节点
    struct dictEntry *next;
} dictEntry;
typedef struct dictht {

    //桶
    dictEntry **table;

    //指针数组大小
    unsigned long size;

    //指针数组掩码,用于计算索引值
    unsigned long sizemask;

    //hash表现有节点数量
    unsigned long used;
} dictht;
typedef struct dict {

    //类型处理函数
    dictType *type;

    //类型处理函数私有值
    void *privdata;

    //两个hash表
    dictht ht[2];

    //rehash标示,为-1表示不在rehash,不为0表示正在rehash的桶
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    //当前正在运行的安全迭代器数量
    int iterators; /* number of iterators currently running */
} dict;

dict的结构如下:

digraph dict_struct {

    // setting

    rankdir = LR;

    node[shape=record, style = filled];

    edge [style = bold];

    // nodes

    dict [label="dict | type | privdata |<ht> ht[2] | rehashidx: -1 | iterators: 0", fillcolor = "#A8E270"];

    ht0 [label="<dictht>dictht |<table> table | size: 4 | sizemask: 3 | used: 3", fillcolor = "#95BBE3"];

    ht1 [label="<dictht>dictht |<table> table | size: 0 | sizemask: 0 | used: 0", fillcolor = "#95BBE3"];

    bucket [label="<head>dictEntry**\n(bucket) |<table0> 0 |<table1> 1 |<table2> 2 |<table3> 3 ", fillcolor = "#F2F2F2"];

    pair_1 [label="<head>dictEntry |{key1 | value1 |<next>next}", fillcolor = "#FADCAD"];

    pair_2 [label="<head>dictEntry |{key2 | value2 |<next>next}", fillcolor = "#FADCAD"];

    pair_3 [label="<head>dictEntry |{key3 | value3 |<next>next}", fillcolor = "#FADCAD"];

    null0 [label="NULL", shape=plaintext];
    null1 [label="NULL", shape=plaintext];
    null2 [label="NULL", shape=plaintext];
    null3 [label="NULL", shape=plaintext];

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

    // lines

    dict:ht -> ht0:dictht [label="ht[0]"];
    dict:ht -> ht1:dictht [label="ht[1]"];

    ht0:table -> bucket:head;

    ht1:table -> tnull1;

    bucket:table0 -> pair_1:head; pair_1:next -> null0;

    bucket:table1 -> null1;

    bucket:table2 -> pair_2:head; pair_2:next -> null2;

    bucket:table3 -> pair_3:head; pair_3:next -> null3;
}

字典中添加元素的过程

digraph dictAdd {

    node[shape=plaintext, style = filled];

    edge [style = bold];

    //

    start [label="dictAdd", fillcolor = "#A8E270"];
    rehash_or_not[label="是否在rehash?",shape=diamond, fillcolor = "#95BBE3"];
    
    start -> rehash_or_not;

    is_rehash [label="调用_dictRehashStep"];

    rehash_or_not -> is_rehash [label="是"];

    iterators_is_0_or_not[label="iterators是否为0?",shape=diamond, fillcolor = "#95BBE3"];
    is_rehash -> iterators_is_0_or_not [label="是"]

    rehash_one_step[label="迁移一个桶中元素"];
    iterators_is_0_or_not -> rehash_one_step;

    need_expand_or_not[label="是否要扩展空间?",shape=diamond, fillcolor = "#95BBE3"];

    rehash_one_step -> need_expand_or_not;
    iterators_is_0_or_not -> need_expand_or_not [label="否"];
    rehash_or_not -> need_expand_or_not [label="否"];
    
    dict_empty_or_not [label="ht[0]\n 未分配任何空间?", shape=diamond, fillcolor = "#95BBE3"];
    need_expand_or_not -> dict_empty_or_not[label="是"]

    init_hash_table_one [label="初始化 ht[0]"];
    dict_empty_or_not -> init_hash_table_one [label="是"];

    expand_hash_table_two_or_not[label="是否需要增加桶数?", shape=diamond, fillcolor = "#95BBE3"];
    dict_empty_or_not -> expand_hash_table_two_or_not[label="否"]

    init_hash_table_two [label="初始化 ht[1]"];
    expand_hash_table_two_or_not -> init_hash_table_two [label="是"];

    key_exists_or_not [label="键已经存在?", shape=diamond, fillcolor = "#95BBE3"];
    init_hash_table_one -> key_exists_or_not;
    init_hash_table_two -> key_exists_or_not;
    need_expand_or_not -> key_exists_or_not [label="否"];
    expand_hash_table_two_or_not -> key_exists_or_not [label="否"];

    return_null_if_key_exists [label="返回 NULL ,\n表示添加失败"];

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


    rehashing_or_not [label="rehash\n 正在进行中?", shape=diamond, fillcolor = "#95BBE3"];
    key_exists_or_not -> rehashing_or_not [label="否"];

    is_rehashing [label="选择 ht[1] 作为新键值对的添加目标"];

    not_rehashing [label="选择 ht[0] 作为新键值对的添加目标"];

    rehashing_or_not -> is_rehashing [label="是"];

    rehashing_or_not -> not_rehashing [label="否"];

    calc_hash_code_and_index_by_key [label="根据给定键,计算出哈希值,以及索引值"];

    is_rehashing -> calc_hash_code_and_index_by_key;
    not_rehashing -> calc_hash_code_and_index_by_key;

    create_entry_and_assoc_key_and_value [label="创建新 dictEntry ,并保存给定键值对"];

    calc_hash_code_and_index_by_key -> create_entry_and_assoc_key_and_value;

    add_entry_to_hashtable [label="根据索引值,将新节点添加到目标哈希表"];
    
    create_entry_and_assoc_key_and_value -> add_entry_to_hashtable;

}

rehash介绍

字典的 rehash 操作实际上就是执行以下任务:

创建一个比 ht[0]->table 更大的 ht[1]->table , size为大于used*2的2的指数, 开始值为4;

将 ht[0]->table 中的所有键值对迁移到 ht[1]->table ;

将原有 ht[0] 的数据清空,并将 ht[1] 替换为新的 ht[0] ;

进行rehash的条件:

自然 rehash : ratio >= 1 ,且变量 dict_can_resize 为真。

强制 rehash : ratio 大于变量 dict_force_resize_ratio (目前版本中, dict_force_resize_ratio 的值为 5 )。

阶进rehash:

主动方式:databaseCron中调用dictRehashMilliseconds执行一毫秒。

被动方式:调用dictAdd,dicFind,dictDelete,dictGetRandomKey时,调用_dictRehashStep,迁移一个非空桶。