哈希在很多编程语言中都有着很广泛的应用, 而在 Redis 中也是如此, 在 Redis 中, 哈希类型是指 Redis 键值对中的值本身又是一个键值对结构, 形如 value=[{field1,value1},...{fieldN,valueN}], 其与 Redis 字符串对象的区别如下图所示:
一, 内部编码
哈希类型的内部编码有两种: ziplist(压缩列表),hashtable(哈希表). 只有当存储的数据量比较小的情况下, Redis 才使用压缩列表来实现字典类型. 具体需要满足两个条件:
当哈希类型元素个数小于 hash-max-ziplist-entries 配置 (默认 512 个)
所有值都小于 hash-max-ziplist-value 配置 (默认 64 字节)
ziplist 使用更加紧凑的结构实现多个元素的连续存储, 所以在节省内存方面比 hashtable 更加优秀. 当哈希类型无法满足 ziplist 的条件时, Redis 会使用 hashtable 作为哈希的内部实现, 因为此时 ziplist 的读写效率会下降, 而 hashtable 的读写时间复杂度为 O(1).
有关 ziplist 和 hashtable 这两种 Redis 底层数据结构的具体实现可以参考我的另外两篇文章.
Redis 数据结构 -- 压缩列表
Redis 数据结构 -- 字典.
二, 常用命令
Redis 哈希对象常用命令如下表 (点击命令可查看命令详细说明).
命令 | 说明 | 时间复杂度 |
---|---|---|
HDEL key field [field ...] | 删除一个或多个 Hash 的 field | O(N) N 是被删除的字段数量。 |
HEXISTS key field | 判断 field 是否存在于 hash 中 | O(1) |
HGET key field | 获取 hash 中 field 的值 | O(1) |
HGETALL key | 从 hash 中读取全部的域和值 | O(N) N 是 Hash 的长度 |
HINCRBY key field increment | 将 hash 中指定域的值增加给定的数字 | O(1) |
HINCRBYFLOAT key field increment | 将 hash 中指定域的值增加给定的浮点数 | O(1) |
HKEYS key | 获取 hash 的所有字段 | O(N) N 是 Hash 的长度 |
HLEN key | 获取 hash 里所有字段的数量 | O(1) |
HMGET key field [field ...] | 获取 hash 里面指定字段的值 | O(N) N 是请求的字段数 |
HMSET key field value [field value ...] | 设置 hash 字段值 | O(N) N 是设置的字段数 |
HSET key field value | 设置 hash 里面一个字段的值 | O(1) |
HSETNX key field value | 设置 hash 的一个字段,只有当这个字段不存在时有效 | O(1) |
HSTRLEN key field | 获取 hash 里面指定 field 的长度 | O(1) |
HVALS key | 获得 hash 的所有值 | O(N) N 是 Hash 的长度 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代 hash 里面的元素 |
三, 适用场景
3.1 存储对象
Redis 哈希对象常常用来缓存一些对象信息, 如用户信息, 商品信息, 配置信息等.
我们以用户信息为例, 它在关系型数据库中的结构是这样的
uid | name | age |
---|---|---|
1 | Tom | 15 |
2 | Jerry | 13 |
而使用 Redis Hash 存储其结构如下图:
相比较于使用 Redis 字符串存储, 其有以下几个优缺点:
原生字符串每个属性一个键.
- set user:1:name Tom
- set user:1:age 15
优点: 简单直观, 每个属性都支持更新操作.
缺点: 占用过多的键, 内存占用量较大, 同时用户信息内聚性比较差, 所以此种方案一般不会在生产环境使用.
序列化字符串后, 将用户信息序列化后用一个键保存
set user:1 serialize(userInfo)
优点: 简化编程, 如果合理的使用序列化可以提高内存的使用效率.
缺点: 序列化和反序列化有一定的开销, 同时每次更新属性都需要把全部数据取出进行反序列化, 更新后再序列化到 Redis 中.
序列化字符串后, 将用户信息序列化后用一个键保存
hmset user:1 name Tom age 15
优点: 简单直观, 如果使用合理可以减少内存空间的使用.
缺点: 要控制哈希在 ziplist 和 hashtable 两种内部编码的转换, hashtable 会消耗更多内存.
此外, 我们曾经在做配置中心系统的时候, 使用 Hash 来缓存每个应用的配置信息, 其在数据库中的数据结构大致如下表
AppId | SettingKey | SettingValue |
---|---|---|
10001 | AppName | myblog |
10001 | Version | 1.0 |
10002 | AppName | admin site |
在使用 Redis Hash 进行存储的时候
新增或更新一个配置项
- 127.0.0.1:6379> HSET 10001 AppName myblog
- (integer) 1
获取一个配置项
- 127.0.0.1:6379> HGET 10001 AppName
- "myblog"
删除一个配置项
- 127.0.0.1:6379> HDEL 10001 AppName
- (integer) 1
3.2 购物车
很多电商网站都会使用 cookie 实现购物车, 也就是将整个购物车都存储到 cookie 里面. 这种做法的一大优点: 无须对数据库进行写入就可以实现购物车功能, 这种方式大大提高了购物车的性能, 而缺点则是程序需要重新解析和验证 ( validate) cookie, 确保 cookie 的格式正确, 并且包含的商品都是真正可购买的商品. cookie 购物车还有一个缺点: 因为浏览器每次发送请求都会连 cookie 一起发送, 所以如果购物车 cookie 的体积比较大, 那么请求发送和处理的速度可能会有所降低.
购物车的定义非常简单: 我们以每个用户的用户 ID(或者 CookieId) 作为 Redis 的 Key, 每个用户的购物车都是一个哈希表, 这个哈希表存储了商品 ID 与商品订购数量之间的映射. 在商品的订购数量出现变化时, 我们操作 Redis 哈希对购物车进行更新:
如果用户订购某件商品的数量大于 0, 那么程序会将这件商品的 ID 以及用户订购该商品的数量添加到散列里面.
- // 用户 1 商品 1 数量 1
- 127.0.0.1:6379> HSET uid:1 pid:1 1
- (integer) 1 // 返回值 0 代表改 field 在哈希表中不存在, 为新增的 field
如果用户购买的商品已经存在于散列里面, 那么新的订购数量会覆盖已有的订购数量;
- // 用户 1 商品 1 数量 5
- 127.0.0.1:6379> HSET uid:1 pid:1 5
- (integer) 0 // 返回值 0 代表改 field 在哈希表中已经存在
相反地, 如果用户订购某件商品的数量不大于 0, 那么程序将从散列里面移除该条目.
- // 用户 1 商品 1
- 127.0.0.1:6379> HDEL uid:1 pid:2
- (integer) 1
3.3 计数器
Redis 哈希表作为计数器的使用也非常广泛. 它常常被用在记录网站每一天, 一月, 一年的访问数量. 每一次访问, 我们在对应的 field 上自增 1
- // 记录我的
- 127.0.0.1:6379> HINCRBY MyBlog 202001 1
- (integer) 1
- 127.0.0.1:6379> HINCRBY MyBlog 202001 1
- (integer) 2
- 127.0.0.1:6379> HINCRBY MyBlog 202002 1
- (integer) 1
- 127.0.0.1:6379> HINCRBY MyBlog 202002 1
- (integer) 2
也经常被用在记录商品的好评数量, 差评数量上
- 127.0.0.1:6379> HINCRBY pid:1 Good 1
- (integer) 1
- 127.0.0.1:6379> HINCRBY pid:1 Good 1
- (integer) 2
- 127.0.0.1:6379> HINCRBY pid:1 bad 1
- (integer) 1
也可以实时记录当天的在线的人数.
- // 有人登陆
- 127.0.0.1:6379> HINCRBY MySite 20200310 1
- (integer) 1
- // 有人登陆
- 127.0.0.1:6379> HINCRBY MySite 20200310 1
- (integer) 2
- // 有人登出
- 127.0.0.1:6379> HINCRBY MySite 20200310 -1
- (integer) 1
小结
本篇文章我们总结了 Redis 哈希对象的内部实现, 常用命令以及常用的一些场景, 那么大家在项目中对 Redis 哈希对象的使用都有哪些场景呢, 欢迎在评论区给我留言和分享, 我会第一时间反馈! 我们共同学习与进步!
参考
《Redis 设计与实现》
《Redis 开发与运维》
《Redis 官方文档》
-----END-----
来源: https://www.cnblogs.com/hunternet/p/12651530.html