Redis,作为一个开源的、内存中的数据结构存储系统,以其出色的性能和灵活的数据类型,广泛应用于缓存、消息队列、发布订阅系统等多种场景。在 Redis 的五种基本数据类型中,Hash 类型是一种非常重要的数据类型。它可以存储键值对的集合,且能够用小于1毫秒的时间复杂度进行添加、删除、更新和查找操作,因此在实际应用中有着广泛的用途。 在接下来的文章中,我将详细介绍 Redis 的 Hash 类型,包括它的内部实现、主要特性、常用命令以及应用场景。无论你是刚接触 Redis 的新手,还是已经有一定经验的开发者,我相信你都能从这篇文章中学到一些有用的知识。让我们一起深入了解 Redis 的 Hash 类型,探索它的魅力所在。
1、Redis-Hash数据类型
1.1、Redis-Hash类型简介
Redis 的 Hash 类型是一种键值对集合,这种数据类型适合用于存储对象。在 Hash 类型中,每个键都有一个对应的值,这和 Python 的字典、Java 的 HashMap 以及 JavaScript 的对象非常相似。
以下是 Redis Hash 类型的一些主要特性:
- 键值对集合:Hash 类型可以存储多个键值对,每个键都有一个对应的值。
- 二进制安全:Hash 类型的键和值都是二进制安全的,这意味着它们可以包含任何数据,包括二进制数据。
- 大容量:单个 Hash 类型可以存储超过 4 亿个键值对。
- 高效的查找速度:无论 Hash 中存储了多少数据,查找某个键的速度都非常快。
1.2、Redis-Hash应用场景
Redis 的 Hash 类型是一种键值对集合,适合用于存储对象,因此在很多场景下都有着广泛的应用。以下是一些常见的应用场景:
- 存储对象:Hash 类型可以存储多个键值对,非常适合用于存储对象。例如,你可以使用 Hash 类型存储用户的信息,如用户名、密码、邮箱等;
- 数据分析:你可以使用 Hash 类型存储各种统计数据,例如用户的行为数据,然后进行数据分析;
- 社交网络:在社交网络应用中,你可以使用 Hash 类型存储用户的朋友列表、粉丝列表等
以上只是一些常见的应用场景,实际上,由于 Redis 的灵活性,你可以根据自己的需求,将 Redis 的 Hash 类型应用在更多的场景中。
2、Redis-Hash底层结构
2.1、Redis-Hash底层结构介绍
Redis 的 Hash 类型的底层实现是一个非常优化的数据结构,它会根据实际情况选择使用紧凑的压缩列表(ziplist)或者散列表(hashtable)作为底层实现。
Redis 的 Hash 类型会根据实际情况在压缩列表(ziplist)和散列表(hashtable)之间进行切换,这主要取决于两个配置参数:hash-max-ziplist-entries
和 hash-max-ziplist-value
。
hash-max-ziplist-entries
:这个参数用于设置压缩列表可以存储的最大节点数量。如果一个 Hash 类型的元素数量超过这个值,那么就会从压缩列表切换到散列表。默认值为 512;hash-max-ziplist-value
:这个参数用于设置压缩列表中每个节点的最大值大小(以字节为单位)。如果一个 Hash 类型的任何元素的大小超过这个值,那么就会从压缩列表切换到散列表。默认值为 64。
这两个参数都可以在 Redis 的配置文件中进行设置。通过调整这两个参数,你可以根据自己的应用特性,选择更倾向于节省内存,还是更倾向于提高性能。
- 从压缩列表转换到散列表:当 Hash 类型存储的字段和值的数量超过
hash-max-ziplist-entries
的值,或者任何字段或值的大小超过hash-max-ziplist-value
的值时,Redis 会将底层结构从压缩列表转换为散列表。这个过程是自动进行的,对用户来说是透明的。 - 从散列表转换到压缩列表:然而,一旦 Hash 类型的底层结构被转换为散列表,就无法再转换回压缩列表。这是因为散列表的性能更高,而且在大多数情况下,一旦一个 Hash 类型的大小超过了一定的阈值,那么它的大小就很可能会继续增长。
2.2、Redis-压缩列表(ziplist)
当 Hash 类型存储的字段和值的数量较少,且字段和值的字符串长度较短时,Redis 会选择使用压缩列表作为底层实现。压缩列表是一种为节省内存而设计的特殊编码结构,它将所有的字段和值紧凑地存储在一起。这种方式的优点是占用内存少,但是在需要修改数据时,可能需要对整个压缩列表进行重写,性能较低。
Redis 的压缩列表(ziplist)是一种特殊的编码结构,它被设计用来节省内存。压缩列表将所有的元素紧凑地存储在一起,每个元素都只占用连续的内存空间。
一个压缩列表的结构如下:
代码语言:javascript复制 --------- --------- -------- --------- --------- --------- --------
| zlbytes | zltail | zllen | entry_1 | entry_2 | ... | zlend |
--------- --------- -------- --------- --------- --------- --------
Ps:在 Redis 的源代码中,压缩列表(ziplist)的结构并没有直接定义为一个 C 结构体,而是通过一系列的宏和函数来操作一段连续的内存。
属性 | 说明 |
---|---|
“zlbytes” | 一个 4 字节的整数,表示整个压缩列表占用的字节数量,包括 <zlbytes> 自身的大小。 |
“zltail” | 一个 4 字节的整数,表示压缩列表中最后一个元素的偏移量。这个偏移量是相对于整个压缩列表的起始地址的。 |
“zllen” | 一个 2 字节的整数,表示压缩列表中的元素数量。如果元素数量超过 65535,那么这个值就会被设定为 65535,需要遍历整个压缩列表才能获取到实际的元素数量。 |
“entry” | 压缩列表中的元素,每个元素都由一个或多个字节组成。每个元素的第一个字节(又称为"entry header")用于表示这个元素的长度以及编码方式。 |
“zlend” | 一个字节,值为 255,表示压缩列表的结束。 |
2.3、Redis-散列表(hashtable)
当 Hash 类型存储的字段和值的数量较多,或者字段和值的字符串长度较长时,Redis 会选择使用散列表作为底层实现。散列表是一种常见的键值对映射结构,它通过一个散列函数将键映射到一个桶中,然后在桶中进行查找。这种方式的优点是查找和修改数据的性能较高,但是占用的内存也较多。
Redis 的散列表(hash table)是一种常见的键值对映射结构,它通过一个散列函数将键映射到一个桶中,然后在桶中进行查找。Redis 的散列表使用链表法解决哈希冲突,即当多个键映射到同一个桶时,将它们存储在同一个链表中。
在 Redis 的源代码中,散列表的结构定义如下:
代码语言:javascript复制typedef struct dictEntry {
void *key;
void *val;
struct dictEntry *next;
} dictEntry;
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dict {
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
其中:
-
dictEntry
结构体表示散列表中的一个节点,包含键(key
)、值(val
)和指向下一个节点的指针(next
)。 -
dictht
结构体表示一个散列表,包含指向哈希表数组的指针(table
)、哈希表数组的大小(size
)、哈希表数组大小掩码(sizemask
)和已使用的节点数量(used
)。 -
dict
结构体表示一个字典,包含两个散列表(ht
)、当前进行 rehash 的索引(rehashidx
)和当前运行的迭代器数量(iterators
)。
这就是 Redis 散列表的结构。
3、Hash 常用命令
以下是 Redis 中与 Hash 类型相关的一些命令:
3.1、设置Hash值
Redis 中设置 Hash 值的命令是 HSET
,它的语法如下:
HSET key field value
其中,key
是哈希表的名称,field
是哈希表中的字段名,value
是字段对应的值。如果哈希表中原本不存在该字段,则会创建一个新的字段,并将其值设置为指定的值。如果该字段已经存在,则会覆盖原有的值。
例如,我们可以使用以下命令设置一个名为 user:1001
的哈希表中的字段 name
的值为 Alice
:
HSET user:1001 name Alice
如果需要同时设置多个字段的值,可以使用 HMSET
命令,它的语法如下:
HMSET key field1 value1 [field2 value2 ...]
例如,我们可以使用以下命令同时设置 user:1001
哈希表中的 name
和 age
字段的值:
HMSET user:1001 name Alice age 25
这样就可以一次性设置多个字段的值了。
在 Redis 中,获取 Hash 值的命令是 HGET
,它的语法如下:
HGET key field
其中,key
是哈希表的名称,field
是哈希表中的字段名。
例如,我们可以使用以下命令获取一个名为 user:1001
的哈希表中的字段 name
的值:
HGET user:1001 name
3.2、获取Hash值
如果需要获取哈希表中的多个字段的值,可以使用 HMGET
命令,它的语法如下:
HMGET key field1 [field2 ...]
例如,我们可以使用以下命令获取 user:1001
哈希表中的 name
和 age
字段的值:
HMGET user:1001 name age
如果需要获取哈希表中所有的字段和值,可以使用 HGETALL
命令,它的语法如下:
HGETALL key
例如,我们可以使用以下命令获取 user:1001
哈希表中的所有字段和值:
HGETALL user:1001
3.3、删除Hash值
在 Redis 中,删除 Hash 值的命令是 HDEL
,它的语法如下:
HDEL key field [field ...]
其中,key
是哈希表的名称,field
是要删除的字段名。你可以一次删除一个或多个字段。
例如,我们可以使用以下命令删除一个名为 user:1001
的哈希表中的字段 name
:
HDEL user:1001 name
如果你想要删除多个字段,可以在命令后面依次列出这些字段的名字,例如:
代码语言:javascript复制HDEL user:1001 name age
这个命令会删除 user:1001
哈希表中的 name
和 age
字段。
需要注意的是,HDEL
命令只会删除指定的字段及其值,而不会删除整个哈希表。如果你想要删除整个哈希表,可以使用 DEL
命令,例如:
DEL user:1001
这个命令会删除整个 user:1001
哈希表。
3.4、其他Hash命令
Redis 中 Hash 其他的一些常用命令还有:
HEXISTS key field
:查看哈希表 key 中,指定的字段是否存在。HLEN key
:获取哈希表中字段的数量。HKEYS key
:获取所有哈希表中的字段。HVALS key
:获取哈希表中所有值。HGETALL key
:获取在哈希表中指定 key 的所有字段和值。HINCRBY key field increment
:为哈希表 key 中的指定字段的整数值加上增量 increment 。HINCRBYFLOAT key field increment
:为哈希表 key 中的指定字段的浮点数值加上增量 increment 。HMSET key field value [field value ...]
:同时将多个 field-value (字段-值)对设置到哈希表的 key 中。HMGET key field [field ...]
:获取所有给定字段的值。