Redis自身已经是键值对结构,Redis自身的键值对就是通过哈希的方式来组织的
把key这一层组织完成后,到了value这一层,calue其中的一种类型还可以再是哈希
后面的field-value是为了防止混淆前面的key
常用命令
hset
使用命令
返回值是设置成功的键值对(field-value)的个数 里面的value是字符串
hget
使用命令
如果要get某个value不存在的话会返回nil
hexists
返回值:1表示存在 0表示不存在
时间复杂度为O(1)
hdel
删除的field
使用命令
还可以批量删除
返回值是本次操作删除的字段个数
若是删除并不存在的field,则删除失败,影响行数为0
还可以删除所有的field
时间复杂度为O(1)
hkeys
这个操作,先根据key找到对应的hash,O(1),然后再遍历hash,O(K)
K为hash的元素个数
这个操作也是有一定风险的,和keys *是一样的风险
hvals
和hkeys相同,获得field相对应的value
h系列的命令必须要保证key对应的value必须是hash类型的
这个操作的时间复杂度为O(K)
这个操作的使用要慎重,有可能会阻塞住redis服务器
hgetall
获取hash中的所有字段以及对应的值
通用命令
每一个field对应value,这样的操作风险较大,多数情况下不需要查询所有的field,可能只查其中的几个field,所以引出hmget
hmget
类似于mget一次可以查询多个ield
- 多个value对应的顺序和field一致,那么有没有hmset来一次设置多个hash的value,有!但是我们并不需要,hset就支持多个field-value的设置
- 上述的hkeys,hvales,hgetall都是存在一定风险的,hash的元素个数太多,执行的耗时会比较长,从而阻塞redis,hscan遍历redis中的hash,但是他只属于”渐进式遍历“-敲一次遍历一小部分,在敲一次再遍历一小部分
- 这让我们想到了ConcurrentHashMap,这个哈希表再扩容的时候,也是化整为零这样执行的
hlen
获取到hash中的所有字段的个数
时间复杂度为O(1),获取hash的个数,不需要遍历
hsetnx
这个操作和setnx类似,不存在时才能设置成功,如果存在则失败
hincrby
hash这里的value也可以当作数字来处理,hincrby 就可以加减整数,hincrbyfloat就可以加减小数,这里使用频率不是很高
hincrbyfloat
上述操作的时间复杂度为O(1)
Hash的命令小结
序号 | 命令及描述 |
---|---|
1 | HDEL key field1 [field2] 删除一个或多个哈希表字段 |
2 | HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 |
3 | HGET key field 获取存储在哈希表中指定字段的值。 |
4 | HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
5 | HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
7 | HKEYS key 获取哈希表中的所有字段 |
8 | HLEN key 获取哈希表中字段的数量 |
9 | HMGET key field1 [field2] 获取所有给定字段的值 |
10 | HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
11 | HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 |
12 | HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 |
13 | HVALS key 获取哈希表中所有值。 |
14 | HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。 |
hash的内部编码:
ziplist(压缩列表)
zip,rar,gzip,7z.. 一些具体的压缩算法,压缩的本质是针对数据进行重新编码,不同的数据有不同的特点,结合这些特点,进行精妙的设计,重新编码之后,就能够缩小体积
例如abbcccddddeeeee可以压缩为1a2b3c4d
例如abcd00000000000000000000000000000efgh重新编码可以压缩为abcd[100]efgh
上述是比较粗糙的编码方式
ziplist内部的数据结构也是精心设计的,目的是节省内存空间
- hash表示一个普通的hash表,可能会浪费一定的空间,hash首先是一个数组,数组上有些位置有元素,有些没元素
- ziplist付出的代价,进行读写元素,速度是比较慢的,如果元素个数少,慢的并不明显,如果元素个数太多了,慢就会雪上加霜
如果
- 哈希中的元素个数比较少,使用ziplist表示,元素个数比较多,使用hashtable来表示
- 每个value的值长度都比较短,使用ziplist表示,如果某个value的长度太长,也会转换为hashtable
- redis中有配置项可以改变转换的阈值:hash-max-ziplist-entries配置(默认512个)
- 在etc/redis/redis.conf文件中可以看到配置项
- 下面一行是表示单个value的长度不能超过64,不然转换为hashtable
应用场景
作为缓存
原生字符串类型-使用字符串类型,每个属性一个键,这种方式相当于把同一个数据的各个属性都分散出来了
string也是可以作为缓存使用的,存储结构化的数据时,使用hash类型更合适一些
转化为下图
上述类型使用string类型也能做到,就需要使用到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 ⼩时(3600 秒)
Redis 执⾏命令:expire key 3600
// 返回⽤⼾信息
return userInfo;
}
缓存方式对比
- 如果使用string(json)的格式来表示UserInfo,万一只想获取其中的某个field,或者修改某个field就需要把整个json都读出来,解析成对象,操作field,再重新转成json格式,在写回去
- 如果使用hash的方式来表示UserInfo就可以使用field表示对象的某个属性(属性表的每个列)此时就可以非常方便的修改/获取任何一个属性的值了
- 使用hash的方式可能会导致内存的较大消耗(在ziplist和hashtable两种内部编码的转换)
- 写代码要做到高内聚低耦合
- 高内聚:有关联的代码写在一个模块里
- 低耦合:避免牵一发动全身,这边一改bug,影响到了其他的地方
使用hash时要保证为null时也要填写上,如果使用string可以不填写,各有各的好
在下面的string存储缓存时,uid和key都是1,可不可以不存呢?
如果确实不想存uid也可以,但是在工程实践中,一般都会把uid在value中再存一份,再存一份,后续写到相关的代码,使用起来会比较方便