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

栏目: 数据库 · 发布时间: 6年前

内容简介:大部分编程语言都提供了哈希形如哈希类型中的

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

深入剖析 <a href='https://www.codercto.com/topics/18994.html'>Redis</a> 系列(六) - Redis数据结构之哈希

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

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

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

正文

1. 相关命令

1.1. 基本命令

1.1.1. 设置值

hset key field value

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

127.0.0.1:6379> hset user:1 name tom
(integer) 1
复制代码

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

1.1.2. 获取值

hget key field

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

127.0.0.1:6379> hget user:1 name
"tom"
复制代码

如果 field 不存在,会返回 nil

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 的个数,例如:

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

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 。例如:

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

127.0.0.1:6379> hexists user:1 name
(integer) 1
复制代码

1.1.7. 获取所有field

hkeys key

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

127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"
复制代码

1.1.8. 获取所有value

hvals key

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

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

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

127.0.0.1:6379> hstrlen user:1 name
(integer) 3
复制代码

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

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

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

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
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
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系列(六) - Redis数据结构之哈希

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

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

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

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系列(六) - Redis数据结构之哈希
  • 关系型数据库 可以做复杂的 关系查询 ,而使用 Redis 去模拟关系型复杂查询 开发困难维护成本高

3.2. 几种缓存方式

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

3.2.1. 原生字符串类型

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

set user:1:name tom
set user:1:age 23
set user:1:city beijing
复制代码
  • 优点:简单直观,每个属性都支持 更新操作

  • 缺点:占用 过多的键内存占用量 较大,同时用户信息 内聚性比较差 ,所以此种方案一般不会在生产环境使用。

3.2.2. 序列化字符串类型

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

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

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

3.2.3. 哈希类型

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

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

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

小结

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


以上所述就是小编给大家介绍的《深入剖析Redis系列(六) - Redis数据结构之哈希》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms Illuminated (Part 2)

Algorithms Illuminated (Part 2)

Tim Roughgarden / Soundlikeyourself Publishing, LLC / 2018-8-5 / USD 17.99

Algorithms are the heart and soul of computer science. Their applications range from network routing and computational genomics to public-key cryptography and machine learning. Studying algorithms can......一起来看看 《Algorithms Illuminated (Part 2)》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具