zoukankan      html  css  js  c++  java
  • redis代码解析-dictionary类型

    dict本质上是为了解决算法中的查找问题(Searching),一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表。

    redis中的dict传统的哈希算法类似,它采用某个哈希函数从key计算得到在哈希表中的位置,采用拉链法解决冲突,并在装载因子(load factor)超过预定值时自动扩展内存,引发重哈希(rehashing).在一个dict中有两个hash表,目的是来实现逐步的rehash,同时rehash时仍然可以访问dict.

    正常使用的是ht[0],在rehash的时候先设置ht[1]大小为rehash后的需要的大小,然后逐步的将ht[0]中的内容rehash到ht[1]中,rehashidx记录着当前rehash到的index,在全部rehash完成之后,将ht[1]给ht[0],再reset ht[1].

    当前dict是否处于rehash过程中可以通过rehashidx的值来判断,rehash时记录的是已经进行到的index,非rehash时为-1.

    逐步的rehash是将rehash操作分散到对于dict的各个增删改查的操作中去。这种方法能做到每次只对一小部分key进行重哈希,而每次重哈希之间不影响dict的操作。dict这样设计避免重哈希期间单个请求的响应时间的剧烈增加

    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;
    

    一个hash table即dictht中的table是一个dictEntry数组,dict每次rehash时,table扩容或者缩小的时候都是2的n次方大小.

    同时dictht还记录了当前table的大小,以及存在的dictEntry的数量.判断是否需要rehash就是通过used/size==1为需要rehash,used/size==5需要强制rehash

    dictht中sizemask的值为size-1,因为size总是2的n次方,所以sizemask是n位的1,用来一个key的hash&sizemask定位出这个key应该在table中的位置.

     同时,解决hash冲突的方法是链式

    typedef struct dictEntry {
        void *key;
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
            double d;
        } v;
        struct dictEntry *next;
    } dictEntry;
    
    /* 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;
        unsigned long sizemask;
        unsigned long used;
    } dictht;
    
    
    

    一个dict的图示如下:

    图片引用自:http://zhangtielei.com/posts/blog-redis-dict.html

    dict的其他操作都非常简单,都是正常的对hash table的操作

    dict scan还有点意思,因为redis中dict数据结构的关系,所以没办法使用传统的顺序遍历,因为

      1 如果发生了扩容,原来的表大小是8,扩容之后是16,就会把原来的1-8rehash到新的1-16个slot中.如果现在要顺序访问8,则从8-16之前的其实全部在旧的表中1-7中访问过,会有大量的重复访问.

      2 如果发生了缩表,原来的16缩小到8,就正好相反会有遗漏.

      3 如果正在rehash,t0表中的数据是不全的也依然肯定有问题.

    所以,redis中dict scan设计了一种新的遍历方法:对二进制高位进行加1遍历.

    如果表的大小是8的话,和大小为2的访问顺序会是如下:

    000 -> 0   00-> 0
    100 -> 4   10-> 2
    010 -> 2   01-> 1
    110 -> 6   11-> 3
    001 -> 1
    101 -> 5
    011 -> 3
    111 -> 7
    

     1 表扩容,先访问n,然后是访问n+2的n次方,不会重复,不会遗漏.

    2 表缩小,通过n&m0(size mask)定位,可能会有重复的情况,不会遗漏.

    3 如果正在rehash过程中,访问n的时候,会先在容量小的表中访问(n&m0),然后在大的表中遍历出全部的n&m0对应的位置.

    dict scan 详细可参考这篇blog,http://chenzhenianqing.cn/articles/1101.html

  • 相关阅读:
    第七章
    第五章
    第六章
    Git使用入门
    源代码的下载和编译
    向中国最牛的前端群-鬼群致敬
    Normalize.css做了哪些事情--看代码
    谷歌浏览器:书签被误删了怎么办
    2013/8月读书计划
    Limu:JavaScript的那些书
  • 原文地址:https://www.cnblogs.com/nicganon/p/7550713.html
Copyright © 2011-2022 走看看