目录
- 0.前言
- 1.常见命令
- 1.HSET
- 2.HGET
- 3.HEXISTS
- 4.HDEL
- 5.HKEYS
- 6.HVALS
- 7.HGETALL
- 8.HMGET
- 9.HLEN
- 10.HSETNX
- 11.HINCRBY
- 12.HINCRBYFLOAT
- 2.内部编码
- 1.ziplist(压缩链表)
- 2.hashtable(哈希表)
- 3.使用场景
- 4.缓存方式对比
- 1.原⽣字符串类型
- 2.序列化字符串类型
- 3.哈希类型
0.前言
-
在Redis中,哈希类型是指值本⾝是⼀个键值对结构,形如
key="key",value={{field1, value1}, ..., {fieldN, valueN}}
-
字符串和哈希类型对比:存储一个uid为1的用户对象,姓名James,年龄28
-
注意:哈希类型中的映射关系通常称为
field-value
,⽤于区分Redis整体的键值对(key-value
), 注意这⾥的value
是指field
对应的值,不是键(key
)对应的值
1.常见命令
1.HSET
- 功能:设置
hash
中指定的字段(field
)的值(value
) - 语法:`HSET key field value [field value]
- 返回值:添加字段的个数
- 时间复杂度:插入一组
field
为 O ( 1 ) O(1) O(1),插入N组field
为 O ( N ) O(N) O(N)
2.HGET
- 功能:获取hash中指定字段的值
- 语法:
HGET key field
- 返回值:字段对应的值或者
nil
- 时间复杂度: O ( 1 ) O(1) O(1)
3.HEXISTS
- 功能:判断hash中是否有指定的字段
- 语法:
HEXISTS key field
- 返回值:1表示存在,0表示不存在
4.HDEL
- 功能:删除hash中指定的字段
- 语法:
HDEL key field [field ...]
- 返回值:本次操作删除的字段个数
- 时间复杂度:删除一个元素为 O ( 1 ) O(1) O(1),删除N个元素为 O ( N ) O(N) O(N)
5.HKEYS
- 功能:获取hash中的所有字段
- 先根据key找到对应的hash -> O ( 1 ) O(1) O(1)
- 然后再遍历hash -> O ( N ) O(N) O(N)
- 语法:
HKEYS key
- 返回值:字段列表
- 时间复杂度:
O
(
N
)
O(N)
O(N),N为
field
的个数 - 注意:该操作也是存在一定风险的,类似于之前介绍的
KEYS
6.HVALS
- 功能:获取hash中所有的值
- 语法:
HVALS key
- 返回值:所有的值
- 时间复杂度:
O
(
N
)
O(N)
O(N),N为
field
的个数
7.HGETALL
- 功能:获取hash中的所有字段以及对应的值
- 语法:
HGETALL key
- 返回值:字段和对应的值
- 时间复杂度:
O
(
N
)
O(N)
O(N),N为
field
的个数 - 注意:在使⽤
HGETALL
时,如果哈希元素个数⽐较多,会存在阻塞Redis的可能- 如果只需要获取部分field,可以使⽤
HMGET
- 如果⼀定要获取全部
field
,可以尝试使⽤HSCAN
命令,该命令采⽤渐进式遍历哈希类型
- 如果只需要获取部分field,可以使⽤
8.HMGET
- 功能:一次获取hash中多个字段的值
- 语法:
HMGET key field [field ...]
- 返回值:字段对应的值或者
nil
- 时间复杂度:只查询⼀个元素为 O ( 1 ) O(1) O(1),查询多个元素为 O ( N ) O(N) O(N),N为查询元素个数
9.HLEN
- 功能:获取hash中的所有字段的个数
- 语法:
HLEN key
- 返回值:字段个数
- 时间复杂度: O ( 1 ) O(1) O(1)
10.HSETNX
- 功能:在字段不存在的情况下,设置hash中的字段和值
- 语法:
HSETNX key field value
- 返回值:1表示设置成功,0表示失败
- 时间复杂度: O ( 1 ) O(1) O(1)
11.HINCRBY
- 功能:将hash中字段对应的数值添加指定的值
- 语法:
HINCRBY key field increment
- 返回值:该字段变化之后的值
- 时间复杂度: O ( 1 ) O(1) O(1)
12.HINCRBYFLOAT
- 功能:
HINCRBY
的浮点数版本 - 语法:
HINCRBYFLOAT key field increment
- 返回值:该字段变化之后的值
- 时间复杂度: O ( 1 ) O(1) O(1)
2.内部编码
1.ziplist(压缩链表)
- 当哈希类型元素个数⼩于
hash-max-ziplist-entries
配置(默认512个)、 同时所有值都⼩于hash-max-ziplist-value
配置(默认64字节)时,Redis会使⽤ziplist
作为哈希的内部实现 ziplist
使⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐hashtable
更加优秀
2.hashtable(哈希表)
- 当哈希类型⽆法满⾜
ziplist
的条件时,Redis会使⽤hashtable
作为哈希的内部实现,因为此时ziplist
的读写效率会下降,⽽hashtable
的读写时间复杂度为O(1) - 哈希类型的内部编码,以及响应的变化
- 当
field
个数⽐较少且没有⼤的value
时,内部编码为ziplist
- 当有
value
⼤于64字节时,内部编码会转换为hashtable
- 当
field
个数超过512时,内部编码也会转换为hashtable
- 当
3.使用场景
-
关系型数据表记录的两条用户信息
-
映射关系表⽰⽤⼾信息:可以将每个⽤⼾的
id
定义为键后缀,多对field-value
对应⽤⼾的各个属性
-
优势:相⽐于使⽤JSON格式的字符串缓存⽤⼾信息,哈希类型变得更加直观,并且在更新操作上变得更灵活
UserInfo GetUserInfo(long uid) { // 根据 uid 得到 Redis 的键 String key = “user:" + uid; // 尝试从 Redis 中获取对应的值 userInfoMap = Redis 执⾏命令: hgetall key; // 如果缓存命中(hit) if (value != null) { // 将映射关系还原为对象形式 UserInfo userInfo = 利⽤映射关系构建对象 (userInfoMap); return userInfo; } // 如果缓存未命中(miss),从数据库中,根据 uid 获取⽤⼾信息 UserInfo userInfo = MySQL 执⾏ SQL : select * from user_info where uid = <uid> // 如果表中没有 uid 对应的用户信息 if (userInfo == null) { 响应404 return null; } // 将缓存以哈希类型保存 Redis 执⾏命令: hmset key name userInfo.name age userInfo.age city userInfo.city // 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时 Redis 执⾏命令: expire key 3600 // 返回用户信息 return userInfo; }
-
需要注意的是哈希类型和关系型数据库有两点不同之处:
-
哈希类型是稀疏的,⽽关系型数据库是完全结构化的
- 例如:哈希类型每个键可以有不同的
field
,⽽关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即使为null
- 例如:哈希类型每个键可以有不同的
-
关系数据库可以做复杂的关系查询,⽽Redis去模拟关系型复杂查询
- 例如:联表查询、聚合查询等基本不可能,维护成本⾼
-
4.缓存方式对比
- 目前学习了三种方法缓存用户信息,以下给出三种方案的实现方法和优缺点分析
1.原⽣字符串类型
- 说明:使⽤字符串类型,每个属性⼀个键
- 优点:实现简单,针对个别属性变更也很灵活
- 缺点:占⽤过多的键,内存占⽤量较⼤,同时⽤⼾信息在Redis中⽐较分散,缺少内聚性,所以这种⽅案基本没有实⽤性
- 示例:
set user:1:name James set user:1:age 23 set user:1:city Beijing
2.序列化字符串类型
- 说明:例如JSON格式
- 优点:针对总是以整体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使⽤效率很⾼
- 缺点:本⾝序列化和反序列需要⼀定开销,同时如果总是操作个别属性则⾮常不灵活
- 示例:
set user:1 经过序列化后的⽤⼾对象字符串
3.哈希类型
- 优点:简单、直观、灵活,尤其是针对信息的局部变更或者获取操作
- 缺点:需要控制哈希在
ziplist
和hashtable
两种内部编码的转换,可能会造成内存的较⼤消耗