深入剖析Redis系列(六) - Redis数据结构之哈希

前言

大部分编程语言都提供了 哈希hash)类型,它们的叫法可能是 哈希字典关联数组。在 Redis 中,哈希类型 是指键值本身又是一个 键值对结构

哈希 形如 value={ {field1,value1},...{fieldN,valueN} }Redis 键值对哈希类型 二者的关系如图所示:

哈希类型中的 映射关系 叫作 field-value,这里的 value 是指 field 对应的 ,不是 对应的值。

正文

1. 相关命令

1.1. 基本命令

1.1.1. 设置值

hset key field value

下面为 user:1 添加一对 field-value,如果设置成功会返回 1,反之会返回 0

1
2
127.0.0.1:6379> hset user:1 name tom
(integer) 1

此外 Redis 提供了 hsetnx 命令,它们的关系就像 setsetnx 命令一样,只不过 作用域 变为 field

1.1.2. 获取值

hget key field

下面操作用于获取 user:1name 域(属性) 对应的值。

1
2
127.0.0.1:6379> hget user:1 name
"tom"

如果 field 不存在,会返回 nil

1
2
3
4
127.0.0.1:6379> hget user:2 name
(nil)
127.0.0.1:6379> hget user:1 age
(nil)

1.1.3. 删除field

hdel key field [field …]

hdel 会删除 一个或多个 field,返回结果为 成功删除 field 的个数,例如:

1
2
3
4
127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hdel user:1 age
(integer) 0

1.1.4. 计算field个数

hlen key

例如键 user:13field

1
2
3
4
5
6
7
8
127.0.0.1:6379> hset user:1 name tom
(integer) 1
127.0.0.1:6379> hset user:1 age 23
(integer) 1
127.0.0.1:6379> hset user:1 city chengdu
(integer) 1
127.0.0.1:6379> hlen user:1
(integer) 3

1.1.5. 批量设置或获取field-value

hmget key field [field …]
hmset key field value [field value …]

hmsethmget 分别是 批量设置获取 field-valuehmset 需要的参数是 key多对 field-valuehmget 需要的参数是 key多个 field。例如:

1
2
3
4
5
127.0.0.1:6379> hmset user:1 name tom age 12 city chengdu
OK
127.0.0.1:6379> hmget user:1 name city
1) "tom"
2) "chengdu"

1.1.6. 判断field是否存在

hexists key field

例如 user:1 包含 name 域,所以返回结果为 1,不包含时返回 0

1
2
127.0.0.1:6379> hexists user:1 name
(integer) 1

1.1.7. 获取所有field

hkeys key

hkeys 命令应该叫 hfields 更为恰当,它返回指定 哈希键 所有的 field,例如:

1
2
3
4
127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"

1.1.8. 获取所有value

hvals key

下面操作获取 user:1 的全部 value

1
2
3
4
127.0.0.1:6379> hvals user:1
1) "tom"
2) "12"
3) "chengdu"

1.1.9. 获取所有的field-value

hgetall key

下面操作获取 user:1 所有的 field-value

1
2
3
4
5
6
7
127.0.0.1:6379> hgetall user:1
1) "name"
2) "tom"
3) "age"
4) "12"
5) "city"
6) "chengdu"

在使用 hgetall 时,如果 哈希元素 个数比较多,会存在 阻塞 Redis 的可能。如果开发人员只需要获取 部分 field,可以使用 hmget,如果一定要获取 全部 field-value,可以使用 hscan 命令,该命令会 渐进式遍历 哈希类型。

1.2. 不常用命令

1.2.1. 键值自增

hincrby key field
hincrbyfloat key field

hincrbyhincrbyfloat,就像 incrbyincrbyfloat 命令一样,但是它们的 作用域field

1.2.2. 计算value的字符串长度

hstrlen key field

例如 hget user:1 namevaluetom,那么 hstrlen 的返回结果是 3

1
2
127.0.0.1:6379> hstrlen user:1 name
(integer) 3

下面是 哈希类型命令时间复杂度,开发人员可以参考此表选择适合的命令。

2. 内部编码

哈希类型内部编码 有两种:

2.1. ziplist(压缩列表)

