Redis底层数据结构---hash表

Redis使用渐进式rehash策略优化哈希表扩展和收缩,避免一次性操作导致性能影响。在rehash过程中,两个哈希表并存,通过客户端请求和定时任务逐步迁移数据,确保操作效率和内存使用。rehash涉及扩容、缩容条件、步骤及对内存的影响,与HashMap的扩容策略有显著区别。
摘要由CSDN通过智能技术生成

Redis的哈希表—rehash详细讲解

  Redis的性能优越,应用普遍,可以存储的键值个数大到上亿条记录,依然保持较高的效率。作为一个内存数据库,Redis内部采用了字典(哈希表)的数据结构实现了键值对的存储。随着数据量的不断增加,数据必然会产生hash碰撞,而Redis采用链地址法解决hash冲突。我们知道如果哈希表数据量达到了一个很大的量级,那么冲突的链的元素数量就会很大,这时查询效率就会变慢,因为取值的时候Redis会遍历链表。而随着数据量的缩减,也会产生一定的内存浪费。Redis在设计时充分考虑了字典的增加和缩减,为了优化数据量增加时的查询效率和缩减时的内存利用率,Redis进行了一系列操作,而处理的这个过程被称作rehash。Redis是在插入新节点之前判断是否需要进行扩容,如果不需要,则直接插入,否则需要先扩容,再插入新节点。
  为了避免对服务器性能造成影响,Redis使用了一种渐进式哈希的机制来提高字典的缩放效率。渐进式rehash的好处是它采取分而治之的方式,将rehash键值所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash带来的庞大计算量。比如:如果哈希表中的数据量达到上百万级别,进行一次rehash会消耗大量的时间,可能导致服务器在一段时间内停止服务。

存储结构

  在Redis中,键值对存储方式是由字典(dict)保存的,而字典底层是通过哈希表来实现。通过哈希表中的节点保存字典中的键值对。

hash表的底层数据结构定义:

//字典定义
typedef struct  dict{
	dictType *type;  //type和privdata是针对不同类型的键值对,为创建多态字典而设置的
	void *privdata;
	dictht ht[2];  //两个hashtable,用于存储和rehash
	long rehashidx;  //如果没有进行rehash,则值为-1,否则,rehashidx表示rehash进行到的索引位置
	unsigning long iterators;
}dict;

//哈希表定义
typedef struct dictht {
	dictEntry **table;  //指针,指向一个哈希桶bucket
	unsigning long size;  //哈希表的大小
	unsigning long sizemask;  //总是size-1,这个值和哈希值一起决定元素应该定位到桶中的什么位置
	unsigning long used;  //已使用的桶数量
}dictht;

//具体键值对定义
typedef struct dictEntry{
	void *key;   //键值
	union{
		void *val;  //value值
		unint64_tu64;
		int64_ts64;
	}v;
	struct dictEntry *next;  //指向下一个键值对的指针,用于解决哈希冲突问题
}dictEntry;

为什么每个字典中包含两个hashtable?

  1. 首先Redis在正常读写时会用到一个hashtable;
  2. 另一个hashtable的作用实际上是作为哈希表进行rehash时的一个临时载体。

  当需要进行扩容时,redis会根据数据量和桶的个数初始化那个备用的hashtable,来使这个hashtable从容量上满足后续的使用,并开始把之前的hashtable的数据迁移到这个新的hashtable上(重新计算哈希值),等到数据全部迁移完毕,再进行一次HashTable的地址更名,把这个备用的HashTable更名为正式的HashTable,同时清空另一个HashTable,以供下一次rehash的使用。然后将rehashidx赋值为0,打开渐进式rehash标志。同时该值也标志渐进式rehash当前已经进行到哪个hash值。

扩容的条件

负载因子 = 哈希表保存的key的数量 / 哈希表的大小

  1. 当前哈希表中保存的key的数量超过了哈希表的大小size,并且redis服务当前允许执行rehash(指的是当前没有子进程在执行AOF文件重写或者生成RDB文件《持久化操作》);
  2. 或者保存的节点数与哈希表的大小的比例超过了安全阈值(默认为5).

  即,当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:

  1. 服务器目前没有在执行BGSAVE命令或者BGREWRITEAPF命令,并且哈希表的负载因子>=1;
  2. 服务器目前正在执行BGSAVE命令或者BGREWRITEAPF命令,并且哈希表的负载因子>=5;。

如果程序正在进行持久化,这个时候进行rehash会导致数据不一致。

缩容的条件

元素个数低于数组长度的10%.
缩容不会考虑Redis是否正在做bgsave。

渐进式rehash初始化

不管是扩容(包含初始化)还是缩容,最终都是调用dictExpand函数来完成。
这个函数完成的任务:

  1. 判断:如果当前正在rehash或者传进来的扩容参数<当前哈希表中已使用的元素个数,则返回错误;
  2. 计算新的hash表大小,使得新的hash表的大小为一个2的n次方:>= size(传进来的参数)的第一个2的n次方;
  3. 判断:如果扩容后的大小 == 原来表的大小,则返回错误;
  4. 初始化ht[1],并且将rehashidx的值设置为0,返回正确;
操作辅助rehash(每当客户端请求时,rehash一个槽)

  在redis中,每一个增、删、改、查命令中都会判断哈希表是否正在进行扩容,如果是则帮助执行一次,调用_dictRehashStep(dict *d)函数。
  此函数仅执行一步hash表的重散列,并且仅当没有安全迭代器绑定到哈希表时。这是因为当我们在重新散列中有迭代器时,我们不能混淆打散两个哈希表的数据,否则某些元素可能被遗漏或者重复遍历。

static void _dictRehashStep(dict *d) {
   
    if (d->iterators == 
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值