续上一篇Redis-String,本篇文章为了复习和巩固而记录,这次是到Redis的Hash类型了

  • 结构
  • 内部编码
  • 命令和栗子
  • String与Hash对比

结构

key fieId value
user:1:info name aquan
age 23
date 905
viewCounter 658
PS:可以看成一个`key`对应这一个对象里面有很多属性,*fieId是不可以相同的*。

内部编码

Hash类型的内部编码有两种,ziplist压缩列表,hashtable哈希表。

  • ziplist:当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64个字节)时,Redis会使用ziplist作为哈希的内部实现ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
  • hashtable:当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现。因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

我们利用命令进行一下操作

127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "2.5"
5) "name"
6) "aquan"
7) "height"
8) "180"
127.0.0.1:6379> object encoding user:1:info
"ziplist"
127.0.0.1:6379> hset user:1:info signature "寻寻觅觅,冷冷清清,凄凄惨惨戚戚。乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急!雁过也,正伤心,却是旧时相识。"
127.0.0.1:6379> hgetall user:1:info
 1) "height"
 2) "180"
 3) "age"
 4) "27"
 5) "name"
 6) "aquan"
 7) "pageview"
 8) "2.5"
 9) "signature"
10) "\xd1\xb0\xd1\xb0\xc3\xd9\xc3\xd9\xa3\xac\xc0\xe4\xc0\xe4\xc7\xe5\xc7\xe5\xa
3\xac\xc6\xe0\xc6\xe0\xb2\xd2\xb2\xd2\xc6\xdd\xc6\xdd\xa1\xa3\xd5\xa7\xc5\xaf\xb
b\xb9\xba\xae\xca\xb1\xba\xf2\xa3\xac\xd7\xee\xc4\xd1\xbd\xab\xcf\xa2\xa1\xa3\xc
8\xfd\xb1\xad\xc1\xbd\xd5\xb5\xb5\xad\xbe\xc6\xa3\xac\xd4\xf5\xb5\xd0\xcb\xfb\xa
1\xa2\xcd\xed\xc0\xb4\xb7\xe7\xbc\xb1\xa3\xa1\xd1\xe3\xb9\xfd\xd2\xb2\xa3\xac\xd
5\xfd\xc9\xcb\xd0\xc4\xa3\xac\xc8\xb4\xca\xc7\xbe\xc9\xca\xb1\xcf\xe0\xca\xb6\xa
1\xa3"
127.0.0.1:6379> object encoding user:1:info
"hashtable"

命令和栗子

hget,hset,hdel

命令解释时间复杂度
hget key fieId获取hash key对应的fieId的valueO(1)
hset key fieId value设置hash key对应的fieId的valueO(1)
hdel key field删除hash key对应的fieId的valueO(1)
127.0.0.1:6379> hset user:1:info name aquan
(integer) 0
127.0.0.1:6379> hget user:1:info name
"aquan"
127.0.0.1:6379> hdel user:1:info name
(integer) 1
127.0.0.1:6379> hget user:1:info name
(nil)

hexists,hlen

命令解释时间复杂度
hexists key fieId判断hash key是否存在fieIdO(1)
hlenkey fieId value获取hash key fieId的数量O(1)
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "3"
127.0.0.1:6379> hexists user:1:info age
(integer) 1
127.0.0.1:6379> hexists user:1:info name
(integer) 0
127.0.0.1:6379> hlen user:1:info
(integer) 2

hmget,hmset

命令解释时间复杂度
hmget key fieId1 fieId2 .. fieIdN批量获取hash key的一批fieId对应的值O(n)
hmset key fieId1 value1 fieId2 value2 ..批量设置hash key的一批fieId valueO(n)
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "3"
127.0.0.1:6379> hmget user:1:info age pageview
1) "27"
2) "3"
127.0.0.1:6379> hmset user:1:info name "aquan" height 180
OK
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "3"
5) "name"
6) "aquan"
7) "height"
8) "180"

hgetall,hvals,hkeys

命令解释时间复杂度
hgetall key获取hash key中所有的fieId和valueO(n)
hvals key返回hash key对应所有fieId的value(不返回fieId)O(n)
hkeys key返回hash key对应所有的fieIdO(n)

PS:hgetall谨慎使用在线上环境,谨记Redis是单线程,要是线上环境hash key里的fieId属性值多的话,hgetall命令返回时间就会长,后面的命令就会进行排队等待,建议使用hmget命令代替hgetall获取需要的fieId。

127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "3"
5) "name"
6) "aquan"
7) "height"
8) "180"
127.0.0.1:6379> hvals user:1:info
1) "27"
2) "3"
3) "aquan"
4) "180"
127.0.0.1:6379> hkeys user:1:info
1) "age"
2) "pageview"
3) "name"
4) "height"

hsetnx,hincrby,hincrbyfloat

命令解释时间复杂度
hsetnx key fieId value设置hash key对应fieId的value(如fieId已经存在了,则失败)O(1)
hincrby key fieId numhash key对应的fieId的value自增num(num可以为正负整数)O(1)
hincrbyfloat key fieId numhincrby的浮点数版本O(1)

PS:fieId不可重复,fieId对应的value可以重复。

127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "3"
5) "name"
6) "aquan"
7) "height"
8) "180"
127.0.0.1:6379> hsetnx user:1:info name "haha"
(integer) 0
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "3"
5) "name"
6) "aquan"
7) "height"
8) "180"
127.0.0.1:6379> hincrby user:1:info pageview 2
(integer) 5
127.0.0.1:6379> hincrby user:1:info pageview -1
(integer) 4
127.0.0.1:6379> hincrbyfloat user:1:info pageview -1.5
"2.5"
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "27"
3) "pageview"
4) "2.5"
5) "name"
6) "aquan"
7) "height"
8) "180"

String与Hash对比

相似的API

StringHash
gethget
set,setnxhset,hsetnx
delhdel
incr,incrby,decr,decrbyhincrby
msethmset
mgethmget

存储用户信息场景对比

v1:使用String实现

keyvalue(serializable:Json,xml,protobuf)
user:1{"id":1,"name":"aquan","age":23,"pageView":905}

存储结构如上表,这样做是一个key对应整个用户信息序列化后的JSON,xml等,这样更新数据,存储数据都要进行序列化操作,更新一个小的属性的情况,例如更新age更新到24,那就先要把这个age属性更新到24,整个对象序列化后,再整个更新到整个user:1对应的key中的value中。


v2:使用String实现

keyvalue
user:1:nameaquan
user:1:age23
user:1:pageView905

存储结构如上表,可以看到对比v1方法是把信息拆分为3个key单独存储用户对应的信息,这样做的好处可以部分更新,不用整个覆盖更新,直观,添加新的属性也方便,不影响之前的key,这样做后用户的信息不是一个整体,分散到个个key中,不便于管理。


v3:使用Hash实现

key fieId value
user:1:info name aquan
age 23
viewCounter 905

存储结构如上表,这样做可以实现使用fieId进行单独的属性更新,删除与新增,也让用户信息成为整体方便管理

v1,v2,v3优缺点分析

方案优点缺点
v1 String编程简单,可能节约内存序列化开销,设置属性要操作整个数据
v2 String直观,可以部分更新内存占用较大,key较分散
v3 Hash直观,节省空间,可以部分更新编程稍微复杂,ttl过期时间不好可控制

PS:*过期时间只能针对key设置,无法针对key中二级属性进行设置,*但是可以在逻辑层面进行删除和管理。


三十年河东,三十年河西,莫欺少年穷。