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

前言

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

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

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

其余文章

正文

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
复制代码

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

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 哈希结构 存储 用户信息 的示意图以下:

相比于使用 字符串序列化 缓存 用户信息哈希类型 变得更加 直观,而且在 更新操做 上会 更加便捷。能够将每一个用户的 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 去模拟关系型复杂查询 开发困难维护成本高

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 开发与运维》


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

零壹技术栈

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

相关文章
相关标签/搜索