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 会消耗 更多内存

 


作者:零壹技术栈
链接:https://juejin.cn/post/6844903693075103757
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值