一、前言
Redis 提供了 5 种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(汇合)、Zset(有序汇合),了解每种数据类型的特点对于 redis 的开发和运维十分重要。
<p align=”right”> 原文解析 </p>
Redis 中的 hash 是咱们常常应用到的一种数据类型,依据应用形式的不同,能够利用到很多场景中。
二、实现剖析
由上述结构图可知,Hash 类型有以下两种实现形式:
1、ziplist 编码的哈希对象应用压缩列表作为底层实现
2、hashtable 编码的哈希对象应用字典作为底层实现
1.ziplist 编码作为底层实现
ziplist 编码的哈希对象应用压缩列表作为底层实现,每当有新的键值对要退出到哈希对象时,程序会先将保留了键的压缩列表节点推入到压缩列表表尾,而后再将保留了值的压缩列表节点推入到压缩列表表尾,因而:
保留了同一键值对的两个节点总是紧挨在一起,保留键的节点在前,保留值的节点在后;
先增加到哈希对象中的键值对会被放在压缩列表的表头方向,而起初增加到哈希对象中的键值对会被放在压缩列表的表尾方向。
例如,咱们执行以下 HSET 命令,那么服务器将创立一个列表对象作为 profile 键的值:
redis> HSET profile name "Tom"
(integer) 1
redis> HSET profile age 25
(integer) 1
redis> HSET profile career "Programmer"
(integer) 1
profile 键的值对象应用的是 ziplist 编码,其中对象所应用的压缩列表构造如下图所示。
2.hashtable 编码作为底层实现
hashtable 编码的哈希对象应用字典作为底层实现,哈希对象中的每个键值对都应用一个字典键值对来保留:
字典的每个键都是一个字符串对象,对象中保留了键值对的键;字典的每个值都是一个字符串对象,对象中保留了键值对的值。
例如,如果后面 profile 键创立的不是 ziplist 编码的哈希对象,而是 hashtable 编码的哈希对象,那么这个哈希对象构造如下图所示。
三、命令实现
因为哈希键的值为哈希对象,所以用于哈希键的所有命令都是针对哈希对象来构建的,下表列出了其中一部分哈希键命令,以及这些命令在不同编码的哈希对象下的实现办法。
命令 | ziplist 编码实现办法 | hashtable 编码的实现办法 |
---|---|---|
HSET | 首先调用 ziplistPush 函数,将键推入到压缩列表的表尾,而后再次调用 ziplistPush 函数,将值推入到压缩列表的表尾。 | 调用 dictAdd 函数,将新节点增加到字典外面。 |
HGET | 首先调用 ziplistFind 函数,在压缩列表中查找指定键所对应的节点,而后调用 ziplistNext 函数,将指针挪动到键节点旁边的值节点,最初返回值节点。 | 调用 dictFind 函数,在字典中查找给定键,而后调用 dictGetVal 函数,返回该键所对应的值。 |
HEXISTS | 调用 ziplistFind 函数,在压缩列表中查找指定键所对应的节点,如果找到的话阐明键值对存在,没找到的话就阐明键值对不存在。 | 调用 dictFind 函数,在字典中查找给定键,如果找到的话阐明键值对存在,没找到的话就阐明键值对不存在。 |
HDEL | 调用 ziplistFind 函数,在压缩列表中查找指定键所对应的节点,而后将相应的键节点、以及键节点旁边的值节点都删除掉。 | 调用 dictDelete 函数,将指定键所对应的键值对从字典中删除掉。 |
HLEN | 调用 ziplistLen 函数,获得压缩列表蕴含节点的总数量,将这个数量除以 2,得出的后果就是压缩列表保留的键值对的数量。 | 调用 dictSize 函数,返回字典蕴含的键值对数量,这个数量就是哈希对象蕴含的键值对数量。 |
HGETALL | 遍历整个压缩列表,用 ziplistGet 函数返回所有键和值(都是节点)。 | 遍历整个字典,用 dictGetKey 函数返回字典的键,用 dictGetVal 函数返回字典的值。 |
四、编码转换
当哈希对象能够同时满足以下两个条件时,哈希对象应用 ziplist 编码:
哈希对象保留的所有键值对的键和值的字符串长度都小于 64 字节;哈希对象保留的键值对数量小于 512 个;
不能满足这两个条件的哈希对象须要应用 hashtable 编码。
留神 :这两个条件的上限值是能够批改的,具体请看配置文件中对于 hash-max-ziplist-value 选项和 hash-max-ziplist-entries 选项的阐明。
对于应用 ziplist 编码的列表对象来说,当应用 ziplist 编码所需的两个条件的任意一个不能被满足时,对象的编码转换操作就会被执行:本来保留在压缩列表里的所有键值对都会被转移并保留到字典外面,对象的编码也会从 ziplist 变为 hashtable。
以下代码展现了哈希对象编码转换的状况:
1. 键的长度太大引起编码转换
# 哈希对象只蕴含一个键和值都不超过 64 个字节的键值对
redis> HSET book name "Mastering C++ in 21 days"
(integer) 1
redis> OBJECT ENCODING book
"ziplist"
# 向哈希对象增加一个新的键值对,键的长度为 66 字节
redis> HSET book long_long_long_long_long_long_long_long_long_long_long_description "content"
(integer) 1
# 编码已扭转
redis> OBJECT ENCODING book
"hashtable"
2. 值的长度太大引起编码转换
# 哈希对象只蕴含一个键和值都不超过 64 个字节的键值对
redis> HSET blah greeting "hello world"
(integer) 1
redis> OBJECT ENCODING blah
"ziplist"
# 向哈希对象增加一个新的键值对,值的长度为 68 字节
redis> HSET blah story "many string ... many string ... many string ... many string ... many"
(integer) 1
# 编码已扭转
redis> OBJECT ENCODING blah
"hashtable"
3. 键值对数量过多引起编码转换
# 创立一个蕴含 512 个键值对的哈希对象
redis> EVAL "for i=1, 512 do redis.call('HSET', KEYS(http://www.yund.tech/yund-cms//sys/common/view/files/20200402/list-2.png), i, i) end" 1 "numbers"
(nil)
redis> HLEN numbers
(integer) 512
redis> OBJECT ENCODING numbers
"ziplist"
# 再向哈希对象增加一个新的键值对,使得键值对的数量变成 513 个
redis> HMSET numbers "key" "value"
OK
redis> HLEN numbers
(integer) 513
# 编码扭转
redis> OBJECT ENCODING numbers
"hashtable"
五、要点总结
1.Hash 类型两种编码方式,ziplist 与 hashtable
2.hashtable 编码的哈希对象应用字典作为底层实现
3.ziplist 与 hashtable 编码方式之间存在转换