• redis数据结构(三)字典(dict)


    dict为redis服务器中出现的使用最为频繁的复合型数据结构,不但在hash类型和zset中使用了dict结构,整个redis数据库就是一个大的字典表,带有过期时间的key也组成一个大的字典表.

    1.dict的内部结构

      1.1 dict的内部结构

    typedef struct dict {
        dictType *type;//dictType中定义了很多dict中常用的方法,具体见dictType
        void *privdata;//一些私有数据
        dictht ht[2];//两个dictht数组,正常只有一个在用,只有当rehash时才会使用两个
        long rehashidx; /* dic是否正在进行rehash,当不为-1时,说明正在rehash */
        int iterators; /* 遍历器的个数*/
    } dict;

    ht[2]:里面存放了两个dictht类型的数据,dictht用于保存真正的数据,但是通常情况下只会使用一个dictht保存数据,只有当dict进行rehash的时候才会两个同时使用,关于rehash的详情见rehash小节.

    rehashidx:dict是否正在rehash的标识,如果值为-1,标识dict没有正在进行rehash,否则标识dict正在进行rehash,并且rehashidx的值表示dictht中还没有进行rehash的键值对的数量.

    dictType和privdata属性是针对不同类型的键值对为创建多态字典而设置的.dictType中定义了一组操作特定类型键值对的函数,privdata属性则保存了需要传给那些类型特定函数的可选参数

      1.2 dictType的内部结构

    typedef struct dictType {
         // 计算哈希值的函数
         unsigned int (*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;

      1.3 dictht的内部结构

    typedef struct dictht {
        dictEntry **table; //dictEntry数组保存真正的键值对
        unsigned long size;//table数组的长度
        unsigned long sizemask;//table数组的长度-1,为了方便计算键值对保存在table中的位置而定义的变量
        unsigned long used;//dictht中已经存在的键值对数量
    } dictht;

    size:是table数组的长度,这个长度是固定的,并不会因为插入和删除元素而改变

    used:保存在table中元素的数量,即当前dictht中有多少元素

      1.4 dictEntry的内部结构

    typedef struct dictEntry {
        void *key; //键值对中的key
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
            double d;
        } v; //键值对中的value,对不同的value选取不同的值
        struct dictEntry *next;//指向下一个dictEntry的指针
    } dictEntry;

    key,v:key,v分别保存了键值对的键和值.

    next:记录数组中统一个位置的下一个dictEntry的.

    2.hash冲突

    因为键值对在table中的位置是根据key的hashCode决定的,所以可能会发生hash冲突的情况,Redis的哈希表使用链地址法(separate chaining)来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来,这就解决了键冲突的问题。因为dictEntry节点组成的链表没有指向链表表尾的指针,所以为了速度考虑,程序总是将新节点添加到链表的表头位置(头插法,复杂度为O(1)),排在其他已有节点的前面。

    3.扩容

    一般来讲,当dictht中used和size相等的时候就会触发redis的扩容操作,默认redis扩容的时候会把容量扩大到原来size的两倍,计算方式为比used*2大的最小的2的n次方.

    但是如果dict正在进行bgsave,为了减少内存页的数量,redis会暂缓扩容,但是如果dict中元素的数量继续增加,当到达size的5被的时候,redis就会忽略bgsave进行强制扩容,

    dict扩容的时候会提前为ht数的第二个元素提前分配好内存空间,然后逐个对第一个元素中的dictEntry进行rehash转移到第二个元素中,,然后把第一个元素指向第二个元素

    4.缩容

    当dict中的元素不断被删除的时候,dictht中剩余的空间越来越多,当used的数量小于size的10%的时候就会触发redis的缩容操作,缩容的时候是不会考虑redis是否正在进行bgsave操作的.

    dict缩容和扩容一样也会先为tht数组的第二个元素分配内存,然后把第一个元素中的数据rehash然后放到第二个元素中,然后把第一个元素指向第二个元素

    5.rehash

    因为redis是单线程执行命令的.而如果dict中元素的数量表多,则进行扩容或则缩容的时候就会耗费的时间比较长,这就导致后续的指令都会返回的较慢,redis为了解决这个问题采用饿了渐进式hash的策略.

    即再进行rehash的时候不是一次性的把第一个dictht中的元素一次性全部rehash然后放到第二个dictht中,而是每次等到dict中有增删改查操作的时候每次顺便把dictht一种的部分元素转移到dictht2中,这样就把rehash的时间分配到了不同的指令上,不会造成一个指令延迟时间较长的情况.如果正在进行rehash的dict中没有来自客户端的指令,那么redis会定再定时任务中对数据进行主动的rehash操作

  • 相关阅读:
    sublime there are no packages for installation
    linux 安装php扩展mbstring
    生成器表达式和列表推导式
    send()和next()
    迭代器生成器
    装饰器
    函数随笔
    Django进阶
    数据结构与算法入门
    MySQL必会
  • 原文地址:https://www.cnblogs.com/monkeydai/p/16074712.html
Copyright © 2020-2023  润新知