redis的dict结构

Posted 翔之天空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis的dict结构相关的知识,希望对你有一定的参考价值。

极客时间:02 | 数据结构:快速的Redis有哪些慢操作?
 redis设计与实践
 
 
Redis的一个database中所有key到value的映射,就是使用一个dict来维护的
 
 
 
 
 
dict数据结构
 
 
 
 
 
 
--1、新增一个key的流程: set msg 999
 
main
|-aeMain                              //事件处理器的主循环, aeMain 函数其实就是一个封装的 while 循环,循环中的代码会一直运行直到 eventLoop 的 stop 被设置为 true
 |-aeProcessEvents                    //实际用于处理事件的函数
  |-processInputBuffer
   |-processCommand                    
    |-call
     |-setCommand                      //具体命令的解析 这里是set命令:  SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] 命令
      |-setGenericCommand              //set类型命令的通用方法,包括 :SET, SETEX, PSETEX, SETNX
       |-setKey                        //set key的具体方法
        |-lookupKeyWrite               //查找是否存在此key,如果不存在用dbAdd添加,如果存在走dbOverwrite(db,key,val);覆盖 详见如下
         |-lookupKey
          |-dictFind                   //查找key的具体方法 计算出ht[].table.index 具体的bucket, hash效率O(1), 除非哈希冲突链表 很长
        |-dbAdd
         |-dictAdd                     //新增key 和value值
          |-dictAddRaw                 //新增key 操作,详见如下
           |-_dictRehashStep           //rehash的步骤
           |-_dictKeyIndex             //经过hash后 确认在哪个bucket中 赋值给index ,在index冲突链表的表头添加这个key : entry->next = ht->table[index];    ht->table[index] = entry;
            |-_dictExpandIfNeeded      //判断hash table是否需要扩展 需要的话扩展一倍buckets数量
              |-dictExpand             //创建hash table
           |-dictSetKey                //添加key
          |-dictSetVal(d, entry, val)  //添加value
        |-dbOverwrite                  //如果要set的key存在 走dbOverwrite(db,key,val);覆盖
         |-dictReplace                 //具体的逻辑实现 还是继续dictAddRaw 和dictSetVal 方式 添加key和value  ,详见如下
 



#1  0x0000000000428371 in dictAddRaw (d=d@entry=0x7ffff0818540, key=0x7ffff0816761, existing=existing@entry=0x0) at dict.c:302
#2  0x0000000000428541 in dictAdd (d=0x7ffff0818540, key=<optimized out>, val=val@entry=0x7ffff088f9f0) at dict.c:267
#3  0x0000000000440d55 in dbAdd (db=0x7ffff094f038, key=0x7ffff08a9ed0, val=0x7ffff088f9f0) at db.c:169
#4  0x0000000000441235 in setKey (db=0x7ffff094f038, key=key@entry=0x7ffff08a9ed0, val=val@entry=0x7ffff088f9f0) at db.c:208
#5  0x000000000044c378 in setGenericCommand (c=c@entry=0x7ffff0962340, flags=flags@entry=0, key=0x7ffff08a9ed0, val=0x7ffff088f9f0,
    expire=expire@entry=0x0, unit=unit@entry=0, ok_reply=ok_reply@entry=0x0, abort_reply=abort_reply@entry=0x0) at t_string.c:86
#6  0x000000000044c58f in setCommand (c=0x7ffff0962340) at t_string.c:139
#7  0x000000000042c0de in call (c=c@entry=0x7ffff0962340, flags=flags@entry=15) at server.c:2229
#8  0x000000000042c7e7 in processCommand (c=0x7ffff0962340) at server.c:2515
#9  0x000000000043b8d5 in processInputBuffer (c=0x7ffff0962340) at networking.c:1357
#10 0x0000000000426820 in aeProcessEvents (eventLoop=eventLoop@entry=0x7ffff083c050, flags=flags@entry=11) at ae.c:443
#11 0x0000000000426aeb in aeMain (eventLoop=0x7ffff083c050) at ae.c:501
#12 0x00000000004238ef in main (argc=<optimized out>, argv=0x7fffffffe168) at server.c:3899
 
 
 
 
 
--2、具体新增key的方法 dictAddRaw
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;

    if (dictIsRehashing(d)) _dictRehashStep(d);                                     // 如果条件允许的话,进行单步 rehash

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    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. */
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];                                // 如果字典正在 rehash ,那么将新键添加到 1 号哈希表, 否则,将新键添加到 0 号哈希表
    entry = zmalloc(sizeof(*entry));                                                // 为新节点分配空间
    entry->next = ht->table[index];                                                 // 将新节点插入到链表表头,详见下图 在bucket桶(idx3) 中每次都插入到链表头部 再次查找时减少比较次数
    ht->table[index] = entry;
    ht->used++;                                                                     // 更新哈希表已使用节点数量

    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);                                                      // 设置新节点的键
    return entry;
}

 

 
 
 
 
 
 
 
--3、具体覆盖key的方法  dictReplace
int dictReplace(dict *d, void *key, void *val)
{
    dictEntry *entry, *existing, auxentry;

    /* Try to add the element. If the key
     * does not exists dictAdd will suceed. */
    entry = dictAddRaw(d,key,&existing);            //再次尝试添加key到redis中,如果key不存在那么 添加成功 直接返回,    存在的话 记录位置 existing
    if (entry) {
        dictSetVal(d, entry, val);
        return 1;
    }

    /* Set the new value and free the old one. Note that it is important
     * to do that in this order, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */
    auxentry = *existing;                            //说明key已存在  保存原有值的指针
    dictSetVal(d, existing, val);                    //设置新的值
    dictFreeVal(d, &auxentry);                       //释放旧的值
    return 0;
}
 
 
 
 
 
 
 
