大部分编程语言都提供了 哈希(hash
)类型,它们的叫法多是 哈希、字典、关联数组。在 Redis
中,哈希类型 是指键值自己又是一个 键值对结构。java
哈希 形如 value={ {field1,value1},...{fieldN,valueN} }
,Redis
键值对 和 哈希类型 两者的关系如图所示:mysql
哈希类型中的 映射关系 叫做
field-value
,这里的value
是指field
对应的 值,不是 键 对应的值。redis
hset key field value
下面为 user:1
添加一对 field-value
,若是设置成功会返回 1
,反之会返回 0
。
127.0.0.1:6379> hset user:1 name tom
(integer) 1
复制代码
此外 Redis
提供了 hsetnx
命令,它们的关系就像 set
和 setnx
命令同样,只不过 做用域 由 键 变为 field
。
hget key field
下面操做用于获取 user:1
的 name
域(属性) 对应的值。
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)
复制代码
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
复制代码
hlen key
例如键 user:1
有 3
个 field
:
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
复制代码
hmget key field [field ...] hmset key field value [field value ...]
hmset
和 hmget
分别是 批量设置 和 获取 field-value
,hmset
须要的参数是 key
和 多对 field-value
,hmget
须要的参数是 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"
复制代码
hexists key field
例如 user:1
包含 name
域,因此返回结果为 1
,不包含时返回 0
:
127.0.0.1:6379> hexists user:1 name
(integer) 1
复制代码
hkeys key
hkeys
命令应该叫 hfields
更为恰当,它返回指定 哈希键 全部的 field
,例如:
127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"
复制代码
hvals key
下面操做获取 user:1
的所有 value
:
127.0.0.1:6379> hvals user:1
1) "tom"
2) "12"
3) "chengdu"
复制代码
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
命令,该命令会 渐进式遍历 哈希类型。
hincrby key field hincrbyfloat key field
hincrby
和 hincrbyfloat
,就像 incrby
和 incrbyfloat
命令同样,可是它们的 做用域 是 field
。
hstrlen key field
例如 hget user:1 name
的 value
是 tom
,那么 hstrlen
的返回结果是 3
。
127.0.0.1:6379> hstrlen user:1 name
(integer) 3
复制代码
下面是 哈希类型命令 的 时间复杂度,开发人员能够参考此表选择适合的命令。
哈希类型 的 内部编码 有两种:
当 哈希类型 元素个数 小于 hash-max-ziplist-entries
配置(默认 512
个)、同时 全部值 都 小于 hash-max-ziplist-value
配置(默认 64
字节)时,Redis
会使用 ziplist
做为 哈希 的 内部实现,ziplist
使用更加 紧凑的结构 实现多个元素的 连续存储,因此在 节省内存 方面比 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"
复制代码
如图所示,为 关系型数据表 的两条 用户信息,用户的属性做为表的列,每条用户信息做为行。
使用 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;
}
复制代码
须要注意的是 哈希类型 和 关系型数据库 有两点不一样之处:
field
,而 关系型数据库 一旦添加新的 列,全部行 都要为其 设置值(即便为 NULL
),如图所示:Redis
去模拟关系型复杂查询 开发困难,维护成本高。到目前为止,咱们已经可以用 三种方法 缓存 用户信息,下面给出三种方案的 实现方法 和 优缺点分析。
给用户信息的每个属性分配 一个键。
set user:1:name tom
set user:1:age 23
set user:1:city beijing
复制代码
优势:简单直观,每一个属性都支持 更新操做。
缺点:占用 过多的键,内存占用量 较大,同时用户信息 内聚性比较差,因此此种方案通常不会在生产环境使用。
将用户信息 序列化 后用 一个键 保存。
set user:1 serialize(userInfo)
复制代码
优势:简化编程,若是合理的使用 序列化 能够 提升内存利用率。
缺点:序列化 和 反序列化 有必定的开销,同时每次 更新属性 都须要把 所有数据 取出进行 反序列化,更新后 再 序列化 到 Redis
中。
每一个用户属性使用 一对 field-value
,可是只用 一个键 保存。
hmset user:1 name tom age 23 city beijing
复制代码
优势:简单直观,若是使用合理能够 减小内存空间 的使用。
缺点:要控制和减小 哈希 在 ziplist
和 hashtable
两种 内部编码 的 转换,hashtable
会消耗 更多内存。
本文介绍了 Redis
中的 哈希结构 的 一些 基本命令、内部编码 和 适用场景。最后对比了 关系型表 和 哈希结构 的区别,以及几种 存储方式 的优缺点。
《Redis 开发与运维》
欢迎关注技术公众号: 零壹技术栈
本账号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。