redis 的数据结构: String(字符串)、Hash(哈希)、List(列表)、Set(集合)、zset(有序集合)

一、String(字符串)

  1. 简介
    String 类型是 Redis 中最基本、最常用的数据类型,甚至被很多玩家当成 Redis 唯一的数据类型去使用。String 类型在 Redis 中是二进制安全(binary safe)的,这意味着 String 值关心二进制的字符串,不关心具体格式,你可以用它存储 json 格式或 JPEG 图片格式的字符串。

2、内部编码
如果存储数字的话,是用int(8字节长整型)类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr编码;大于39个字节,则是raw编码。

有关redis的数据内部编码抽空整理一篇文章单独写

3、使用场景
(1) 存储一些配置数据

在前后分离式开发中,有些数据虽然存储在数据库,但是更改特别少。比如有个全国地区表。当前端发起请求后,后台如果每次都从关系型数据库读取,会影响网站整体性能。

我们可以在第一次访问的时候,将所有地区信息存储到redis字符串中,再次请求,直接从数据库中读取地区的json字符串,返回给前端。

(2) 缓存对象

将对象转为json存储,比如商品信息,用户信息。

(3) 数据统计

redis整型可以用来记录网站访问量,某个文件的下载量,签到人数、视频访问量等等。(自增自减)

(4) 时间内限制请求次数

比如已登录用户请求短信验证码,验证码在5分钟内有效的场景。
当用户首次请求了短信接口,将用户id存储到redis 已经发送短信的字符串中,并且设置过期时间为5分钟。当该用户再次请求短信接口,发现已经存在该用户发送短信记录,则不再发送短信。

(5) 订单号(全局唯一)

有时候你需要去生成一个全局唯一值的时候可以通过redis生成。关键命令:incrby(原子自增)。

SET order_no 2001 --假设订单号从2001开始,这里vlaue必须是int类型
INCRBY order_no 1 --自增1,这个时候返回2002
(6) 分布式session

当我们用nginx做负载均衡的时候,如果我们每个从服务器上都各自存储自己的session,那么当切换了服务器后,session信息会由于不共享而会丢失,我们不得不考虑第三应用来存储session。

通过我们用关系型数据库或者Redis等非关系型数据库。关系型数据库存储和读取性能远远无法跟Redis等非关系型数据库比。

二、Hash(哈希)
1、简介
Hash的数据结构我们可以简单理解为java中的 Map<String,Map<String,String>,这种结构就特别适合存储对象,上面的String的类型确实也可以存储对象,但每次修改对象中的某一个属性,都要拿出整个json字符串在修改这个属性,之后在重新插入,而hash的接口特点让我们可以只修改该对象的某一个属性。

hash数据类型在存储上述类型的数据时具有比 String 类型更灵活、更快的优势,具体的说,使用 String 类型存储,必然需要转换和解析 json 格式的字符串,即便不需要转换,在内存开销方面,还是 hash 占优势。

2、内部编码
哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。

4、使用场景
(1) Redisson分布式锁

Redisson在实现分布式锁的时候,内部的用的数据就是hash而不是String。因为Redisson为了实现可重入加锁机制。所以在hash中存入了当前线程ID。

(2) 购物车列表

以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素,如下图所示。

Redis6 的数据类型 redis使用的数据类型_java


这里涉及的命令如下

hset cart:{用户id} {商品id} 1 # 添加商品

hincrby cart:{用户id} {商品id} 1 # 增加数量

hlen cart:{用户id} # 获取商品总数

hdel cart:{用户id} {商品id} # 删除商品

hgetall cart:{用户id} #获取购物车所有商品
说明:当前仅仅是将商品ID存储到了Redis中,在回显商品具体信息的时候,还需要拿着商品id查询一次数据库。

(3) 缓存对象

hash类型的 (key, field, value) 的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。

在介绍String类型的应用场景时有所介绍,String + json也是存储对象的一种方式,那么存储对象时,到底用String + json还是用hash呢?

两种存储方式的对比如下表所示。

string + json hash
效率 很高 高
容量 低 低
灵活性 低 高
序列化 简单 复杂
一般对象用string + json存储,对象中某些频繁变化的属性可以考虑抽出来用hash存储。

3、常用命令
–增
hset key field1 “s” --若字段field1不存在,创建该键及与其关联的Hash, Hash中,key为field1 ,并设value为s ,若字段field1存在,则覆盖
hmset key field1 “hello” field2 “world” – 一次性设置多个字段
–删
hdel key field1 --删除 key 键中字段名为 field1 的字段
del key – 删除键
–改
hincrby key field 1 --给field的值加1
–查
hget key field1 --获取键值为 key,字段为 field1 的值
hlen key --获取key键的字段数量
hmget key field1 field2 field3 --一次性获取多个字段
hgetall key --返回 key 键的所有field值及value值
hkeys key --获取key 键中所有字段的field值
hvals key --获取 key 键中所有字段的value值

三、List(列表)
1、简介
List类型是按照插入顺序排序的字符串链表,一个列表最多可以存储2^32-1个元素。我们可以简单理解为就相当于java中的LinkesdList。