--4、获取key的流程   get msg
 
#0  dictFind (d=0x7ffff0818540, key=0x7ffff08a9f13) at dict.c:477
#1  0x000000000043fcb4 in lookupKey (db=db@entry=0x7ffff094f038, key=key@entry=0x7ffff08a9f00, flags=flags@entry=0) at db.c:54
#2  0x0000000000440f55 in lookupKeyReadWithFlags (db=0x7ffff094f038, key=0x7ffff08a9f00, flags=flags@entry=0) at db.c:127
#3  0x0000000000440fe7 in lookupKeyRead (key=<optimized out>, db=<optimized out>) at db.c:138
#4  lookupKeyReadOrReply (c=c@entry=0x7ffff0962340, key=<optimized out>, reply=0x7ffff0817920) at db.c:152
#5  0x000000000044c798 in getGenericCommand (c=0x7ffff0962340) at t_string.c:160
#6  0x000000000042c0de in call (c=c@entry=0x7ffff0962340, flags=flags@entry=15) at server.c:2229
#7  0x000000000042c7e7 in processCommand (c=0x7ffff0962340) at server.c:2515
#8  0x000000000043b8d5 in processInputBuffer (c=0x7ffff0962340) at networking.c:1357
#9  0x0000000000426820 in aeProcessEvents (eventLoop=eventLoop@entry=0x7ffff083c050, flags=flags@entry=11) at ae.c:443
#10 0x0000000000426aeb in aeMain (eventLoop=0x7ffff083c050) at ae.c:501
#11 0x00000000004238ef in main (argc=<optimized out>, argv=0x7fffffffe168) at server.c:3899
 
 
 
 
 
 
 
 
--5、删除key的方法 :dictGenericDelete
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
    uint64_t h, idx;
    dictEntry *he, *prevHe;
    int table;

    if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;              //如果hash表总元素为空,直接返回空

    if (dictIsRehashing(d)) _dictRehashStep(d);                             //如果正在做rehashing,那么帮忙做一桶的迁移工作
    h = dictHashKey(d, key);                                                //获取key相关的hash值

    for (table = 0; table <= 1; table++) {                                  //查找table0,table1两张hash表
        idx = h & d->ht[table].sizemask;                                    //获取所在桶的索引 和sizemask掩码与 和 取余一样
        he = d->ht[table].table[idx];                                       //获取桶的首元素
        prevHe = NULL;
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key)) {         //如果找到了相同的元素
                /* Unlink the element from the list */
                if (prevHe)                                                 //不是在首位置,即在中间,链表中跳过这个元素即可
                    prevHe->next = he->next;
                else
                    d->ht[table].table[idx] = he->next;                     //在首位的情况下,就直接去掉首位
                if (!nofree) {                                              //如果需要释放,将键和值的空间释放,并且将这个元素的空间也释放
                    dictFreeKey(d, he);
                    dictFreeVal(d, he);
                    zfree(he);
                }
                d->ht[table].used--;
                return he;
            }
            prevHe = he;                                                    //没有找到相同额元素,将自身赋值给prevHe保存
            he = he->next;                                                  //查找下一个
        }
        if (!dictIsRehashing(d)) break;                                     //没有做rehashing,那么值需要查找表0即可
    }
    return NULL; /* not found */没有找到元素,返回空
}

举例:在第一个bucket桶里(idx1)中,此时单向链表首位置n1是he,如果he找到了匹配的key 那么 走else选项 用 he的next指针 赋给d->ht[table].table[idx] 也就是去掉首位置的n1节点

在第三个bucket桶里(idx3)中,在n1和n2节点都没有匹配到key 在n3匹配到了, 那么n3是he  、n3的前一个节点n2是prevHe  此时要把 he的next指针 赋给 prevHe->next 也就是把n3节点去掉
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

以上是关于redis的dict结构的主要内容,如果未能解决你的问题,请参考以下文章

死磕 Redis----- Redis 数据结构:dict

死磕 Redis----- Redis 数据结构:dict

死磕 Redis----- Redis 数据结构:dict

死磕 Redis----- Redis 数据结构:dict

redis数据结构存储Dict设计细节(redis的设计与实现笔记)

redis的dict结构