• 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操作

  • 相关阅读:
    (原)Lazarus 异构平台下多层架构思路、DataSet转换核心代码
    (学)新版动态表单研发,阶段成果3
    (学) 如何将 Oracle 序列 重置 清零 How to reset an Oracle sequence
    (学)XtraReport WebService Print 报错
    (原)三星 i6410 刷机 短信 无法 保存 解决 办法
    (原) Devexpress 汉化包 制作工具、测试程序
    linux下网络配置
    apache自带ab.exe小工具使用小结
    Yii::app()用法小结
    PDO使用小结
  • 原文地址:https://www.cnblogs.com/monkeydai/p/16074712.html
Copyright © 2020-2023  润新知