Redis字典实现、Hash键冲突及渐进式rehash详解


Posted in Redis onSeptember 04, 2021

本笔记参考《Redis设计与实现》 P24~ 37

Redis字典实现

哈希表节点结构

typedef struct dictEntry
{
	// 键
	void *key;

	// 值 : 可以是一个指针,或者是一个uint64/int64 的整数
	union {
		void *val;
		uint64_t u64;
		int64_t s64
	} v;

	// 指向下一个哈希表节点,形成链表 : 该指针可以将多个哈希值相同的键值对连接在一起,以此解决键冲突的问题。
	struct dictEntry *next;
} dictEntry;

哈希表结构

typedef struct dictht
{
	// 哈希表数据
	dictEntry **table;

	// 哈希表集合大小
	unsigned long size;

	// 哈希表大小掩码,用于计算索引值
	// 总是等于 size - 1
	unsigned long sizemask;

	// 哈希表已有节点数量
	unsigned long used;
} dictht;

字典

typedef struct dict 
{
	// 类型特定函数
	dicType *type;

	// 私有数据
	void *privdata;

	// 哈希表
	dictht ht[2];

	// rehash 索引
	// 当rehash不在进行时, 值为-1
	int rehashidx;
} dict;

type属性和privdata属性针对不同类型的键值对,为多态字典而设置。
ht是包含两个项的数组,每个元素都是一个dictht哈希表,一般情况下字典之是哟个ht[0],ht[1]会在对ht[0]进行rehash的时候使用。
rehashidx记录了rehash目前的进度,如果目前没有在进行rehash,值为-1。

哈希算法

  • 使用字典设置的哈希函数,计算key的hashvalue

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

  • 使用哈希表的sizemask属性和哈希值,计算出索引值
  • 根据不同的情况,ht[x]可以是ht[0]或ht[1]

index = hash & dict->ht[x].sizemask;

redis使用的是MurmurHash算法,优点是:输入的键是有规律的时候,算法仍然能给出很好的随机分布性,计算速度也快。

解决hash冲突

当有两个或以上的key分配到了hash table数组的同一个index上,称为发生了collision。
Redis采用链地址法解决冲突,每个hash table节点都有一个next指针,多个hash table节点可以用next指针构成一个单向链表。为了速度考虑,程序总是会将新节点插入到链表头位置。

rehash

随着操作不断执行,哈希表保存的key value对会逐渐增加和减少。哈希表有一个统计参数load factor,即负载因子,公式如下:

# 负载因子 = 哈希表已经保存的节点数量 / 哈希表大小
load_factor = ht[0].used / ht[0].size;

为了维持负载因子在一个合理的范围,程序会对哈希表的大小进行相应的扩展或收缩,条件如下:

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

2、服务器正在执行BGSAVE命令或者BGREWRITEAOF命令,且负载因子 >= 5

  • 在执行BGSAVE命令或者BGREWRITEAOF命令过程中,Redis需要创建当前服务器进程的子进程,大多的OS采用写时复制技术优化子进程的使用效率,所以子进程存在期间,**服务器会提高执行扩展操作的负载因子,避免在子进程存在期间进行哈希表的扩展操作,避免不必要的内存写入操作,最大限度节约内存。**当负载因子小于0.1时,程序自动对哈希表进行收缩操作。
  • 此时就会进行扩展收缩,规则如下:
  • 这里就是rehash(重新散列)操作了:
  • 1、为字典的ht[1]哈希表分配内存空间,空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(ht[0].used)
  • 如果是扩展操作,ht[1]的大小为 >= ht[0].used * 2的 2的幂次方
  • 如果是收缩操作,ht[1]的大小为 >= ht[0].used 的 2的幂次方
  • 2、将保存在ht[0]中的所有键值对rehash到ht[1]上:即重新计算key的hashValue以及indexValue,然后将键值对放到ht[1]的指定位置
  • 3、当ht[0]包含的所有键值对都迁移到ht[1]之后,ht[0]变为空表,释放ht[0],将ht[1]置为ht[0],在ht[1]重新分配一个空白的哈希表,为下一次rehash做准备

渐进式hash

rehash的动作并不是一次性集中完成的,而是分多次渐进完成。
如果哈希表中村的键值对数量很多,一次性将键值对全部rehash到ht[1]的计算量十分庞大,可能会导致服务器在一段时间内停止服务。
渐进式rehash采取分而治之的方法,将rehash键值对所需要的计算工作分摊到每次对字典的CRUD操作上,从而避免了集中式rehash带来的庞大计算量。
详细步骤如下:
1、为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
2、在字典中维护一个索引计数器:rehashidx,将值设置为0,表示rehash工作正式开始。
3、在rehash进行期间,每次对字典的CRUD操作,程序除了执行指定操作以外,顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1]上,当rehash操作完成后,程序将rehashidx值++
4、重复迭代操作执行后,ht[0]的数据全部rehash到ht[1]上,将rehashidx设为-1,表明rehash操作已经完成

