问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

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

创作时间:
作者:
@小白创作中心

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

引用
CSDN
1.
https://m.blog.csdn.net/m0_53117341/article/details/141794585

Redis中的Hash数据结构是一种非常实用的数据类型,它允许用户在单个键下存储多个字段和值的映射关系。本文将详细介绍Redis Hash的各种命令、底层编码方式以及实际应用场景,帮助读者全面掌握这一重要数据结构。

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

那在哈希类型中,映射关系通常称为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会更适合一些。

比如我们想要存储这个数据:

那我们通过哈希类型存储到Redis的缓存中就是这个样子:

那通过哈希,就很容易的将关系型的数据表示出来保存到缓存中。

那上述场景我们通过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的哈希类型存储数据的区别。那我们MySQL对于数据的格式要求特别严格,如果某列不存在我们就需要置为null。而Redis则不是,某列不存在那我们就不去存储他,比如user:1的James就没存储age的信息。

另外,关系型数据库是可以做复杂的关系查询的,比如:联表查询、聚合查询等等,而Redis想要进行这样的功能就需要我们自己的代码手动实现。

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

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号