Redis数据结构:Hash类型全面解析

2023-10-16 14:36:09 浏览数 (2)

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 类型的一些主要特性:

  1. 键值对集合:Hash 类型可以存储多个键值对,每个键都有一个对应的值。
  2. 二进制安全:Hash 类型的键和值都是二进制安全的,这意味着它们可以包含任何数据,包括二进制数据。
  3. 大容量:单个 Hash 类型可以存储超过 4 亿个键值对。
  4. 高效的查找速度:无论 Hash 中存储了多少数据,查找某个键的速度都非常快。
1.2、Redis-Hash应用场景

Redis 的 Hash 类型是一种键值对集合,适合用于存储对象,因此在很多场景下都有着广泛的应用。以下是一些常见的应用场景:

  1. 存储对象:Hash 类型可以存储多个键值对,非常适合用于存储对象。例如,你可以使用 Hash 类型存储用户的信息,如用户名、密码、邮箱等;
  2. 数据分析:你可以使用 Hash 类型存储各种统计数据,例如用户的行为数据,然后进行数据分析;
  3. 社交网络:在社交网络应用中,你可以使用 Hash 类型存储用户的朋友列表、粉丝列表等

以上只是一些常见的应用场景,实际上,由于 Redis 的灵活性,你可以根据自己的需求,将 Redis 的 Hash 类型应用在更多的场景中。

2、Redis-Hash底层结构
2.1、Redis-Hash底层结构介绍

Redis 的 Hash 类型的底层实现是一个非常优化的数据结构,它会根据实际情况选择使用紧凑的压缩列表(ziplist)或者散列表(hashtable)作为底层实现。

Redis 的 Hash 类型会根据实际情况在压缩列表(ziplist)和散列表(hashtable)之间进行切换,这主要取决于两个配置参数:hash-max-ziplist-entrieshash-max-ziplist-value

  1. hash-max-ziplist-entries:这个参数用于设置压缩列表可以存储的最大节点数量。如果一个 Hash 类型的元素数量超过这个值,那么就会从压缩列表切换到散列表。默认值为 512;
  2. hash-max-ziplist-value:这个参数用于设置压缩列表中每个节点的最大值大小(以字节为单位)。如果一个 Hash 类型的任何元素的大小超过这个值,那么就会从压缩列表切换到散列表。默认值为 64。

这两个参数都可以在 Redis 的配置文件中进行设置。通过调整这两个参数,你可以根据自己的应用特性,选择更倾向于节省内存,还是更倾向于提高性能。

  1. 从压缩列表转换到散列表:当 Hash 类型存储的字段和值的数量超过 hash-max-ziplist-entries 的值,或者任何字段或值的大小超过 hash-max-ziplist-value 的值时,Redis 会将底层结构从压缩列表转换为散列表。这个过程是自动进行的,对用户来说是透明的。
  2. 从散列表转换到压缩列表:然而,一旦 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,它的语法如下:

代码语言:javascript复制
HSET key field value

其中,key 是哈希表的名称,field 是哈希表中的字段名,value 是字段对应的值。如果哈希表中原本不存在该字段,则会创建一个新的字段,并将其值设置为指定的值。如果该字段已经存在,则会覆盖原有的值。

例如,我们可以使用以下命令设置一个名为 user:1001 的哈希表中的字段 name 的值为 Alice

代码语言:javascript复制
HSET user:1001 name Alice

如果需要同时设置多个字段的值,可以使用 HMSET 命令,它的语法如下:

代码语言:javascript复制
HMSET key field1 value1 [field2 value2 ...]

例如,我们可以使用以下命令同时设置 user:1001 哈希表中的 nameage 字段的值:

代码语言:javascript复制
HMSET user:1001 name Alice age 25

这样就可以一次性设置多个字段的值了。

在 Redis 中,获取 Hash 值的命令是 HGET,它的语法如下:

代码语言:javascript复制
HGET key field

其中,key 是哈希表的名称,field 是哈希表中的字段名。

例如,我们可以使用以下命令获取一个名为 user:1001 的哈希表中的字段 name 的值:

代码语言:javascript复制
HGET user:1001 name
3.2、获取Hash值

如果需要获取哈希表中的多个字段的值,可以使用 HMGET 命令,它的语法如下:

代码语言:javascript复制
HMGET key field1 [field2 ...]

例如,我们可以使用以下命令获取 user:1001 哈希表中的 nameage 字段的值:

代码语言:javascript复制
HMGET user:1001 name age

如果需要获取哈希表中所有的字段和值,可以使用 HGETALL 命令,它的语法如下:

代码语言:javascript复制
HGETALL key

例如,我们可以使用以下命令获取 user:1001 哈希表中的所有字段和值:

代码语言:javascript复制
HGETALL user:1001
3.3、删除Hash值

在 Redis 中,删除 Hash 值的命令是 HDEL,它的语法如下:

代码语言:javascript复制
HDEL key field [field ...]

其中,key 是哈希表的名称,field 是要删除的字段名。你可以一次删除一个或多个字段。

例如,我们可以使用以下命令删除一个名为 user:1001 的哈希表中的字段 name

代码语言:javascript复制
HDEL user:1001 name

如果你想要删除多个字段,可以在命令后面依次列出这些字段的名字,例如:

代码语言:javascript复制
HDEL user:1001 name age

这个命令会删除 user:1001 哈希表中的 nameage 字段。

需要注意的是,HDEL 命令只会删除指定的字段及其值,而不会删除整个哈希表。如果你想要删除整个哈希表,可以使用 DEL 命令,例如:

代码语言:javascript复制
DEL user:1001

这个命令会删除整个 user:1001 哈希表。

3.4、其他Hash命令

Redis 中 Hash 其他的一些常用命令还有:

  1. HEXISTS key field:查看哈希表 key 中,指定的字段是否存在。
  2. HLEN key:获取哈希表中字段的数量。
  3. HKEYS key:获取所有哈希表中的字段。
  4. HVALS key:获取哈希表中所有值。
  5. HGETALL key:获取在哈希表中指定 key 的所有字段和值。
  6. HINCRBY key field increment:为哈希表 key 中的指定字段的整数值加上增量 increment 。
  7. HINCRBYFLOAT key field increment:为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
  8. HMSET key field value [field value ...]:同时将多个 field-value (字段-值)对设置到哈希表的 key 中。
  9. HMGET key field [field ...]:获取所有给定字段的值。

0 人点赞