Redis常用数据结构(整理分享)
- 作者: 选择75222135
- 来源: 51数据库
- 2022-11-15
字符串(String)
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
(虽然Redis是C写的,C里面有字符串<本质使用char数组来实现>,但是处于种种考虑,Redis还是自己实现了字符串类型)
操作命令
set 设置值
set key value
set命令有几个选项:
ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在,才可以设置成功,用于添加(分布式锁常用)。
xx: 与nx相反,键必须存在,才可以设置成功,用于更新。
从执行效果上看,ex参数和expire命令基本一样。还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间,然后你调用了set 方法修改了它,它的过期时间会消失。
而nx和xx执行效果如下
除了set选项,Redis 还提供了setex和 setnx两个命令:
setex key seconds value
setnx key value
setex和 setnx的作用和ex和nx选项是一样的。也就是,setex为键设置秒级过期时间,setnx设置时键必须不存在,才可以设置成功。
setex示例:
setnx示例:
因为键foo-ex已存在,所以setnx失败,返回结果为0,键foo-ex2不存在,所以setnx成功,返回结果为1。
有什么应用场景吗?以setnx命令为例子,由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案。当然分布式锁没有不是只有一个命令就OK了,其中还有很多的东西要注意,我们后面会用单独的章节来讲述基于Redis的分布式锁。
get 获取值
如果要获取的键不存在,则返回nil(空):
mset 批量设置值
通过mset命令一次性设置4个键值对
mget 批量获取值
批量获取了键a、b、c、d的值:
如果有些键不存在,那么它的值为nil(空),结果是按照传入键的顺序返回。
批量操作命令可以有效提高效率,假如没有mget这样的命令,要执行n次get命令具体耗时如下:
n次 get时间=n次网络时间+n次命令时间
使用mget命令后,要执行n次get命令操作具体耗时如下:
n次get时间=1次网络时间+n次命令时间
Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令除了命令时间还是有网络时间,假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次 get命令需要1.1秒(10001+10000.1=1100ms),1次mget命令的需要0.101秒(11+10000.1=101ms)。
Incr 数字运算
incr命令用于对值做自增操作,返回结果分为三种情况:
值不是整数,返回错误。
值是整数,返回自增后的结果。
键不存在,按照值为0自增,返回结果为1。
除了incr命令,Redis提供了decr(自减)、 incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数),具体效果请同学们自行尝试。
append追加指令
append可以向字符串尾部追加值
strlen 字符串长度
返回字符串长度
注意:每个中文占3个字节
getset 设置并返回原值
getset和set一样会设置值,但是不同的是,它同时会返回键原来的值
setrange 设置指定位置的字符
下标从0开始计算。
getrange 截取字符串
getrange 截取字符串中的一部分,形成一个子串,需要指明开始和结束的偏移量,截取的范围是个闭区间。
命令的时间复杂度
字符串这些命令中,除了del 、mset、 mget支持多个键的批量操作,时间复杂度和键的个数相关,为O(n),getrange和字符串长度相关,也是O(n),其余的命令基本上都是O(1)的时间复杂度,在速度上还是非常快的。
使用场景
字符串类型的使用场景很广泛:
缓存功能
Redis 作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
计数
使用Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。
共享Session
一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。
为了解决这个问题,可以使用Redis将用户的Session进行集中管理,,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。
限速
比如,很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。一些网站限制一个IP地址不能在一秒钟之内方问超过n次也可以采用类似的思路。
哈希(Hash)
Java里提供了HashMap,Redis中也有类似的数据结构,就是哈希类型。但是要注意,哈希类型中的映射关系叫作field-value,注意这里的value是指field对应的值,不是键对应的值。
操作命令
基本上,哈希的操作命令和字符串的操作命令很类似,很多命令在字符串类型的命令前面加上了h字母,代表是操作哈希类型,同时还要指明要操作的field的值。
hset设值
hset user:1 name lijin
如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。
hget取值
hget user:1 name
如果键或field不存在,会返回nil。
hdel删除field
hdel会删除一个或多个field,返回结果为成功删除field的个数。
hlen计算field个数
hmset批量设值
hmget批量取值
hexists判断field是否存在
若存在返回1,不存在返回0
hkeys获取所有field
它返回指定哈希键所有的field
hvals获取所有value
hgetall获取所有field与value
在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型,hscan将在后面的章节介绍。
hincrby增加
hincrby和 hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是filed。
hstrlen 计算value的字符串长度
命令的时间复杂度
哈希类型的操作命令中,hdel,hmget,hmset的时间复杂度和命令所带的field的个数相关O(k),hkeys,hgetall,hvals和存储的field的总数相关,O(N)。其余的命令时间复杂度都是O(1)。
使用场景
从前面的操作可以看出,String和Hash的操作非常类似,那为什么要弄一个hash出来存储。
哈希类型比较适宜存放对象类型的数据,我们可以比较下,如果数据库中表记录user为:
id | name | age |
---|---|---|
1 | lijin | 18 |
2 | msb | 20 |
1、使用String类型
需要一条条去插入获取。
set user:1:name lijin;
set user:1:age 18;
set user:2:name msb;
set user:2:age 20;
优点:简单直观,每个键对应一个值
缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境
2、将对象序列化存入redis
set user:1 serialize(userInfo);
优点:编程简单,若使用序列化合理内存使用率高
缺点:序列化与反序列化有一定开销,更新属性时需要把userInfo全取出来进行反序列化,更新后再序列化到redis
3、使用hash类型
hmset user:1 name lijin age 18
hmset user:2 name msb age 20
优点:简单直观,使用合理可减少内存空间消耗
缺点:要控制内部编码格式,不恰当的格式会消耗更多内存
列表(list)
列表( list)类型是用来存储多个有序的字符串,a、b、c、c、b四个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储(2^32-1)个元素(4294967295)。
在Redis 中,可以对列表两端插入( push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
列表类型有两个特点:
第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
第二、列表中的元素可以是重复的。
操作命令
lrange 获取指定范围内的元素列表(不会删除元素)
key start end
索引下标特点:从左到右为0到N-1
lrange 0 -1命令可以从左到右获取列表的所有元素
rpush 向右插入
lpush 向左插入
linsert 在某个元素前或后插入新元素
这三个返回结果为命令完成后当前列表的长度,也就是列表中包含的元素个数,同时rpush和lpush都支持同时插入多个元素。
lpop 从列表左侧弹出(会删除元素)
r
请注意,弹出来元素就没了。
rpop 从列表右侧弹出
rpop将会把列表最右侧的元素d弹出。
lrem 对指定元素进行删除
lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:
count>0,从左到右,删除最多count个元素。
count<0,从右到左,删除最多count绝对值个元素。
count=0,删除所有。
返回值是实际删除元素的个数。
ltirm 按照索引范围修剪列表
例如想保留列表中第0个到第1个元素
ls
lset修改指定索引下标的元素
lindex 获取列表指定索引下标的元素
l
llen 获取列表长度
blpop和brpop阻塞式弹出元素
blpop和brpop是lpop和rpop的阻塞版本,除此之外还支持多个列表类型,也支持设定阻塞时间,单位秒,如果阻塞时间为0,表示一直阻塞下去。我们以brpop为例说明。
A客户端阻塞了(因为没有元素就会阻塞)
A客户端一直处于阻塞状态。此时我们从另一个客户端B执行
A客户端则输出
注意:brpop后面如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。
使用场景
列表类型可以用于比如:
消息队列,Redis 的 lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
实现其他数据结构
lpush+lpop =Stack(栈)
lpush +rpop =Queue(队列)
lpsh+ ltrim =Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
集合(set)
集合( set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
一个集合最多可以存储2的32次方-1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。
集合内操作命令
sadd 添加元素
允许添加多个,返回结果为添加成功的元素个数
srem 删除元素
允许删除多个,返回结果为成功删除元素个数
scard 计算元素个数
sismember 判断元素是否在集合中
如果给定元素element在集合内返回1,反之返回0
srandmember 随机从集合返回指定个数元素
指定个数如果不写默认为1
spop 从集合随机弹出元素
同样可以指定个数,如果不写默认为1,注意,既然是弹出,spop命令执行后,元素会从集合中删除,而srandmember不会。
smembers 获取所有元素(不会弹出元素)
返回结果是无序的
集合间操作命令
现在有两个集合,它们分别是set1和set2
sinter 求多个集合的交集
suinon 求多个集合的并集
sdiff 求多个集合的差集
将交集、并集、差集的结果保存
sinterstore destination key [key ...] suionstore destination key [key ...] sdiffstore destination key [key ...]复制代码
集合间的运算在元素较多的情况下会比较耗时,所以 Redis提供了上面三个命令(原命令+store)将集合间交集、并集、差集的结果保存在destination key中,例如:
使用场景
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。
例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。
除此之外,集合还可以通过生成随机数进行比如抽奖活动,以及社交图谱等等。
有序集合(ZSET)
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数( score)作为排序的依据。
有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。
集合内操作命令
zadd添加成员
返回结果代表成功添加成员的个数
要注意:
zadd命令还有四个选项nx、xx、ch、incr 四个选项
nx: member必须不存在,才可以设置成功,用于添加。
xx: member必须存在,才可以设置成功,用于更新。
ch: 返回此次操作后,有序集合元素和分数发生变化的个数
incr: 对score做增加,相当于后面介绍的zincrby
zcard 计算成员个数
zscore 计算某个成员的分数
如果成员不存在则返回nil
zrank计算成员的排名
zrank是从分数从低到高返回排名
zrevrank反之
很明显,排名从0开始计算。
zrem 删除成员
允许一次删除多个成员。
返回结果为成功删除的个数。
zincrby 增加成员的分数
zrange和zrevrange返回指定排名范围的成员
有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。如果加上 withscores选项,同时会返回成员的分数
zrangebyscore返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count] zrevrangebyscore key max min [withscores][limit offset count]复制代码
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如下面操作从低到高返回200到221分的成员,withscores选项会同时返回每个成员的分数。
同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大:
zcount 返回指定分数范围成员个数
zcount key min max
zremrangebyrank 按升序删除指定排名内的元素
zremrangebyrank key start end
zremrangebyscore 删除指定分数范围的成员
zremrangebyscore key min max
集合间操作命令
zinterstore 交集
zinterstore
这个命令参数较多,下面分别进行说明
destination:交集计算结果保存到这个键。
numkeys:需要做交集计算键的个数。
key [key ...]:需要做交集计算的键。
weights weight [weight ...]:每个键的权重,在做交集计算时,每个键中的每个member 会将自己分数乘以这个权重,每个键的权重默认是1。
aggregate sum/ min |max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum。
不太好理解,我们用一个例子来说明。(算平均分)
zunionstore 并集
该命令的所有参数和zinterstore是一致的,只不过是做并集计算,大家可以自行实验。
使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情