Redis 哈希结构内存模型剖析

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

Profile

本文共 1231字,阅读大约需要 5分钟 !


概述

在前文《Redis字符串类型内部编码剖析》之中已经剖析过 Redis最基本的 String类型的内部是怎么编码和存储的,本文再来阐述 Redis中使用 最为频繁的数据类型:哈希(或称散列),在Redis内部是怎么存的。

  • 实验源码环境:Redis 4.0.10

注: 本文首发于 My Personal Blog,欢迎光临 小站

本文内容脑图如下:

本文内容脑图


哈希类型内部编码详情

对于 Redis的常用 5 种数据类型(String、Hash、List、Set、sorted set),每种数据类型都提供了 最少两种 内部的编码格式,而且每个数据类型内部编码方式的选择 对用户是完全透明的,Redis会根据数据量自适应地选择较优化的内部编码格式。

如果想查看某个键的内部编码格式,可以使用 OBJECT ENCODING keyname 指令来进行,比如:

127.0.0.1:6379> 
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> 
127.0.0.1:6379> object encoding foo  // 查看某个Redis键值的编码
"embstr"
127.0.0.1:6379> 
127.0.0.1:6379> 

对于使用最为频繁的 Hash类型,其内部编码方式可能有两种:

  • OBJ_ENCODING_ZIPLIST(压缩列表)
  • OBJ_ENCODING_HT(哈希表)

Redis 会根据数据量的情况来自适应地选择这两种编码方式中 较优 的一种,而这一切对用户完全透明。

数据条目较少数据值较小 的时候 Redis会采用 压缩列表(OBJ_ENCODING_ZIPLIST)编码方式进行存储。这里成员"较少",成员值"较小"的标准可以通过如下配置项进行配置:

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

Redis 默认给出了默认值,当然用户可根据实际情况自行配置。

Hash类型键的字段个数 < hash-max-ziplist-entries 并且 每个字段名和字段值的长度 < hash-max-ziplist-value 时,Redis 会使用 OBJ_ENCODING_ZIPLIST来存储该键,反之则会转换为 OBJ_ENCODING_HT的编码方式。

口说无凭,我们不妨先来做个实验感受一下吧:

Redis 自适应地选择编码方式

很明显该实验验证了当 字段值长度大于64时,编码格式会由 ZIPLIST方式切换为 Hashtable方式。

源码之前,了无秘密,我们再来看一下Redis关于这部分切换的源码实现,那就理解得更加清楚了:

Redis哈希类型编码选择的源码

Redis 哈希类型编码选择的源码

下面详解 OBJ_ENCODING_ZIPLISTOBJ_ENCODING_HT 这两种编码格式的内部存储模型,知道了其各自特点和优缺点,自然也就明白了Redis内部使用它们的意图。


OBJ_ENCODING_ZIPLIST 编码

Ziplist 压缩列表是一种紧凑编码格式,总体思想是时间换空间,即以部分读写性能为代价,来换取极高的内存空间利用率,因此只会用于 字段个数少,且字段值也较小 的场景。

压缩列表内存利用率极高的原因与其连续内存的特性是分不开的,其典型的内存结构可以用下图形象地展示出来:

ZIPLIST 内存模型

所以如果用 Ziplist来存储 Redis的散列类型的话,元素的排列方式就变成了如下图所示的形象示意图:即key和value都是逻辑连续内存:

用 Ziplist来存储 Redis的散列类型


OBJ_ENCODING_HT 编码

OBJ_ENCODING_HT 这种编码方式内部才是真正的哈希表结构,或称为字典结构,其可以实现O(1)复杂度的读写操作,因此效率很高。

在 Redis内部,从 OBJ_ENCODING_HT类型到底层真正的散列表数据结构是一层层嵌套下去的,关系如下:

Redis哈希嵌套关系

这一关系我们可以从 Redis哈希表定义部分的源码来看出:

