Redis 字典结构细谈
Redis 字典底层基于哈希表实现。
一、哈希表结构
1、dictht:
typedef struct dictht {
dictEntry **table; //哈希表数组,存储具体的键值对元素,对象类型 dictEntry
unsigned long size; //哈希表容量
unsigned long sizemask; //哈希表大小掩码,计算索引使用
unsigned long used; //已使用容量
} dictht
2、示例数据:

二、哈希表节点
1、dictEntry:
typedef struct dictEntry {
void *key; //键值对 key
union{ //键值对 value 三种类型
void *val;
uint64_tu64;
int64_ts64;
} v;
struct dictEntry *next; //下一个节点指针
} dictEntry;
说明:next 为指向下一个节点的指针,是我们熟悉的链表节点结构,单向链表,用于处理键哈希冲突问题。
相同哈希值的键的键值对会以链表的形式存在同一位置。
2、示例数据:

三、Redis 字典
1、dict:
typedef struct dict{
dictType *type; //类型特定函数
void *privdata; //私有数据
dictht ht[2]; //哈希表数组,类型为dictht,ht[0]为实际存储数据使用,ht[1] 为rehash时使用
int rehashidx; //rehash进度标志,-1 代表当前不在 rehash
} dict
2、示例数据:

四、添加元素
向字典中添加元素主要涉及一下几步操作:
1、计算键值对键的哈希值
hash:dict->type->hashFunction(key)
使用dictType内部的哈希函数得到键哈希值
2、计算需要放入的位置索引
index:hash&dict->ht[0].sizemask
使用上一步计算得到的哈希值与哈希表的sizemask属性进行与操作得到需要放入的位置索引值
3、键冲突解决
没有完美的哈希函数,哈希冲突往往无法避免,当多个键被所引导同一个位置时,这种现象,我们称之为键冲突。
解决间冲突,Redis 采用链地址法,也即将冲突的键值对组成一条链条放到同一个哈希位置上。上面第二节我们介绍过 dictEntry的结构,其中包含一个指向另一个节点的指针next。
这里需要说明的一点是,冲突节点插入时,是插入到链表的头部,这样只需要执行操作一次操作即可,也即时间复杂度为O(1)。
如下图:(k2,v2)与(k1,v1)发生冲突,直接将(k2,v2)插入到链表头部:

五、rehash
rehash过程是在重新规划哈希表占用空间时发生的。
负载因子 load_factor:已保存节点数量(dict.ht[0].used)/ 哈希表容量(dict.ht[0].size)
负载因子用以表名当前哈希表的使用状态,它需要保持在一个合理的范围,以保障资源的最优利用。通常需要适时的对哈希表进行扩展或者收缩来对负载因子进行维护,而这个过程,我们称之为 rehash。
这里涉及到一个问题,就是什么时候需要进行伸缩维护?
1、扩展时机:
当前无bgsave及bgrewriteaop操作,load_factor >= 1
当前存在bgsave及bgrewriteaop操作,load_factor >= 5
Redis服务器通过fork子进程形式执行bgsave及bgrewriteaop操作,此时整个服务的资源耗费较大,为了避免可能发生的rehash带来额外的资源压力,此期间,服务器会调高触发执行扩展操作的负载因子界限。
2、收缩时机:
load_factor < 0.1
3、rehash 基本操作:
a) 为dict.ht[1]分配空间:
空间大小计算如下:
扩展:最小n满足2n >= dict.ht[0].used * 2
收缩:最小n满足2n >= dict.ht[0].used
如下图:ht[0].used = 3,假定无bg相关任务,则h[1]大小需要计算:2n >= 3 * 2 = 6
n = 3,ht[1].size = 23 = 8

b) rehash
对于dict.ht[0] 中的元素,依据dict.ht[1]特性(sizemask)重新计算索引值,并放置到dict.ht[1]中。

c) 当所有元素迁移完毕,释放dict.ht[0],并将dict.ht[1]设置为dict.ht[0],重新在dict.ht[1]上创建空的哈希表。

