zoukankan      html  css  js  c++  java
  • 我知道点redis-数据结构与对象(字典)

    我知道点redis-数据结构与对象(字典)

    字典在redis中的应用相当广泛,比如redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、改、查操作也是构建在对字典的操作之上的。

    使用

    1. redis的数据库
    2. 哈希键
    3. 集合

    4.1 字典的实现

    Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

    4.1.1 哈希表

    Redis字典所使用的哈希表由dict.h/dictht结构定义:

    typedef struct dictht {
    	
    	//哈希表数组
    	//数组中的每个元素都是一个指向`dict.h/dictEntry`结构的指针,每个`dictEntry`保存着一个键值对;
    	dictEntry **table;
    	
    	//哈希表大小
    	unsigned long size;
    	
    	//哈希表大小掩码,用于计算索引值,这个属性和哈希值一起决定一个key被放到table的哪个index上。
    	//总是等于size-1
    	unsigned long sizemask;
    	
    	//该哈希表已有节点的数量
    	unsigned long used;
    
    }dictht;
    

    4.1.2 哈希表节点

    哈希表节点使用dictEntry结构表示,每隔dictEntry结构都保存着一个键值对:

    typedef struct dictEntry {
    	
    	// 键
    	void *key;
    	
    	// 值
    	union {
    		void *val;
    		uint64_t u64;
    		int64_t s64;
    	} v;
    	
    	// 指向下个哈希表节点,形成LinkedList
    	// 将多个哈希值相同的dictEntry连接在一起,以此来解决键冲突(collision)
    	struct dictEntry *next;
    } dictEntry;
    

    4.1.3 字典

    Redis中的字典由dict.h/dict结构表示:

    typedef struct dict {
    	
    	// 类型特定函数
    	dictType *type;
    	
    	// 私有数据
    	void *privdata;
    	
    	// 哈希表
    	dictht ht[2];
    	
    	// rehash索引 , 当reshah不在进行时, 值为-1
    	int rehashidx;
    	
    } dict;
    
    • typeprivdata属性是为创建多态字典设置的。
    • ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一个情况下,字典只使用ht[0]ht[1]哈希表只会在对ht[0]进行rehash时使用。
    • 除了ht[1]之外,另一个和rehash有关的属性就是rehashidx,他记录了rehash目前的进度,如果没有进行rehash,它的值是-1

    4.2 哈希算法

    当要将一个新的键值对添加到字典里,程序先根据键的值计算出哈希值和索引值,然后根据索引值,将dictEntry添加到哈希表数组指定索引上面。

    #使用字典设置的hash函数,计算key的哈希值
    hash = dict->type->hashFunction(key);
    
    #使用hash表的sizemask属性和hash值,计算index。根据情况不同,ht[x]可以是ht[0]或者ht[1]
    index = hash & dict->ht[x].sizemask;
    

    Examples:

    rehashidx = -1; sizemask = 3;

    hash = dict->type->hashFunction(k0);

    假设 hash = 8

    index = hash & dict->ht[0].sizemask = 8 & 3 = 0;
    当字典被用作数据库的底层实现,或者hash键的底层实现时,Redis使用MurmurHash2算法来计算键的hash值。这个算法的有点在于,即使输入有有规律的,算法仍能给出一个很好的随机分布性,并且算法的计算速度也非常快。


    4.3 解决键冲突

    redis的哈希表使用链地址法来解决键冲突,每个hash表节点都有一个next指针,多个hash表节点可以用next指针构成一个单向链表。


    4.4 reshah

    随着操作的不断执行,哈希表保存的键值对会主键地增多或者减少,为了让哈希表的负载因子(load factor) 维持在一个合理的范围内,当哈希表保存的键值对数量太多或者太少,程序需要对哈希表的大小相应的扩展或者收缩。

    扩展或者收缩hash表的工作可以通过执行rehash操作完成,步骤如下:

    1. 为ht[1]分配空间,这个hash表的空间大小取决于要执行的操作,以及ht[0]当前包括的键值对数量(ht[0].used):

      • 扩展操作:ht[1]的大小为第一个>=ht[0].used*2的2^n;
      • 收缩操作:ht[1]的大小为第一个>=ht[0].used的2^n;

      扩展操作,ht[0].used=4,4*2=8,而8恰好是第一个>=4的2的n次方,所以ht[1].size=8

    2. 将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的hash值和index值,然后将键值对放置到ht[1]哈希表的指定位置上

    3. 当ht[0]包含的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白hash表,为下一次rehash做准备。

    哈希表扩展条件

    • 服务器目前没有执行BGSAVE或者BGREWRITEAOF命令,并且哈希表的负载因子>=1
    • 服务器目前正在执行BGSAVE或者BGREWRITEAOF命令,并且hash表的负载因子>=5

    在执行BGSAVE或者BGREWRITEAOF的过程中,redis创建当前服务器进程的子进程,大多数OS都会采用写时复制技术来优化子进程的使用效率,所以在子进程存在期间,服务器回提高执行扩展操作所需的负载因子,从而尽可能地避免在子进程存在期间进行哈希表的扩展操作,这样可以避免不必要的内存写入。

    哈希表收缩条件

    哈希表的负载因子小于0.1

    4.5 渐进式rehash

    扩展或者收缩hash表需要将ht[0]里面所有键值对resh到ht[1]里面,但是这个rehash动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。这样做的原因是如果ht[0]中存在很多键值对,要一次性rehash,那么会导致服务器在一段时间内停止服务。

    渐进式rehash的详细步骤

    1. 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个hash表。
    2. 在字典维持一个索引计数器rehashidx,并将它的值设置为0,表示rehash工作正式开始。
    3. 在rehash期间,每次对字典执行增删改查是,程序除了完成指定操作,还会顺带将ht[0]的哈希表在rehashidx索引上的键值对rehash到ht[1],然后将rehashidx属性加一。
    4. 随着字典操作的不断执行, 最终ht[0]上所有的键值对rehash到ht[1]上,这时程序将rehash置为-1,表示rehash完成。

    渐进式rehash执行期间的hash表操作

    因为在渐进式rehahs期间,字典会同时使用ht[0]和ht[1]。所以在rehash期间,字典的del,find,update操作会在2个hash表上进行。

    另外,新添加的键值对回添加到ht[1]上。

  • 相关阅读:
    mysql查看每张表的空间使用情况
    下一步开发的技术点
    技术体系需要继续探索的东西
    架构体系需要进一步研究探索的V2路线图
    串行写队列的MYSQL大文本参数
    Node.js 数据存储方式的选择
    Node.js npm 详解
    Node入门
    Node.js知识点学习
    为什么应使用 Node.js
  • 原文地址:https://www.cnblogs.com/wangxin201492/p/4705836.html
Copyright © 2011-2022 走看看