字典——dict

字典又称为散列表,是用来存储键值(key-value)对的一种数据结构

Redis字典的实现

Redis字典实现依赖的数据结构主要包含了三部分:字典、Hash表、Hash表节点。字典中嵌入了两个Hash表,Hash表中的table字段存放着Hash表节点,Hash表节点对应存储的是键值对。

1.1 Hash表

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;//指针数组,用来存储键值对
    unsigned long size;//table数组的大小
    unsigned long sizemask;//掩码 = size - z
    unsigned long used;//数组中已经存储元素个数
} dictht;

其中size默认为4,每次扩容成倍数扩容

1.2 Hash表节点

Hash表中的元素是用dictEntry结构体来封装的,主要作用是存储键值对,具体结构体如下:

typedef struct dictEntry {
    void *key;//存储键
    union {
        void *val;// db.dict 中的val
        uint64_t u64;
        int64_t s64;// db.expires 中的过期时间
        double d;
    } v;
    struct dictEntry *next;//指向next指针,HASH冲突时候形成单链表
} dictEntry;

1.3 字典

Redis字典实现除了包含前面介绍的两个结构体Hash表及Hash表节点外,还在最外面层封装了一个叫字典的数据结构 dict

typedef struct dict {
    dictType *type;//该字典对应的特定操作函数
    void *privdata;//字典依赖的数据
    dictht ht[2];//hash表,键值对存储
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;

字典这个结构体整体占用96字节,其中type字段,指向dictType结构体,里面包含了对该字典操作的函数指针。

Redis字典这个数据结构,除了主数据库的K-V数据存储外,还有很多其他地方会用到。例如,Redis的哨兵模式,就用字典存储管理所有的Master节点及Slave节点;再如,数据库中键值对的值为Hash类型时,存储这个Hash类型的值也是用的字典。

一个完整的字典数据结构:
在这里插入图片描述

字典扩容

随着Redis数据库添加操作逐步进行,存储键值对的字典会出现容量不足,达到上限,此时就需要对字典的Hash表进行扩容,扩容对应的源码是dict.h文件中的dictExpand函数


/* Expand or create the hash table,
 * when malloc_failed is non-NULL, it'll avoid panic if malloc fails (in which case it'll be set to 1).
 * Returns DICT_OK if expand was performed, and DICT_ERR if skipped. */
int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
{
    if (malloc_failed) *malloc_failed = 0;

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; /* the new hash table */
    unsigned long realsize = _dictNextPower(size);

    /* Rehashing to the same table size is not useful. */
    if (realsize == d->ht[0].size) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    if (malloc_failed) {
        n.table = ztrycalloc(realsize*sizeof(dictEntry*));
        *malloc_failed = n.table == NULL;
        if (*malloc_failed)
            return DICT_ERR;
    } else
        n.table = zcalloc(realsize*sizeof(dictEntry*));

    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

扩容主要流程为:

  1. 申请一块新内存,初次申请时默认容量大小为4个dictEntry;非初次申请时,申请内存的大小则为当前Hash表容量的一倍。
  2. 把新申请的内存地址赋值给ht[1],并把字典的rehashidx标识由-1改为0,表示之后需要进行rehash操作

渐进式rehash

rehash除了扩容时会触发,缩容时也会触发。Redis整个rehash的实现,主要分为如下几步完成。

  1. 给Hash表ht[1]申请足够的空间;扩容时空间大小为当前容量2,即d->ht[0].used2;当使用量不到总空间10%时,则进行缩容。缩容时空间大小则为能恰好包含d->ht[0].used个节点的2^N次方幂整数,并把字典中字段rehashidx标识为0。
  2. 进行rehash操作调用的是dictRehash函数,重新计算ht[0]中每个键的Hash值与索引值(重新计算就叫rehash),依次添加到新的Hash表ht[1],并把老Hash表中该键值对删除。把字典中字段rehashidx字段修改为Hash表ht[0]中正在进行rehash操作节点的索引值。
  3. rehash操作后,清空ht[0],然后对调一下ht[1]与ht[0]的值,并把字典中rehashidx字段标识为-1。

当数据库中键值对数量达到了百万、千万、亿级别时,整个rehash过程将非常缓慢,Redis优化的思想很巧妙,利用分而治之的思想了进行rehash操作,大致的步骤如下。
执行插入、删除、查找、修改等操作前,都先判断当前字典rehash操作是否在进行中,进行中则调用dictRehashStep函数进行rehash操作(每次只对1个节点进行rehash操作,共执行1次)。除这些操作之外,当服务空闲时,如果当前字典也需要进行rehsh操作,则会调用incrementallyRehash函数进行批量rehash操作(每次对100个节点进行rehash操作,共执行1毫秒)。在经历N次rehash操作后,整个ht[0]的数据都会迁移到ht[1]中,这样做的好处就把是本应集中处理的时间分散到了上百万、千万、亿次操作中,所以其耗时可忽略不计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值