六、渐进式rehash
所谓渐进式,是针对大数据量字典数据。直接一次性的执行rehash会导致服务资源的集中占用,影响正常的服务响应。因此需要进行分而治之。
这里会用到上面我们介绍的dict字典结构中的 rehashidx属性,用以标识当前rehash进度。
首先将rehashidx置0,标示rehash开始,每次rehash一个元素,rehashidx值增加1,当最终所有元素rehash完成,将rehashidx置-1。
这里需要说明下rehash中对正常的服务请求的处理:
1、删除、查找、更新:
会涉及到两个哈希表(ht[0]、ht[1])操作,如查找元素,首先尝试在ht[0]上查找,找不到,则继续在h[1]上查找。
2、添加
添加元素只会在h[1]上操作,h[0]上只减不增。
Redis 字典结构细谈的更多相关文章
- 《闲扯Redis七》Redis字典结构的底层实现
一.前言 上节<闲扯Redis六>Redis五种数据类型之Hash型 中说到 Hash(哈希对象)的底层实现有: 1.ziplist 编码的哈希对象使用压缩列表作为底层实现 2.hasht ...
- Redis核心原理与实践--散列类型与字典结构实现原理
Redis散列类型可以存储一组无序的键值对,它特别适用于存储一个对象数据. > HSET fruit name apple price 7.6 origin china 3 > HGET ...
- redis中的字典结构是怎样的?
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 基础概念 redis支持的5种数据类型中,有hash类型,hash类型的 ...
- 【Redis 系列】redis 学习十六,redis 字典(map) 及其核心编码结构
redis 是使用 C 语言编写的,但是 C 语言是没有字典这个数据结构的,因此 C 语言自己使用结构体来自定义一个字典结构 typedef struct redisDb src\server.h 中 ...
- REDIS 字典数据结构
对于REDIS来讲 其实就是一个字典结构,key ---->value 就是一个典型的字典结构 [当然 对于vaule来讲的话,有不同的内存组织结构 这是后话] 试想一个这样的存储场景: ...
- redis 字典
redis 字典 前言 借鉴了 黄健宏 的 <<Redis 设计与实现>> 一书, 对 redis 源码进行学习 欢迎大家给予意见, 互相沟通学习 概述 字典是一种用于存储键值 ...
- Redis 字典的实现
[Redis 字典的实现] 注意 dict 类型使用了两个指针,分别指向两个哈希表. 其中, 0 号哈希表(ht[0])是字典主要使用的哈希表, 而 1 号哈希表(ht[1])则只有在程序对 0 号哈 ...
- [算法]从Trie树(字典树)谈到后缀树
我是好文章的搬运工,原文来自博客园,博主July_,地址:http://www.cnblogs.com/v-July-v/archive/2011/10/22/2316412.html 从Trie树( ...
- 阿里面试官:HashMap 熟悉吧?好的,那就来聊聊 Redis 字典吧!
最近,小黑哥的一个朋友出去面试,回来跟小黑哥抱怨,面试官不按套路出牌,直接打乱了他的节奏. 事情是这样的,前面面试问了几个 Java 的相关问题,我朋友回答还不错,接下来面试官就问了一句:看来 Jav ...
随机推荐
- JavaScript函数及面向对象
函数及面向对象 目录 函数及面向对象 1. 定义函数 1. 定义方式一 2. 定义方式二 2. 调用函数 1. 参数问题 2. arguments 3. rest 3. 变量的作用域 1 . var的 ...
- JavaWeb实现图片上传功能
首先导入文件上传的jar包 然后在Spring-servlet.xml文件中设置上传文件解析器 <!--上传文件解析器--> <bean id="multipartReso ...
- 利用预编译解决C/C++重复定义的错误 -2020.09.13
利用预编译解决C/C++重复定义的错误 -2020.09.13 我们现在有main.c和function.h两个文件 main.c #include <stdio.h> #include ...
- Java枚举解读
Java枚举 枚举类概念的理解与定义 一个类的对象是有限个,确定的,我们称此为枚举类. 当需要定义和维护一组常量时,强烈建议使用枚举类. 如果一个枚举类中只有一个对象,则可以作为单例模式的实现方式. ...
- express 4.0 connect-mongo 运行时报错的解决方法
如果使用的是express 4.0,且入口文件app.js的模板引入和中间件配置如下 如果你的package.json文件中有以下版本信息 报出的错误如下所示 或者 那么只需修改app.js中 ...
- 现有 Vue.js 项目快速实现多语言切换的一种思路
Web 项目多语言(i18n,即国际化)是比较常见的需求,常规的做法大概有以下几种: 每种语言单独开发页面,适用于 CMS 之类的网站 多语言文本和页面结构分离,运行时动态替换.适用于单页应用(SPA ...
- 如何使用二维码进行表白-一键制作二维码,gif二维码制作
" 如何使用二维码进行表白-一键制作二维码" 二维码在我们现在生活中随处可见,因为一次突发奇想,那我们可不可以制作一个自己的二维码呢!然后让女朋友去扫描一下----嘿嘿嘿--不行, ...
- golang defer 以及 函数栈和return
defer 作为延迟函数存在,在函数执行结束时才会正式执行,一般用于资源释放等操作 参考一段代码https://mp.weixin.qq.com/s/yfH0CBnUBmH0oxfC2evKBA来分析 ...
- Linux环境下C++调试的三板斧
调试解决程序的漏洞,是程序员最基本的技能之一.用惯了图形化IDE,在目前使用gtest框架进行单元测试,需要通过xshell远程连接Linux虚拟机进行C++代码的调试时,觉得很不适应.经过几天查资料 ...
- gRPC-go 入门(1):Hello World
摘要 在这篇文章中,主要是跟你介绍一下gRPC这个东西. 然后,我会创建一个简单的练习项目,作为gRPC的Hello World项目. 在这个项目中,只有很简单的一个RPC函数,用于说明gRPC的工作 ...