哈希类型 元素个数 小于 hash-max-ziplist-entries 配置(默认 512 个)、同时 所有值小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为 哈希内部实现ziplist 使用更加 紧凑的结构 实现多个元素的 连续存储,所以在 节省内存 方面比 hashtable 更加优秀。

2.2. hashtable(哈希表)

哈希类型 无法满足 ziplist 的条件时,Redis 会使用 hashtable 作为 哈希内部实现,因为此时 ziplist读写效率 会下降,而 hashtable 的读写 时间复杂度O(1)

下面的示例演示了 哈希类型内部编码,以及相应的变化。

field 个数 比较少,且没有大的 value 时,内部编码ziplist

1
2
3
4
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
  • 当有 value 大于 64 字节时,内部编码 会由 ziplist 变为 hashtable
1
2
3
4
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte...忽略..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
  • field 个数 超过 512内部编码 也会由 ziplist 变为 hashtable
1
2
3
4
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"

3. 适用场景

如图所示,为 关系型数据表 的两条 用户信息,用户的属性作为表的列,每条用户信息作为行。

使用 Redis 哈希结构 存储 用户信息 的示意图如下:

相比于使用 字符串序列化 缓存 用户信息哈希类型 变得更加 直观,并且在 更新操作 上会 更加便捷。可以将每个用户的 id 定义为 键后缀,多对 field-value 对应每个用户的 属性,类似如下伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public UserInfo getUserInfo(long id) {
// 用户id作为key后缀
String userRedisKey = "user:info:" + id;
// 使用hgetall获取所有用户信息映射关系
Object userInfoMap = redis.hgetAll(userRedisKey);
UserInfo userInfo;
if (userInfoMap != null) {
// 将映射关系转换为UserInfo
userInfo = transferMapToUserInfo(userInfoMap);
} else {
// 从MySQL中获取用户信息
userInfo = mysql.get(id);
// 将userInfo变为映射关系使用hmset保存到Redis中
redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));
// 添加过期时间
redis.expire(userRedisKey, 3600);
}
return userInfo;
}

3.1. 哈希结构与关系型表

需要注意的是 哈希类型关系型数据库 有两点不同之处:

  • 哈希类型稀疏的,而 关系型数据库完全结构化的,例如 哈希类型 每个 可以有不同的 field,而 关系型数据库 一旦添加新的 所有行 都要为其 设置值(即使为 NULL),如图所示:

  • 关系型数据库 可以做复杂的 关系查询,而使用 Redis 去模拟关系型复杂查询 开发困难维护成本高

3.2. 几种缓存方式

到目前为止,我们已经能够用 三种方法 缓存 用户信息,下面给出三种方案的 实现方法优缺点分析

3.2.1. 原生字符串类型

给用户信息的每一个属性分配 一个键

1
2
3
set user:1:name tom
set user:1:age 23
set user:1:city beijing
  • 优点:简单直观,每个属性都支持 更新操作
  • 缺点:占用 过多的键内存占用量 较大,同时用户信息 内聚性比较差,所以此种方案一般不会在生产环境使用。

3.2.2. 序列化字符串类型

将用户信息 序列化 后用 一个键 保存。

1
set user:1 serialize(userInfo)
  • 优点简化编程,如果合理的使用 序列化 可以 提高内存利用率

  • 缺点序列化反序列化 有一定的开销,同时每次 更新属性 都需要把 全部数据 取出进行 反序列化更新后序列化Redis 中。

3.2.3. 哈希类型

每个用户属性使用 一对 field-value,但是只用 一个键 保存。

1
hmset user:1 name tom age 23 city beijing
  • 优点简单直观,如果使用合理可以 减少内存空间 的使用。

  • 缺点:要控制和减少 哈希ziplisthashtable 两种 内部编码转换hashtable 会消耗 更多内存

小结

本文介绍了 Redis 中的 哈希结构 的 一些 基本命令内部编码适用场景。最后对比了 关系型表哈希结构 的区别,以及几种 存储方式 的优缺点。

参考

《Redis 开发与运维》


欢迎关注技术公众号: 零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

文章作者: Chen Vainlgory
文章链接: https://geek.vainlgory.top/2018/10/02/深入剖析Redis系列(六) - Redis数据结构之哈希/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 零壹技術棧 | VainlgoryのBlog
微信打赏
支付宝打赏