2023.12.25 关于 Redis 数据类型 Hash 常用命令、内部编码、应用场景

目录

Hash 数据类型

Hash 操作命令

HSET

HGET

HEXISTS

HDEL

HKEYS

HVALS

HGETALL

HMGET

HLEN

HSETNX

HINCRBY

HINCRBYFLOAT

HSTRLEN

Hash 编码方式

理解什么是压缩

Hash 实际应用

Cache 缓存


Hash 数据类型

  • 整体上来说 Redis 是键值对结构,其中 key 和 value 之间通过哈希方式组织的
  • 该结构中 key 负责组织数据的结构,而 value 却可以为不同类型的数据,其中一种类型就是 哈希
  • 这就相当于在 value 中又存储了一层键值对(field - value),相当于所谓的套娃

实例理解

  • 存储一个 uid 为 1 的用户对象,姓名为 mastermao 年龄为 20

注意:

  • 当我们谈到 key ,指的是整体 Redis 键值对(key - value)结构中的 key 
  • 当我们谈到 field,指的是 Redis 中的键值对对应的值(value)中哈希表中的一个特定的键
  • 该特定键用来进一步区分 和 访问哈希表中的各个数据项

Hash 操作命令

HSET

  • 用来设置 hash 中指定的字段 field 和 value
  • 此处的(field - value)中的 value 只能为字符串类型

语法:

hset key field value [field value ...]

时间复杂度:

  • O(1)

返回值:

  • 返回值是设置成功的 键值对(field - value)的个数

 实例理解


HGET

  • 用来获取 hash 中指定字段 field 的 value 值

语法:

hget key field

时间复杂度:

  • O(1)

 实例理解

  • 紧接着上述 hset 的实例进行测试


HEXISTS

  • 用来检查 hash 中是否存在指定字段 field

语法:

hexists key field

时间复杂度:

  • O(1)

返回值:

  • 返回 1 表示存在
  • 返回 0 表示不存在

实例理解

  • 紧接着上述 hset 的实例进行测试


HDEL

  • 用来删除 hash 中指定的字段 field

语法:

hdel key field [field ...]

时间复杂度:

  • O(N)
  • 其中 N 代表删除的字段个数,一般不会很大,也可认为是 O(1)

返回值:

  • 本次操作删除的字段个数

实例理解

  • 紧接着上述 hset 的实例进行测试

注意:

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

HKEYS

  • 获取 hash 中的所有字段
  • 该操作会现根据 key 找到对应的 hash,然后再遍历 hash

语法:

hkeys key

时间复杂度:

  • O(N)
  • 其中 N 为 field 的个数

实例理解

  • hkeys key 命令存在一定的风险,类似于 keys * 命令
  • 主要因为我们并不知道某个 hash 中是否存在大量的 field,从而导致 Redis 被阻塞 

HVALS

  • 和上面的 hkeys 相对应,用于获取 hash 中的所有 value 

 语法:

hvals key

时间复杂度:

  • O(N)
  • 其中 N 为 field 的个数

 实例理解

注意:

  • 使用 h 系列的命令操作 key 时,必须保证 key 对应的 value 得为 哈希 类型

HGETALL

  • 用于获取 hash 中的所有字段 field 以及对应的值 value
  • 相当于 hkeys + hvals 命令的结合

语法:

hgetall key

时间复杂度:

  • O(N)
  • 其中 N 为 field 的个数

 实例理解

  • 观察此处的返回值
  • 一个 field 一个 value 交替返回

注意:

  • 上述操作风险比较大
  • 因为多数情况下 不需要查询一个 key 中所有的 field 和 value 可能只查其中的几对

HMGET

  • 用于获取 hash 中多个字段的值 value
  • 类似于 mget 可以一次查询多个 key

 语法:

hmget key field [field ...]

时间复杂度:

  • O(N)
  • 其中 N 代表查询的字段个数,一般不会很大,也可认为是 O(1)

实例理解

注意:

  • 此处 hmget 命令返回的值 value,与查询时 输入字段 field 的顺序相匹配

问题:

  • 有没有 hmset 一次可以设置多对(field - value)呢?

回答:

  • 有 hmset 命令
  • 但是并不需要使用其命令,因为 hset 已经支持一次设置多对(field - value)了

小总结:

  • 上述的 hkeys、hvals、hgetall 命令均存在一定风险
  • 如果 hash 的元素个数太多,则执行的耗时会比较长,从而阻塞掉 Redis 

补充:

  • hscan 命令可以渐进式地遍历 Redis 的 hash
  • 即敲一次命令,遍历一小部分,再敲一次命令,再遍历一小部分
  • 此时的时间是可控的,连续执行多次便可完成整个的 hash 的遍历,也就是所谓的 "化整为零"

HLEN

  • 用于获取 hash 中的所有键值对个数

语法:

​​​​​​​

hlen key

时间复杂度:

  • O(1)
  • 获取 hash 的元素个数不需要遍历

 实例理解


HSETNX

  • 字段 field 不存在的情况下,设置 hash 中的字段 field 和 值 value
  • 类似于 setnx ​​​​​​​

语法:

hsetnx key field value

时间复杂度:

  • O(1)

返回值:

  • 返回 1 表示设置成功
  • 返回 0 表示设置失败

实例理解


HINCRBY

  • hash 这里的 value 也可以被当作数字来进行处理
  • hincrby 命令用来对 value 进行加减整数
  • 因为使用频率不是特别高,Redis 没有提供 hincr、hdecr 之类的命令

 语法:

hincrby key field increment

时间复杂度:

  • O(1)

返回值:

  • value ± 之后的结果

实例理解


HINCRBYFLOAT

  • hincybyfloat 命令用来对 value 进行加减小数

 语法:

hincrbyfloat key field increment