和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

Redis6 的数据类型 redis使用的数据类型_字符串_02

2、内部编码
如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码。

在Redis 3.2之后就都改用ziplist+链表的混合结构,称之为 quicklist(快速链表)。

2、使用场景
有人会考虑用list数据结构来做一些朋友圈的点赞列表、评论列表、排行榜。也不是不可以但我个人觉得这些功能用set或zset来做会更加合适,下面会具体举例。

(1) 消息队列

lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能。

但如果是这样你发现redis作为消息队列是不安全的,它不能重复消费,一旦消费就会被删除,同时做消费者确认ACK也麻烦所以一般在实际开发中一般很少用redis中消息队列,因为现在已经有Kafka、NSQ、RabbitMQ等成熟的消息队列了,它们的功能更加完善。

4、常用命令
–增
lpush mykey a b --若key不存在,创建该键及与其关联的List,依次插入a ,b, 若List类型的key存在,则插入value中
rpush mykey a b --在链表尾部先插入b,在插入a(lpush list a b那么读的时候是b,a的顺序,而rpush是怎么放怎么读出来
–删
del mykey --删除已有键
–改
lset mykey 1 e --从头开始, 将索引为1的元素值,设置为新值 e,若索引越界,则返回错误信息
–查
lrange mykey 0 -1 --取链表中的全部元素,其中0表示第一个元素,-1表示最后一个元素。
lrange mykey 0 2 --从头开始,取索引为0,1,2的元素
lpop mykey --获取头部元素,并且弹出头部元素,出栈

四、set(集合)
1、简介
Redis 中的 set和Java中的HashSet 有些类似,它内部的键值对是无序的、唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值 NULL。当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。

2、编码
如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。

应用场景
既然set的集合的特性是:无序的、唯一的。那么我们考虑在一些唯一的场景下使用它。

(1) 抽奖活动

存储某活动中中奖的用户ID ,因为有去重功能,可以保证同一个用户不会中奖两次。

sadd user 1 2 3 4 5 --把所有员工(名称或编号)放入抽奖箱

srandmember user 1 – 抽取一个一等奖(员工可以重复参与抽奖)

spop user 1 – 抽取一个一等奖(员工不可以重复参与抽奖)

srandmember user 3 --抽取3个二等奖

smembers user --查看当前抽奖箱中参所有员工

scard user --查看当前抽奖箱中参与抽奖的人数
(2) 点赞

保证一个用户只能点一个赞。key 可以是某某文章、微信朋友圈的文章id

sadd key userId --点赞(/收藏)

srem key userId --取消点赞(/收藏)

smembers key – 获取所有点赞(/收藏)用户

card key – 获取点赞用户数量

sismember key userId --判断是否点赞(/收藏)
(3) 好友人脉

key 可以是 用户id

sadd userId1 1 2 3 4 5
sadd userId2 4 5 6 7 8 --某个user的好友id放入集合

sinter userId1 userId2 --获取共同好友

sdiff userId1 userId2 --给user2推荐user1的好友

sismember userId1 5
sismember userId2 5 --验证某个用户是否同时被user1和user2关注
4、常用命令
–增
sadd myset a b c --若key不存在,创建该键及与其关联的set,依次插入a ,b,c。若key存在,则插入value中,若a 在myset中已经存在,则插入了 b 和 c 两个新成员。

–删
spop myset --尾部的b被移出,事实上b并不是之前插入的第一个或最后一个成员
srem myset a d f --若f不存在, 移出 a、d ,并返回2

–改
smove myset myset2 a --将a从 myset 移到 myset2,

–查
sismember myset a --判断 a 是否已经存在,返回值为 1 表示存在。
smembers myset --查看set中的内容
scard myset --获取Set 集合中元素的数量
srandmember myset --随机的返回某一成员

五、zset(有序集合)
1、简介
Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。成员是唯一的,但是分数(score)却是可以重复的。

2、数据编码
当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码

3、应用场景
既然是 有序的,不可重复的列表,那么就可以做一些排行榜相关的场景。

排行榜(商品销量,视频评分,用户游戏分数)
新闻热搜。
4、常用命令
–增
zadd key 2 “two” 3 “three” --添加两个分数分别是 2 和 3 的两个成员
–删
zrem key one two --删除多个成员变量,返回删除的数量
–改
zincrby key 2 one --将成员 one 的分数增加 2,并返回该成员更新后的分数(分数改变后相应它的index也会改变)
–查
zrange key 0 -1 WITHSCORES --返回所有成员和分数,不加WITHSCORES,只返回成员
zrange key start stop --按照元素的分值从小到大的顺序返回从start 到stop之间的所有元素
zscore key three --获取成员 three 的分数
zrangebyscore key 1 2 --获取分数满足表达式 1 < score <= 2 的成员
zcard key --获取 myzset 键中成员的数量
zcount key 1 2 --获取分数满足表达式 1 <= score <= 2 的成员的数量
zrank key member --获取元素的排名,从小到大
zrevrank key member --获取元素的排名,从大到小