本文共 1231字,阅读大约需要 3分钟,文末有计时器可自行对时


概   述

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

  • 实验源码环境:Redis 4.0.10

注: 本文首发于 My 公众号 CodeSheep ,可 长按  扫描 下面的 小心心 来订阅 ↓ ↓ ↓

本文内容脑图如下:


Redis哈希类型内部编码详情

对于 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的编码方式。

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

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

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

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


OBJ_ENCODING_ZIPLIST编码

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

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

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


OBJ_ENCODING_HT编码

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

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

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

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

  • 关于哈希节点(dictEntry)

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

  • 关于dictType

  • Redis如何计算Hash值

Redis计算Hash的源代码如下:

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

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

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

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

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


后  记

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

  • 我的个人博客:www.codesheep.cn


如果有兴趣,也可以抽时间看看作者关于容器化、微服务化方面的文章:

  • 一只菜鸡的半年技术博客之路

  • 利用K8S技术栈打造个人私有云连载文章

  • Spring Boot Admin 2.0 上手体验

  • 利用TICK搭建Docker容器可视化监控中心

  • 从一份配置清单详解Nginx服务器配置

  • Docker容器可视化监控中心搭建

  • 利用ELK搭建Docker容器化应用日志中心

  • 高效编写Dockerfile的几条准则

  • Docker容器跨主机通信

  • Spring Boot应用监控实战

  • RPC框架实践之:Google gRPC

  • RPC框架实践之:Apache Thrift

  • 微服务调用链追踪中心搭建



作者更多 务实、能看懂、可复现的 原创文章尽在公众号 CodeSheep,欢迎订阅 ⬇️⬇️⬇️


©著作权归作者所有:来自51CTO博客作者mb5fdcae83766b7的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. Redis五大数据类型内部编码剖析(一、String)
  2. Docker容器跨主机通信之:直接路由方式
  3. JavaWeb-LayUI框架的介绍与使用方式(前端框架篇)
  4. 【前端词典】4 (+1)种滚动吸顶实现方式的比较[性能升级版]
  5. 【前端词典】4 种滚动吸顶实现方式的比较
  6. pandas100个骚操作:变量类型自动转换
  7. 使用类型注解让 Python 代码更易读
  8. Python常用的数据存储方式有哪些?五种!
  9. 用数据分析大家最喜欢什么类型的抖音视频。

随机推荐

  1. Android(安卓)Hook神器:XPosed入门与登陆
  2. android 学习笔记(五)
  3. android小技巧
  4. android资源文件详解
  5. 系统签名apk--转
  6. Android Build System
  7. Android中获取颜色的几种方法
  8. android简单Logo
  9. android 动画
  10. android studio生成apk直接改名字