时间复杂度:

  • O(1)

返回值:

  • value ± 之后的结果

实例理解


HSTRLEN

  • 用于计算 hash 中的值 value 的字符串长度

 语法:

hstrlen key field 

时间复杂度:

  • O(1)

返回值:

  • hash 中的值 value 的字符串长度,单位为字节

实例理解

Hash 编码方式

  • 哈希的内部编码有 两种
  1. ziplist 压缩列表
  2. hashtable 哈希表

实例理解

  • 我们通过 object encoding key 来查看编码方式


理解什么是压缩

  • 压缩的本质,就是对数据进行重写编码
  • 不同的数据有不同的特点
  • 结合这些特点,进行精妙的设计,使其能够在重新编码之后,缩小体积

实例理解

  • ' abbcccddddeeeee ' ———重新编码———> ' 1a2b3c4d5e '
  • ' abcd0000000000000000000000000000000efgh ' ———重新编码———> 'abcd0[30]efgh​​​​​​​ '

注意:

  • 上述实例是比较粗糙的编码方式
  • 实际上 一些常见的压缩算法都有着十分精妙的设计
  • ziplist 也是同理,其内部的数据结构也进行了 精心设计,其目的就是为了节省内存空间


ziplist 和 hashtable

  • 如果上来就直接使用 hashtable,那该哈希表中可能有些位置上有元素,有些位置上没有元素,从而会造成一定程度上的空间浪费
  • 所以当一开始元素个数比较少时,还是先使用 ziplist 进行编码,以达到节省空间的目的
  • 而 ziplist 作为一种内部编码方式,虽然一定程度上 节省了内存空间,但读写元素时的速度较慢
  • 这种慢 在元素较少时可能不太明显,但是当元素数量过多时,性能下降会更为明显
  • 相比之下 hashtable 的内部结构更为灵活,它可以更高效地处理大量的元素
  • 在 hashtable 中,每个元素都通过哈希函数映射到不同的位置,这样在读写元素时可以更快地定位到目标位置

结论:

  1. 哈希中的元素个数比较少,使用 ziplist 表示,元素个数比较多,使用 hashtable 来表示
  2. 每个 value 的值长度都比较短,使用 ziplist 表示,如果某个 value 的长度太长了,也会转换成 hashtable

  • hash-max-ziplist-entries 配置 (默认 512 个)​​​​​​​
  • hash-max-ziplist-value 配置(默认 64 字节)

理解:

  • 这两个配置项是均可以写到 redis.conf 文件中
  • 且两个配置的阈值是可变的,所以不用刻意的去 记背数值
  • 因此我们需在不同的业务场景中,通过测试等相关的手段,来调整上述配置的阈值,以便找到一个更加合适的数值

Hash 实际应用

Cache 缓存

  • 首先 String 类型是可以用作缓存的
  • 但对于 存储结构化的数据,即类似于数据库 表 这样的结构使用 hash 类型更适合一些

实例理解

  • Redis 存储结构化的数据

方式一:

  • 使用 hash 类型

注意:

  • 此处的 key 中已经包含了 uid,那 hash 中 value 存储的 uid 是不是有点多此一举?
  • 省去 hash 中 value 存储的 uid 是不是又可进一步节省存储空间?
  • 首先如果确实不想在 hash 中 value 存储 uid 也可以省略掉
  • 但是在工程实践中,通常会选择在哈希表的值 value 中再存储一份 uid
  • 因为这样有助于简化代码逻辑,并使代码在后续开发中更为方便使用

方式二:

  • 使用 string 类型 +  JSON 格式
set user:1 {"name": "mastermao", "age": 18, "city": "Changsha"}

注意:

  • 使用 string 类型 +  JSON 格式来表示 userinfo 时
  • 只想获取或修改其中的某个字段,就需要将整个 JSON 字符串读取出来
  • 再解析成对象,进行字段操作,然后再将其转换回 JSON 字符串,并写回 Redis 
  • 这样一系列过程相对繁琐

  • 相较之下使用 hash 的方式来表示 userinfo 
  • 可以使用字段 field 来表示对象的每个属性,类似于数据库 表 中的每个列
  • 这样可以更方便地修改或获取任何一个属性的值
  • 虽然 hash 的方式在读写字段 field 上更为直观、高效,但付出的代价是更大的存储空间需求
  • 此外需要控制 哈希表 在 ziplist 和 hashtable 两种内部编码的转换,可能会导致较大的内存消耗

  • 相比之下,使用 string 类型 +  JSON 格式的的内存消耗相对较小

方式三:

  • 原生 String 类型,一个属性对应一个键
  • 该方式相当于将同一个对象的各个属性 给分散开表示(低内聚)
set user:1:name mastermao
set user:1:age 18
set user:1:city Changsha
  • ​​​​​​​​​​​​​​​​​​​​​不建议使用该方式,该方式具有 低内聚性

理解高内聚低耦合

  • 内聚就是将有关联的东西放到一起,最好能放到指定的地方,其优点为 好找
  • 耦合就是两个模块 或 代码之间的关联关系,关联关系越大,越容易相互影响,则耦合也越大
  • 我们追求低耦合,避免 "牵一发而动全身",这边出一个 bug 同时也影响到了其他的地方

hash 类型 和 关系型数据库 的不同

  1. 哈希类型是稀疏的,而关系型数据库是完全结构化的
  2. ​​​​​​​关系型数据库 可以做到复杂的查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等 基本不可能,维护成本高

实例理解

  • 此处 哈希类型 每个键均有不同的字段 field
  • 但关系型数据库 一旦添加新的列,其所有的行需为该列设置一个值,即便为 null​​​​​​​

  • 这就是为什么哈希类型是稀疏的,而关系型数据库是完全结构化的原因

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

茂大师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值