当前位置: 首页 > news >正文

redis个人源码分析2---dict的实现原理

1. 总体结构

redis的dict就是hash表,使用链式结构来解决key值冲突,典型的数据结构

结构体的定义如下:



typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct dictType {
    uint64_t (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;  //hash桶是一个指针数组,里面存放的是hash entry的指针类型,只需要(8字节*size)个连续内存不需要大量的连续内存
    unsigned long size;  //这个是hash桶的大小
    unsigned long sizemask;  //hash桶大小-1, **用hash**/sizemask来计算桶下标
    unsigned long used; //当前这个dict一共放了多少个kv键值对
} dictht;
//一旦used/size >=dict_force_resize_ratio(默认值是5),就会触发rehash,可以理解为一个hash桶后面平均挂载的冲突队列个数为5的时候,就会触发rehash


typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;



复制代码

如下图所示:

2. API接口分析

2.1 创建

API接口函数:

  • dictAdd(dict *d, void *key, void *val)

在d中增加一个k-v对,实现代码如下:

/* Add an element to the target hash table */
int dictAdd(dict *d, void *key, void *val)
{
    dictEntry *entry = dictAddRaw(d,key,NULL);//调用了内部函数

    if (!entry) return DICT_ERR;
    dictSetVal(d, entry, val);
    return DICT_OK;
}



dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;

    if (dictIsRehashing(d)) _dictRehashStep(d); //如果正在rehash进行中,则每次操作都尝试进行一次rehash操作

    /* Get the index of the new element, or -1 if
     * the element already exists. 获取到hash桶的入口index*/
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently.
     (译文:申请内存来存储一个新的entry结构,插入元素到头部,
     这里的实现和一般的hash链式解决冲突的实现有点小不同,基于这样的假定:在数据库系统中,最近增加的entries越有可能被访问。 
     这里是把新插入的entry放到了链表头上,可以看上面的英文解释*/
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields.*/
    dictSetKey(d, entry, key);
    return entry;
}


/* Returns the index of a free slot that can be populated with
 * a hash entry for the given 'key'.
 * If the key already exists, -1 is returned
 * and the optional output parameter may be filled.
 *
 * Note that if we are in the process of rehashing the hash table, the
 * index is always returned in the context of the second (new) hash table. 
 
 这个原版注释写的很清楚,如果正在rehashing的时候,index返回的是new的hashtable*/
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    unsigned long idx, table;
    dictEntry *he;
    if (existing) *existing = NULL;

    /* Expand the hash table if needed ,判断hash桶是否需要扩大,这个地方是redis比较牛逼的地方,  
    hash桶是动态扩大的,默认初始的时候只有4,然后每次乘2的方式进行扩展,如果扩展了,就需要进行rehash*/
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
    /*获取索引的时候,如果正在rehash,需要两个hashtable都进行查询*/
    for (table = 0; table <= 1; table++) {
        /*这个idx就是hash桶的下标*/
        idx = hash & d->ht[table].sizemask;
        /* Search if this slot does not already contain the given key */
        he = d->ht[table].table[idx];
        while(he) {
        /*这里是必须遍历下冲突队列,保证key没有出现过*/
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                if (existing) *existing = he;
                return -1;
            }
            he = he->next;
        }
        /*如果不在rehash的话,其实就没有必要再做查找的操作了,直接返回就好了*/
        if (!dictIsRehashing(d)) break;
    }
    return idx;
}

复制代码
  • dictEntry *dictFind(dict *d, const void *key) 根据key在d中寻找值,这个逻辑和add差不多,代码很简单,这里就不做解释了

dictEntry *dictFind(dict *d, const void *key)
{
    dictEntry *he;
    uint64_t h, idx, table;

    if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
    if (dictIsRehashing(d)) _dictRehashStep(d);  //和增加的时候逻辑一样,如果正在rehashing,则进行一步rehash
    h = dictHashKey(d, key);
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) return NULL;
    }
    return NULL;
}

复制代码

3. rehash过程
redis对于dict支持两种rehash的方式:按照时间,或者按照操作进行rehash。每次都调整一个key值桶内所有的冲突链表到新的hash表中。 rehash 代码如下:

static void _dictRehashStep(dict *d) {
    if (d->iterators == 0) dictRehash(d,1);
}


/* Performs N steps of incremental rehashing. Returns 1 if there are still
 * keys to move from the old to the new hash table, otherwise 0 is returned.
 *
 * Note that a rehashing step consists in moving a bucket (that may have more
 * than one key as we use chaining) from the old to the new hash table, however
 * since part of the hash table may be composed of empty spaces, it is not
 * guaranteed that this function will rehash even a single bucket, since it
 * will visit at max N*10 empty buckets in total, otherwise the amount of
 * work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    if (!dictIsRehashing(d)) return 0;

    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        while(d->ht[0].table[d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1; //redis为了保证性能,扫描空桶,最多也是有一定的限制
        }
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT ,这个循环就是开始把这个rehashidx下标的hashtable迁移到新的下标下面,注意,这里需要重新计算key值,重新插入*/
        while(de) {
            uint64_t h;

            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;//重新计算key值,重新插入
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    /* Check if we already rehashed the whole table...,一次操作完了,可能这个hashtable已经迁移完毕,返回0,否则返回1 */
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1]; //现在的0变成1
        _dictReset(&d->ht[1]);  //现在的1被reset掉
        d->rehashidx = -1;
        return 0;
    }

    /* More to rehash... */
    return 1;
}



复制代码

转载于:https://juejin.im/post/5bf27463f265da6120616d9c

相关文章:

  • DB主从一致性架构优化4种方法
  • mysql的TABLE_SCHEMA的sql和information_schema表, MySQL管理一些基础SQL语句, Changes in MySQL 5.7.2...
  • kafka集群消息格式之V0版本到V2版本的平滑过渡详解-kafka 商业环境实战
  • docker镜像的分层结构三
  • form表单中某个input传入数据库数据默认为on
  • Oracle 未能加载文件或程序集Oracle.DataAccess
  • Java进阶篇设计模式之十二 ---- 备忘录模式和状态模式
  • cx_Oracle.DatabaseError: DPI-1047: 64-bit Oracle Client library cannot be loaded: 解决方案
  • 阿里AI设计师一秒出图,小撒连连惊呼,真相是……
  • 前端进阶课程之模块化(一)CommonJS规范
  • PAT(Basic Level) 乙级练习题 ------ 1031 查验身份证 java
  • bzoj 5210 最大连通子块和——动态DP
  • 理解NGINX的重写break和last,以及location匹配规
  • webpack执行命令参数
  • spark-join算子
  • JavaScript-如何实现克隆(clone)函数
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • Computed property XXX was assigned to but it has no setter
  • js如何打印object对象
  • JS实现简单的MVC模式开发小游戏
  • MD5加密原理解析及OC版原理实现
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • React as a UI Runtime(五、列表)
  • React组件设计模式(一)
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • win10下安装mysql5.7
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 机器学习中为什么要做归一化normalization
  • 力扣(LeetCode)56
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 深度学习入门:10门免费线上课程推荐
  • 微服务入门【系列视频课程】
  • 小李飞刀:SQL题目刷起来!
  • 智能合约Solidity教程-事件和日志(一)
  • 字符串匹配基础上
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • puppet连载22:define用法
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #、%和$符号在OGNL表达式中经常出现
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • %check_box% in rails :coditions={:has_many , :through}
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (九十四)函数和二维数组
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (十八)三元表达式和列表解析
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost