Redis Hash 数据结构:从命令到应用的全面攻略

Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家带来的是 Redis 中 hash 数据结构的相关命令、底层编码以及常见的应用场景 .

在这里插入图片描述

本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践

Redis 本身已经是键值对的结构了 , 那 value 这一层 , 又可以是哈希结构 (套娃)
我们可以看这张图理解一下
image.png

那在哈希类型中 , 映射关系通常称为 field-value

因为 Redis 整体的键值对映射关系叫做 key-value , 为了进行区分 , 哈希类型的映射关系就叫做 field-value

一 . 常见命令

1.1 hset 和 hget

hset 的作用就是设置 hash 中指定的字段 (field) 的值 (value)
语法 : hset key field1 value1 [field2 value2 …]

value 只能为字符串

返回值代表设置成功的键值对的个数 (也就是 field 的个数)

hget 的作用就是获取 hash 中指定字段的值
语法 : hget key field
那我们举一个例子来看看 hset 和 hget 的用法

127.0.0.1:6379> hset key k1 v1 # 设置哈希类型
(integer) 1
127.0.0.1:6379> hset key k2 v2 k3 v3 k4 v4 # 可以设置多组哈希
(integer) 3 # 返回值代表设置成功的键值对的个数 (field 的个数)
127.0.0.1:6379> hget key k1 # 获取哈希类型
"v1"
127.0.0.1:6379> hget key k2
"v2"
127.0.0.1:6379> hget key k100 # 如果获取的 field 不存在就会返回 nil
(nil)
127.0.0.1:6379> hget key2 k1 # 如果获取到的 key 不存在也会返回 nil
(nil)

1.2 hexists

hexists 的作用就是判断 hash 中是否有指定的字段 (也就是判断 field 是否存在)
语法 : hexists key field
返回值 : 1 代表存在 , 0 代表不存在
时间复杂度 : O(1)

127.0.0.1:6379> hexists key k1 # 判断 key 中的 k1 是否存在
(integer) 1 # 1 代表存在
127.0.0.1:6379> hexists key k100 # 如果 field 不存在是查询不到数据的
(integer) 0 # 0 代表不存在
127.0.0.1:6379> hexists key2 k1 # 如果 key 不存在也是查询不到数据的
(integer) 0

1.3 hdel

hdel 的作用就是删除 hash 中指定的字段

del 删除的是 key , hdel 删除的是 field

语法 : hdel key field1 [field2 …]
时间复杂度 : O(1)
返回值代表本次删除成功的字段个数

127.0.0.1:6379> hdel key k1 # 删除 key 中的 k1 
(integer) 1 # 返回值代表删除成功的个数
127.0.0.1:6379> hexists key k1 # 此时 key 中的 k1 就不存在了
(integer) 0
127.0.0.1:6379> hdel key k2 k3 # 可以删除多个 field
(integer) 2
127.0.0.1:6379> hdel key2 k1 # 如果删除不存在的 key 就会删除失败
(integer) 0
127.0.0.1:6379> hdel key k100 # 如果删除不存在的 field 也会删除失败
(integer) 0

1.4 hkeys

hkeys 可以获取到哈希中所有的字段 (field)
语法 : hkeys key

这个操作底层的实现是会先根据 key 找到对应的 field , 然后遍历 field 集合

时间复杂度 : O(N) , N 指的是哈希的元素个数 (field 的个数)

127.0.0.1:6379> hset key f1 111 f2 222 f3 333 f4 444 # 设置多个哈希
(integer) 4
127.0.0.1:6379> hkeys key # 获取哈希中所有的字段
1) "f1"
2) "f2"
3) "f3"
4) "f4"

那这个操作还是有风险的 , 类似于之前的 keys *

主要是咱们也不知道某个 hash 中是否会存在大量的 field , 如果查询数据过多就会发送阻塞 , 就会导致大量请求转移到数据库 , 最终数据库也支撑不住 , 那系统就会出现故障

1.5 hvals

hvals 与 hkeys 相对 , 他能获取到 hash 中所有的 value
语法 : hvals key
时间复杂度 : O(N) , N 指的是哈希的元素个数 (field 的个数)

127.0.0.1:6379> hset key f1 111 f2 222 f3 333 f4 444 # 设置多个哈希
(integer) 4
127.0.0.1:6379> hvals key # 获取哈希中所有的 value
1) "111"
2) "222"
3) "333"
4) "444"

如果哈希存储的元素也非常多 , 同样会导致 Redis 服务器被阻塞住

1.6 hgetall、hmget

hgetall 就相当于把 hkeys 和 hvals 结合起来了
语法 : hgetall key

127.0.0.1:6379> hset key f1 111 f2 222 f3 333 f4 444 # 设置多个哈希
(integer) 4
127.0.0.1:6379> hgetall key # 获取所有的 field 和 value
1) "f1"
2) "111"
3) "f2"
4) "222"
5) "f3"
6) "333"
7) "f4"
8) "444"

如果哈希存储的元素也非常多 , 同样会导致 Redis 服务器被阻塞住
那我们一般多数情况下并不需要查询所有的 field , 只需要查询当中的几个 field , 我们就可以使用 hmget , 一次可以查询多个 field
语法 : hmget key field1 [field2 …]

127.0.0.1:6379> hmget key f1 f2 f3 # 一次查询多个 field
# 多个 value 的顺序和 field 的顺序是匹配的
1) "111"
2) "222"
3) "333"

相对应的 hmset 也存在 , 但是 hset 就已经能够一次设置多个键值对了 , 所以我们一般不会去使用 hmset


那我们之前说 hkeys、hvals、hgetall 都是存在风险的 , hash 的元素个数太多 , 那执行的耗时就会比较长 , 从而阻塞 Redis .
那我们就可以使用 hscan 遍历 Redis 的 hash , 它属于 “渐进式遍历” , 也就是一次遍历一点点 , 多使用几次就可以完成整个遍历过程了 , 我们有机会再介绍

1.7 hlen

hlen 是用来获取 hash 中所有字段的个数
语法 : hlen key
时间复杂度 : O(1)

内部并不是通过遍历的方式去计算个数 , 可以通过变量存储元素个数

127.0.0.1:6379> hset key f1 111 f2 222 f3 333 f4 444
(integer) 4
127.0.0.1:6379> hlen key # 获取 hash 中所有字段的个数
(integer) 4

1.8 hsetnx

与之前讲过的 setnx 类似 , 不存在的时候才能设置成功 . 如果存在 , 则设置失败
语法 : hsetnx key field value

127.0.0.1:6379> hset key f1 111 f2 222 f3 333 f4 444
(integer) 4
# 目前已经有 f1 f2 f3 f4 了
127.0.0.1:6379> hsetnx key f5 555 # 不存在才能设置成功
(integer) 1
127.0.0.1:6379> hsetnx key f5 666 # 存在就会设置失败
(integer) 0
127.0.0.1:6379> hget key f5 # f5 的值还是 555, 代表设置失败
"555"

1.9 hincrby、hincrbyfloat

hash 中的 value 同样可以当做数字来处理
所以 hincrby 就可以加减整数 , hincrbyfloat 就可以加减小数
时间复杂度 : O(1)

127.0.0.1:6379> hincrby key f1 10 # 对 key 中的 f1 进行 +10 操作
(integer) 121
127.0.0.1:6379> hget key f1 # 查看 key 中的 f1 的值
"121"
127.0.0.1:6379> hincrby key f1 -10 # 对 key 中的 f1 进行 -10 操作
(integer) 111
127.0.0.1:6379> hincrbyfloat key f1 3.14 # 对 key 中的 f1 进行 +3.14 操作
"114.14"
127.0.0.1:6379> hincrbyfloat key f1 -3.14 # 对 key 中的 f1 进行 -3.14 操作
"111"

小结

在这里插入图片描述

二 . hash 的编码方式

哈希内部的编码方式有两种 :

  1. ziplist (压缩列表) : 他的目的是节省内存空间 , 但是 ziplist 付出的代价就是进行读写元素速度是比较慢的 , 如果元素个数少 , 那慢的就不太明显 ; 如果元素个数比较多 , 那 ziplist 速度就会比较慢
  2. hashtable (哈希表)

所以我们采取的策略是 :

  1. 如果哈希中的元素个数比较少 , 使用 ziplist 存储 ; 如果哈希中的元素个数比较多 , 使用 hashtable 来表示
  2. 如果每个 value 的值长度都比较短 , 使用 ziplist 来去存储 ; 如果某个 value 的长度太长 , 还是会转化成 hashtable

也就是说 , 只有哈希中的元素个数比较少 && 每个 value 的值长度都比较短的情况下 , 才会使用 ziplist 去存储
那我们也可以在配置文件中配置 hash-max-ziplist-entries (默认 512 个) 来去设置哈希中的元素个数的标准 , 配置 hash-max-ziplist-value (默认 64 字节) 来去设置每个 value 的值的长度的标准

那我们同样可以通过 object encoding 来去查看具体的编码方式

127.0.0.1:6379> hset key f1 111 # 设置一个短的 value
(integer) 1
127.0.0.1:6379> object encoding key # 获取哈希的具体编码方式
"ziplist" # 长度比较短的时候使用 ziplist
# 设置一个长的 value
127.0.0.1:6379> hset key2 k2 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
(integer) 1
127.0.0.1:6379> object encoding key2
"hashtable" # 长度比较长的时候使用 hashtable

三 . 使用场景 : 作为缓存

我们之前讲过 , string 也是可以作为缓存使用的
那如果要存储结构化的数据 , 使用 hash 会更适合一些
比如我们想要存储这个数据 :
image.png
那我们通过哈希类型存储到 Redis 的缓存中就是这个样子 :
image.png
那通过哈希 , 就很容易的将关系型的数据表示出来保存到缓存中

那上述场景我们通过 string 类型也能做到 , 就需要使用到 JSON 这样的结构来去存储 .
但是如果使用 string (JSON) 的格式来去表示相关信息 , 那万一我们只想获取 / 修改其中的某个 field , 就需要把整个 JSON 读取出来 , 解析成对象然后进行修改 , 修改完还需要再转化回 JSON 格式的字符串写入到 Redis 中 , 非常麻烦 .
如果使用 hash 的方式来表示相关信息 , 就可以使用 field 表示对象的相关属性 (也就是数据表的每个列) , 此时就可以非常方便的修改 / 获取任何一个属性的值了

但是也有可能会存在一个问题 , 我们需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换 , 这样就可能会造成内存的消耗 (比如转化成哈希表类型 , 就会有大量元素空缺导致内存浪费)


那除了 string 类型、hash 类型能够存储字符串以外 , 我们还可以使用原生字符串类型来去表示一个复杂的 key

我们刚才讲的 string 的处理方式是通过 value 来去表示信息 , 使用原生字符串类型来去表示一个复杂的 key 这种是通过 key 来去表示信息

比如 : set user:1:name James , set user:1:age 23 …
但是我们并不推荐大家这样做 , 他把同一个数据的各个属性给分散开了

不满足高内聚的特点

那我们比对一下 MySQL 和 Redis 的哈希类型存储数据的区别
image.png
那我们 MySQL 对于数据的格式要求特别严格 , 如果某列不存在我们就需要置为 null
而 Redis 则不是 , 某列不存在那我们就不去存储他 , 比如 user:1 的 James 就没存储 age 的信息
另外 , 关系型数据库是可以做复杂的关系查询的 , 比如 : 联表查询、聚合查询等等 , 而 Redis 想要进行这样的功能就需要我们自己的代码手动实现 .

那我们的 key 也存储了用户的 ID , 而 field 中也存储了 ID , 那我们 field-value 中的 uid 能不能不写 ?
image.png
其实可以不写 , 但是在工程中一般都会把 uid 在 key 和 value 中再存一份 , 后续编写代码操作数据会比较方便


本篇文章就讲解到这里了 , 如果对你有帮助的话 , 还请一键三连~
在这里插入图片描述

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加勒比海涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值