Redis从入门到精通(7):redis的hash数据类型详解

我们可以把一些有关联的键值对作为一个整体,存储为另一个键的值。这种类似于json和python字典的数据类型就叫做hash,中文叫哈希。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

hash数据类型

其实可以理解为redis中存了一个redis的感觉,不过外面这个键叫做key,里面的key叫做field,字段或者域。

注意哈希的值只能是string,不要在哈希中又嵌套一个哈希。

hashmap内存结构

redis的hash数据结构在内存中采用hashmap,了解hashmap在内存中的结构有助于帮助我们理解为什么hash结构查询非常快,也能帮助我们理解为什么说数据量不大的时候是数组结构,数据量大的时候是链表结构。

hashmap的底层由数组和链表一起构成,内存中一块连续的区域构成数组,通过计算key的hash值然后对数组长度取模来决定这个key存在哪一块区域。因为查询次数为1,所以查询速度很快,这也就是为什么数据量小的时候就是个数组结构。

数据量多了以后肯定会出现key的hash值计算出来一致的情况,也就是hash冲突,hashmap是通过单向链表来解决这个问题。数组中存储的只是一个单链表的头节点。如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

下图引用自知乎专栏,出处在此

hashmap.jpg

真是基于这一数据存储特点,hash数据结构在查询的时候是有顺序的,但是并不是按照key的输入前后顺序。而删除增加修改都不会改变数据的顺序。

常用命令

  • hset key field value

对key中的某个域进行赋值

127.0.0.1:6380[1]> hset user:1 name xiaofu
(integer) 1
127.0.0.1:6380[1]> type user:1
hash
127.0.0.1:6380[1]>

这里key为user:1,其中存储了一个哈希数据类型。哈希当中有一个键值对,哈希中的键为name,值为xiaofu

注意如果是已经存在的key,但是类型是string,赋值hash类型的话会报错。但反之却可以给一个哈希类型赋值一个string

27.0.0.1:6380[1]> set user:1 hahaha
OK
127.0.0.1:6380[1]> type user:1
string
127.0.0.1:6380[1]> hset user:1 name xiaofu
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6380[1]>
  • hmset key field1 value1 field2 value2 …

对多个域同时赋值

127.0.0.1:6380[1]> hmset user:2 name xiaofu age 8 sex male
OK
127.0.0.1:6380[1]>
  • hsetnx key field value

只有在key中有该field不做动作,如果没有该field就赋值

127.0.0.1:6380> hset user name xiaofu
(integer) 1
127.0.0.1:6380> hsetnx user age 18
(integer) 1
127.0.0.1:6380> hsetnx user name james
(integer) 0
127.0.0.1:6380> hgetall user
1) "name"
2) "xiaofu"
3) "age"
4) "18"
127.0.0.1:6380>
  • hget key field
  • hmget key field1 field2 …
  • hgetall key

获取哈希中的一个或多个或所有域对应的值

127.0.0.1:6380[1]> hgetall user:2
1) "name"
2) "xiaofu"
3) "age"
4) "8"
5) "sex"
6) "male"
127.0.0.1:6380[1]> hget user:2 name
"xiaofu"
127.0.0.1:6380[1]> hmget user:2 name sex age
1) "xiaofu"
2) "male"
3) "8"
127.0.0.1:6380[1]>

这里hgetall的输出看着会比较别扭,单数行是field,双数行是field对应的值

  • hkeys key

获取一个哈希类型中所有的field

27.0.0.1:6380[1]> hkeys user:2
1) "name"
2) "age"
3) "sex"
127.0.0.1:6380[1]>
  • hvals key

获取一个哈希类型中所有的值

127.0.0.1:6380[1]> hvals user:2
1) "2"
2) "xiaofu"
3) "22"
4) "male"
127.0.0.1:6380[1]>
  • hlen key

获取一个哈希类型中所有field的个数

27.0.0.1:6380[1]> hlen user:2
(integer) 3
127.0.0.1:6380[1]>
  • hdel key field1 field2 …

删除一个或多个哈希数据中的域

127.0.0.1:6380[1]> hgetall user:2
1) "name"
2) "xiaofu"
3) "age"
4) "8"
5) "sex"
6) "male"
127.0.0.1:6380[1]> hdel user:2 age
(integer) 1
127.0.0.1:6380[1]> hgetall user:2
1) "name"
2) "xiaofu"
3) "sex"
4) "male"
127.0.0.1:6380[1]>

del key 是删除整个哈希数据

  • hincrby key field num

对哈希中的某一个整数类型的域进行增加操作

127.0.0.1:6380[1]> hgetall user:2
1) "id"
2) "2"
3) "name"
4) "xiaofu"
5) "age"
6) "18"
7) "sex"
8) "male"
127.0.0.1:6380[1]> hincrby user:2 age 4
(integer) 22
127.0.0.1:6380[1]>

hash中没有减小的命令,可以将num变为负数达到减小的效果

  • hincrbyfloat key field num

对哈希中的某一个浮点型的域进行增加操作

  • hexists key field

