乐趣区

关于redis:闲扯Redis六Redis五种数据类型之Hash型

一、前言

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 编码方式之间存在转换

退出移动版