ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

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

2022-03-30 03:01:21  阅读:188  来源: 互联网

标签:rehash void redis dict 键值 dictht 数据结构


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

 

标签:rehash,void,redis,dict,键值,dictht,数据结构
来源: https://www.cnblogs.com/monkeydai/p/16074712.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有