查询哈希中某一个域是否存在,存在返回1,不存在返回0

127.0.0.1:6380[1]> hexists user:2 hobby
(integer) 0
127.0.0.1:6380[1]> hexists user:2 name
(integer) 1
127.0.0.1:6380[1]>

json字符串 vs hash

json字符串更多的是做为一个整体给外界去读取,而很少会修改;哈希因为是按照field去分开存储,更多的是方便修改。

当然哈希中也可以存储jason字符串,例如下面实例中提到的购物车模型。

注意事项

  • 哈希类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash的设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可将哈希做为对象列表使用

  • hgetall操作可以获取全部属性,如果内部field过多,遍历整体数据效率就会很低,有可能成为数据访问的瓶颈

hash实现购物车

用户实时对购物车内的商品种类进行添加和删除,同时对每一个种类的数量也要进行实时增减,涉及到实时地读取数据库,所以考虑用redis。

购物车模型

将每个用户的购物车做为一个哈希,用户id做为key,购物车内容做为哈希数据类型,商品名称做为field,商品数量做为value,如下

127.0.0.1:6380> hmset user:1 good:1 3 good:2 20
OK
127.0.0.1:6380> hmset user:2 good:2 10 good:4 8
OK
127.0.0.1:6380>

此时有两个用户有了自己的购物车

如果要给用户1添加45个商品3,如下

127.0.0.1:6380> hset user:1 good:3 45
(integer) 1
127.0.0.1:6380>

用户1去查看自己的购物车,如下

127.0.0.1:6380> hgetall user:1
1) "good:1"
2) "3"
3) "good:2"
4) "20"
5) "good:3"
6) "45"
127.0.0.1:6380>

用户1不想要商品2了,全部删除,如下

127.0.0.1:6380> hdel user:1 good:2
(integer) 1
127.0.0.1:6380> hgetall user:1
1) "good:1"
2) "3"
3) "good:3"
4) "45"
127.0.0.1:6380>

用户1想多买2个商品3,如下

127.0.0.1:6380> hincrby user:1 good:3 2
(integer) 47
127.0.0.1:6380> hgetall user:1
1) "good:1"
2) "3"
3) "good:3"
4) "47"
127.0.0.1:6380>

购物车模型仅仅是解决了商品的数量问题,但是还没有解决商品的详情显示问题,例如商品图片,价格等等

商品模型

比较容易想到的解决方式就是将上述的商品field变为两个,分别是good:1:numsgood:1:info分别保存商品的数量和商品的详情。因为商品详情不会经常改,所以可以直接用json字符串来表示

127.0.0.1:6380> hmset user:3 good:1:nums 3 good:1:info {name:book1,pic:123,price:20}
OK
127.0.0.1:6380> hgetall user:3
1) "good:1:nums"
2) "3"
3) "good:1:info"
4) "{name:book1,pic:123,price:20}"
127.0.0.1:6380>

但是现在有个问题,假如用户4也买这个商品,只是商品数量不太一样

127.0.0.1:6380> hmset user:4 good:1:nums 5 good:1:info {name:book1,pic:123,price:20}
OK
127.0.0.1:6380> hgetall user:4
1) "good:1:nums"
2) "5"
3) "good:1:info"
4) "{name:book1,pic:123,price:20}"
127.0.0.1:6380>

这样在数据库中造成了大量的数据重复,所以应该考虑将商品的info提取出来,做一个单独的哈希

改进商品模型

所以最后的模型应该如下

127.0.0.1:6380> hmset user:5 good:1 10 good:2 8
OK
127.0.0.1:6380> hmset good:1 name book1 price 10 pic 111
OK
127.0.0.1:6380> hmset good:2 name book2 price 20 pic 222
OK
127.0.0.1:6380>

这样在生成购物车页面的时候只需要查询一次数据库,并且将用户和商品分开节约了数据库资源。

而且商品的信息也可以进行快速的修改(被商家),例如价格。

hash实现抢购

抢购或者秒杀的时候,商品数量瞬息万变,实时性高而且有原子性要求,适合用redis。

假设一个商家在双十一销售三种食物,每种限购500个。考虑实际情况我们可以对每个商品对象建立一个哈希,这里如果我们只考虑商品数量的话,可以创建一个哈希,key为商家id,field为三种食物的名字,value为食物剩余数量

127.0.0.1:6380> hmset shop:1 food:1 500 food:2 500 food:3 500
OK
127.0.0.1:6380> hgetall shop:1
1) "food:1"
2) "500"
3) "food:2"
4) "500"
5) "food:3"
6) "500"
127.0.0.1:6380>

每次有买家下单,就可以进行递减操作。但是哈希里面没有递减操作,可以将递增的偏移量设置为负值。

127.0.0.1:6380> hincrby shop:1 food:1 -5
(integer) 495
127.0.0.1:6380> hincrby shop:1 food:1 -20
(integer) 475
127.0.0.1:6380>

这里我们只考虑数据库的操作,不考虑是否还有商品之类的逻辑判断,逻辑判断交给业务代码去实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值