需要注意的地方
在rehash的过程中,对于字典的删除、查找、更新操作会在两个哈希表上执行。如想要查找一个键,现在ht[0]中找,没有找到再去ht[1]
对于insert操作来说,新添加到字典的键值对会一律保存到ht[1]中,不然还得多一次搬运。

到此这篇关于Redis字典实现、Hash键冲突以及渐进式rehash的文章就介绍到这了,更多相关Redis 渐进式rehash内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
在K8s上部署Redis集群的方法步骤
Apr 27 Redis
使用Redis实现秒杀功能的简单方法
May 08 Redis
redis 限制内存使用大小的实现
May 08 Redis
Redis高级数据类型Hyperloglog、Bitmap的使用
May 24 Redis
聊一聊Redis与MySQL双写一致性如何保证
Jun 26 Redis
浅谈redis整数集为什么不能降级
Jul 25 Redis
在项目中使用redis做缓存的一些思路
Sep 14 Redis
Redis三种集群模式详解
Oct 05 Redis
Redis之RedisTemplate配置方式(序列和反序列化)
Mar 13 Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Mar 17 Redis
Redis基本数据类型哈希Hash常用操作命令
Jun 01 Redis
Redis唯一ID生成器的实现
Jul 07 Redis
基于Redis的List实现特价商品列表功能
Aug 30 #Redis
Redis 常见使用场景
Aug 30 #Redis
Redis入门教程详解
Redis如何实现分布式锁
Aug 23 #Redis
Redisson实现Redis分布式锁的几种方式
Redis分布式锁Redlock的实现
Aug 07 #Redis
关于redisson缓存序列化几枚大坑说明
Aug 04 #Redis
You might like
PHP利用COM对象访问SQLServer、Access
2006/10/09 PHP
基于数据库的在线人数,日访问量等统计
2006/10/09 PHP
php正则表达匹配中文问题分析小结
2012/03/25 PHP
利用yahoo汇率接口实现实时汇率转换示例 汇率转换器
2014/01/14 PHP
PHP重定向与伪静态区别
2017/02/19 PHP
Centos 6.5下PHP 5.3安装ffmpeg扩展的步骤详解
2017/03/02 PHP
让FireFox支持innerText的实现代码
2009/12/01 Javascript
jquery offset函数应用实例
2012/11/14 Javascript
jQuery设置指定网页元素宽度和高度的方法
2015/03/25 Javascript
javascript学习指南之回调问题
2016/04/23 Javascript
Three.js学习之文字形状及自定义形状
2016/08/01 Javascript
jquery网页日历显示控件calendar3.1使用详解
2016/11/24 Javascript
解析NodeJS异步I/O的实现
2017/04/13 NodeJs
vue better-scroll插件使用详解
2018/01/25 Javascript
Vue.directive 自定义指令的问题小结
2018/03/04 Javascript
JavaScript中call和apply方法的区别实例分析
2018/08/03 Javascript
详解用async/await来处理异步
2019/08/28 Javascript
[01:19:33]DOTA2-DPC中国联赛 正赛 iG vs VG BO3 第一场 2月2日
2021/03/11 DOTA
python库lxml在linux和WIN系统下的安装
2018/06/24 Python
python程序文件扩展名知识点详解
2020/02/27 Python
Python中操作各种多媒体,视频、音频到图片的代码详解
2020/06/04 Python
台湾三立电视电商平台:电电购
2019/09/09 全球购物
The North Face北面法国官网:美国著名户外品牌
2019/11/01 全球购物
西班牙用户之间买卖视频游戏的平台:Wakkap
2020/03/21 全球购物
static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
2015/02/22 面试题
大学生活自我评价
2014/04/09 职场文书
服务行业口号
2014/06/11 职场文书
办理收楼委托书范本
2014/10/09 职场文书
教师党的群众路线教育实践活动个人整改方案
2014/10/31 职场文书
2014年行政助理工作总结
2014/11/19 职场文书
有限责任公司股东合作协议书
2014/12/02 职场文书
自我检讨书怎么写
2015/05/07 职场文书
2016年精神文明建设先进个人事迹材料
2016/02/29 职场文书
研究生毕业登记表的自我鉴定范文
2019/07/15 职场文书
如何设计高效合理的MySQL查询语句
2021/05/26 MySQL
Python pandas之求和运算和非空值个数统计
2021/08/07 Python