Redis哈希表定义部分的源码

下面来详解一下各个部分:

  • 关于哈希节点(dictEntry)

dictEntry

  • 关于哈希表(dictht)和字典(dict)

dictht 和 dict

  • 关于dictType

dictType

  • Redis如何计算Hash值

Redis计算Hash的源代码如下:

计算Hash值

这是一个 C语言宏定义,其实幕后真正承担 Hash值计算的是上面介绍的 dictType结构体中的函数指针 hashFunction

而该 hashFunction函数指针在初始化时会对应被赋值为一个个真实的计算 Hash值的实际函数,就像下面这样:

hashFunction 函数指针赋值

  • Redis如何计算存取索引Index值

Index值的计算依赖于上面计算得出的 Hash值,代码如下:

Redis计算索引Index值的源码

到此,还有一个一直非常值得关注的细节:即字典 dict里总是保存有两个 Hash表结构 ht[2],以及与其高度相关的 rehash操作,这在下一篇文章里详解。


后 记

由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
13天前
|
NoSQL 算法 Redis
redis内存淘汰策略
Redis支持8种内存淘汰策略,包括noeviction、volatile-ttl、allkeys-random、volatile-random、allkeys-lru、volatile-lru、allkeys-lfu和volatile-lfu。这些策略分别针对所有键或仅设置TTL的键,采用随机、LRU(最近最久未使用)或LFU(最少频率使用)等算法进行淘汰。
30 5
|
22天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
19 3
|
2月前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
53 10
|
2月前
|
存储 NoSQL Redis
Redis 哈希(Hash)
10月更文挑战第16天
42 1
|
2月前
|
存储 缓存 NoSQL
Redis Quicklist 竟让内存占用狂降50%?
【10月更文挑战第11天】
52 2
|
1月前
|
存储 NoSQL Redis
【赵渝强老师】Redis的存储结构
Redis 默认配置包含 16 个数据库,通过 `databases` 参数设置。每个数据库编号从 0 开始,默认连接 0 号数据库,可通过 `SELECT &lt;dbid&gt;` 切换。Redis 的核心存储结构包括 `dict`、`expires` 等字段,用于处理键值和过期行为。添加键时需指定数据库信息。视频讲解和代码示例详见内容。
|
3月前
|
缓存 监控 NoSQL
阿里面试让聊一聊Redis 的内存淘汰(驱逐)策略
大家好,我是 V 哥。粉丝小 A 面试阿里时被问到 Redis 的内存淘汰策略问题,特此整理了一份详细笔记供参考。Redis 的内存淘汰策略决定了在内存达到上限时如何移除数据。希望这份笔记对你有所帮助!欢迎关注“威哥爱编程”,一起学习与成长。
|
3月前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
157 3
|
4月前
|
存储 NoSQL 算法
Redis内存回收
Redis 基于内存存储,性能卓越,但单节点内存不宜过大,以免影响持久化或主从同步。可通过配置 `maxmemory` 限制最大内存。内存达到上限时,Redis采用两种策略:内存过期策略和内存淘汰策略。过期策略包括惰性删除和周期删除,后者分为 SLOW 和 FAST 模式。内存淘汰策略有八种,如 LRU、LFU 和随机淘汰等,用于在内存不足时释放空间。官方推荐使用 LFU 算法。
Redis内存回收
|
2月前
|
存储 NoSQL Redis
redis保存数据的结构-redisobject结构体
`redisObject`结构体是Redis内部数据组织的核心,它通过集成类型标识、引用计数和编码方式等关键信息,实现了数据的高效管理和访问。这种设计允许Redis根据数据的实际需求动态调整存储结构,既保证了内存使用的高效性,也确保了数据操作的灵活性和速度。通过对 `redisObject`的深入了解,可以更好地掌握Redis如何在内存中高效存储和操作数据,进而优化数据库的性能和资源利用。
26 0