第二部分:Redis数据结构

一、Redis 基本数据结构

Redis 提供了一些数据结构供我们往 Redis 中存取数据,最常用的的有 5 种, 字符串(String)、哈希(Hash)、列表(list)、集合(set)、有序集合(ZSET)。

(一)字符串(String)

字符串类型是 Redis 最基础的数据结构。首先键都是字符串类型,而且其他 几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数 据结构的学习奠定基础。字符串类型的值实际可以是字符串(简单的字符串、复 杂的字符串(例如 JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、 视频),但是值最大不能超过 512MB。

1、操作命令

(1)set 设置值

set key value [ex seconds] [px milliseconds] [nxlxx]

例如: set hello redis

 设置键为 hello,值为 redis 的键值对,返回结果为 OK 代表设置成功。

set 命令有几个选项:

ex seconds:为键设置秒级过期时间。

px milliseconds:为键设置毫秒级过期时间。

nx:键必须不存在,才可以设置成功,用于添加。

xx:与 nx 相反,键必须存在,才可以设置成功,用于更新。

第二部分:Redis数据结构_第1张图片

 从执行效果上看,ex 参数和 expire 命令基本一样。还有一个需要特别注意的 地方是如果一个字符串已经设置了过期时间,然后你调用了 set 方法修改了它,它 的过期时间会消失。

而 nx 和 xx 执行效果如下

第二部分:Redis数据结构_第2张图片

除了 set 选项,Redis 还提供了 setex 和 setnx 两个命令:

setex key seconds value

setnx key value

setex 和 setnx 的作用和 ex 和 nx 选项是一样的。也就是,setex 为键设置秒 级过期时间,setnx 设置时键必须不存在,才可以设置成功。

setex 示例 

第二部分:Redis数据结构_第3张图片

 setnx 示例

第二部分:Redis数据结构_第4张图片

因为键 foo-ex 已存在,所以 setnx 失败,返回结果为 0,键 foo-ex2 不存在,所 以 setnx 成功,返回结果为 1。

有什么应用场景吗?以 setnx 命令为例子,由于 Redis 的单线程命令处理机制, 如果有多个客户端同时执行 setnx key value,根据 setnx 的特性只有一个客户端 能设置成功,setnx 可以作为分布式锁的一种实现方案。当然分布式锁没有不是 只有一个命令就 OK 了,其中还有很多的东西要注意。

(2)get 获取值

如果要获取的键不存在,则返回 nil(空)

 第二部分:Redis数据结构_第5张图片

(3)mset 批量设置值

通过 mset 命令一次性设置 4 个键值

 (4)mget 批量获取值

 批量获取了键 a、b、c、d 的值

第二部分:Redis数据结构_第6张图片

 如果有些键不存在,那么它的值为 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 秒(1000*1+1000*0.1=1100ms),1 次 mget 命令的需要 0.101 秒 (1*1+1000*0.1=101ms)

(5)Incr 数字运算

incr 命令用于对值做自增操作,返回结果分为三种情况:

值不是整数,返回错误。

值是整数,返回自增后的结果。

键不存在,按照值为 0 自增,返回结果为 1

第二部分:Redis数据结构_第7张图片

 除了 incr 命令,Redis 提供了 decr(自减)、 incrby(自增指定数字)、decrby(自 减指定数字)、incrbyfloat(自增浮点数)。

(6)strlen 字符串长度

返回字符串长度

第二部分:Redis数据结构_第8张图片

 注意:每个中文占 3 个字节

(7)getset 设置并返回原值

getset 和 set 一样会设置值,但是不同的是,它同时会返回键原来的

(8)setrange 设置指定位置的字符

第二部分:Redis数据结构_第9张图片

下标从 0开始

(9)getrange 截取字符串 

getrange 截取字符串中的一部分,形成一个子串,需要指明开始和结束的 偏移量,截取的范围是个闭区间

第二部分:Redis数据结构_第10张图片

2、命令的时间复杂度

字符串这些命令中,除了 del 、mset、 mget 支持多个键的批量操作,时间 复杂度和键的个数相关,为 O(n),getrange 和字符串长度相关,也是 O(n),其余 的命令基本上都是 O(1)的时间复杂度,在速度上还是非常快的。

3、使用场景 

缓存功能

Redis 作为缓存层,MySQL 作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低 后端压力的作用。

计数

使用 Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能, 同时数据可以异步落地到其他数据源。

共享 Session

一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各 自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户 的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个 问题是用户无法容忍的。

为了解决这个问题,可以使用 Redis 将用户的 Session 进行集中管理,,在这种 模式下只要保证 Redis 是高可用和扩展性的,每次用户更新或者查询登录信息都 直接从 Rediss 中集中获取。

限速

比如,很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证 码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分 钟获取验证码的频率,例如一分钟不能超过 5 次。一些网站限制一个 IP 地址不 能在一秒钟之内方问超过 n 次也可以采用类似的思路

(二)哈希(Hash)

Java 里提供了 HashMap,Redis 中也有类似的数据结构,就是哈希类型。但 是要注意,哈希类型中的映射关系叫作 field-value,注意这里的 value 是指 field 对应的值,不是键对应的值。

1、操作命令

基本上,哈希的操作命令和字符串的操作命令很类似,很多命令在字符串类 型的命令前面加上了 h 字母,代表是操作哈希类型,同时还要指明要操作的 field 的值。

(1)hset 设值

hset user:1 name mark

 如果设置成功会返回 1,反之会返回 0。此外 Redis 提供了 hsetnx 命令,它 们的关系就像 set 和 setnx 命令一样,只不过作用域由键变为 field。

(2)hget 取值

hget user:1 name

 如果键或 field 不存在,会返回 nil 

(3)hdel 删除 field

hdel 会删除一个或多个 field,返回结果为成功功删除 field 的个数。

第二部分:Redis数据结构_第11张图片

(4)hlen 计算 field 个

 第二部分:Redis数据结构_第12张图片

(5) hmset 批量设值

 (6)hexists 判断 field 是否存在

若存在返回 1,不存在返回0

 (7)hkeys 获取所有 field

返回指定哈希键所有的 field

 (8)hvals 获取所有 value

 (9)hgetall 获取所有 field 与 value

第二部分:Redis数据结构_第13张图片

在使用 hgetall 时,如果哈希元素个数比较多,会存在阻塞 Redis 的可能。如 果只需要获取部分 field,可以使用 hmget,如果一定要获取全部 field-value,可 以使用 hscan 命令,该命令会渐进式遍历哈希类型,hscan 将在后面的章节。

(10)hincrby 增加 

hincrby 和 hincrbyfloat,就像 incrby 和 incrbyfloat 命令一样,但是它们的作 用域是 filed。

(11)hstrlen 计算 value 的字符串长度

(12)hgetall 获取所有的 field-value 

第二部分:Redis数据结构_第14张图片

2、命令的时间复杂度

哈希类型的操作命令中,hdel,hmget,hmset 的时间复杂度和命令所带的 field 的个数相关 O(k),hkeys,hgetall,hvals 和存储的 field 的总数相关,O(N)。其余的命 令时间复杂度都是 O(1)。

3、使用场景

哈希类型比较适宜存放对象类型的数据,我们可以比较下,如果数据库中表 记录为:

 使用 String 类型

set user:1:name james;

set user:1:age 23;

set user:1:sex boy;

优点:简单直观,每个键对应一个值

缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境

将对象序列化存入 redis: set user:1 serialize(userInfo);

优点:编程简单,若使用序列化合理内存使用率高

缺点:序列化与反序列化有一定开销,更新属性时需要把 userInfo 全取出来 进行反序列化,更新后再序列化到 redis

使用 hash 类型: hmset user:1 name james age 23 sex boy

优点:简单直观,使用合理可减少内存空间消耗

缺点:要控制内部编码格式,不恰当的格式会消耗更多内存

(三)列表(list)

列表( list)类型是用来存储多个有序的字符串,a、b、c、d、e 五个元素从左 到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表 最多可以存储 2-1 个元素。在 Redis 中,可以对列表两端插入( push)和弹出(pop), 还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较 灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。

第二部分:Redis数据结构_第15张图片

 列表类型有两个特点:

        第一、列表中的元素是有序的,可以通过 索引下标获取某个元素或者某个范围内的元素列表

        第二、列表中的元素可以是 重复的

1、操作命令

(1)lrange 获取指定范围内的元素列表

key start end

索引下标特点:从左到右为 0 到 N-1

lrange 0 -1 命令可以从左到右获取列表的所有元素

rpush 从右向左插入

lpush 从左向右插入

linsert 在某个元素前或后插入新元素

 第二部分:Redis数据结构_第16张图片

这三个返回结果为命令完成后当前列表的长度,也就是列表中包含的元素个 数,同时rpush 和 lpush 都支持同时插入多个元素。

 (2)lpop 从列表左侧弹出

          rpop 从列表右侧弹出

第二部分:Redis数据结构_第17张图片

上面的操作将列表最左侧的元素 b 被弹出。rpop 将会把列表最左侧的元素 a 弹出。 

 (3)lrem 对指定元素进行删除

rem 命令会从列表中找到等于 value 的元素进行删除,根据 count 的不同分 为三种情况:

        count>0,从左到右,删除最多 count 个元素。

        count<0,从右到左,删除最多 count 绝对值个元素。

        count=0,删除所有。

第二部分:Redis数据结构_第18张图片

第二部分:Redis数据结构_第19张图片

返回值是实际删除元素的个数。 

(4)ltirm 按照索引范围修剪列表

例如想保留列表中第 0到第1个元素

 (5)lset 修改指定索引下标的元素

key index newvalue 下面操作会将列表 listkey 中的第 3个元素设置为 python

 (6)lindex 获取列表指定索引下标的元素

(7)llen 获取列表长度

 

(8)blpop 和 brpop 阻塞式弹出元素

blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,除此之外还支持多个列表类型, 也支持设定阻塞时间,单位秒,如果阻塞时间为 0,表示一直阻塞下去。我们以 brpop 为例说明。

第二部分:Redis数据结构_第20张图片

A 客户端一直处于阻塞状态。此时我们从另一个客户端 B 执行

A 客户端则输出

注意:brpop 后面如果是多个键,那么 brpop 会从左至右遍历键,一旦有一 个键能弹出元素,客户端立即返回。

客户端 B 执行

此时 A 客户端中

客户端 A 会立即返回 list:2 中的元素,因为 list:2 最先有可以弹出的元素。

如果多个客户端对同一个键执行 brpop,那么最先执行 brpop 命令的客户端 可以获取到弹出的值,其余的客户端依然处于阻塞。 

 2、命令的时间复杂度

列表类型的操作命令中,llen,lpop,rpop,blpop 和 brpop 命令时间复杂度都是 O(1),其余的命令的时间复杂度都是 O(n),只不过 n 的值根据命令不同而不同, 比如 lset,lindex 时间复杂度和命令后的索引值大小相关,rpush 和 lpush 和插入元 素的个数相关等等。

3、使用场景

列表类型:

消息队列,Redis 的 lpush+brpop 命令组合即可实现阻塞队列,生产者客户 端使用 lrpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式的 “抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

文章列表:

每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑 使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

实现其他数据结构:

        lpush+lpop = Stack(栈)

        lpush +rpop = Queue(队列)

        lpsh+ ltrim = Capped Collection(有限集合)

        lpush+brpop =Message Queue(消息队列)

 (四)集合(set)

集合( set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是, 集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。

第二部分:Redis数据结构_第21张图片

 一个集合最多可以存储 2 的 32 次方-1 个元素。Redis 除了支持集合内的增删 改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在 实际开发中解决很多实际问题。

1、集合内操作命令

 (1)sadd 添加元素

允许添加多个,返回结果为添加成功的元素个数

(2)srem 删除元素

允许删除多个,返回结果为成功删除元素个数

(3)scard 计算元素个数

(4)sismember 判断元素是否在集合中

如果给定元素 element 在集合内返回 1,反之返回 0

(5)srandmember 随机从集合返回指定个数元素

指定个数如果不写默认为 1

第二部分:Redis数据结构_第22张图片

(6)spop 从集合随机弹出元素

同样可以指定个数,如果不写默认为 1,注意,既然是弹出,spop 命令执行 后,元素会从集合中删除,而 srandmember 不会。

第二部分:Redis数据结构_第23张图片

(7)smembers 获取所有元素

返回结果是无序的

 

2、集合间操作命令

现在有两个集合,它们分别是 set:1 和 set:2

第二部分:Redis数据结构_第24张图片

(1)sinter 求多个集合的交集

(2)sinter 求多个集合的并集

第二部分:Redis数据结构_第25张图片

(3)sdiff 求多个集合的差集

第二部分:Redis数据结构_第26张图片

(4)将交集、并集、差集的结果保存

 sinterstore destination key [key ...]

suionstore destination key [key ...]

sdiffstore destination key [key ...]

集合间的运算在元素较多的情况下会比较耗时,所以 Redis 提供了上面三个 命令(原命令+store)将集合间交集、并集、差集的结果保存在 destination key 中, 例如:

 3、命令的时间复杂度

scard,sismember 时间复杂度为 O(1),其余的命令时间复杂度为 O(n),其中 sadd,srem 和命令后所带的元素个数相关,spop,srandmember 和命令后所带 count 值相关,交集运算 O(m*k),k 是多个集合中元素最少的个数,m 是键个数, 并集、差集和所有集合的元素个数和相关。

4、使用场景

集合类型比较典型的使用场景是标签( tag)。例如一个用户可能对娱乐、体 育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。 有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这 些数据对于用户体验以及增强用户黏度比较重要。

例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数 码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码 产品,通常会为网站带来更多的利益。

除此之外,集合还可以通过生成随机数进行比如抽奖活动,以及社交图谱等等。

 (五)有序集合(ZSET)

有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合, 那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是, 有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数( score)作为排序的依据。

有序集合中的元素不能重复,但是 score 可以重复,就和一个班里的同学学 号不能重复,但是考试成绩可以相同。

有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理 的利用有序集合,能帮助我们在实际开发中解决很多问题。

1、集合内操作命令

 (1)zadd 添加成员

向有序集合 u: gaokao 添加用户 james 和他的分数 555:

返回结果代表成功添加成员的个数 zadd u:gaokao 630 king 602 lison

zadd 命令还有四个选项 nx、xx、ch、incr 四个选项

nx; member 必须不存在,才可以设置成功,用于添加。

xx: member 必须存在,才可以设置成功,用于更新。

ch:返回此次操作后,有序集合元素和分数发生变化的个数

incr:对 score 做增加,相当于后面介绍的 zincrby

 (2)zcard 计算成员个

(3)zscore 计算某个成员的分数

如果成员不存在则返回 nil 

(4)zrank 计算成员的排名

zrank 是从分数从低到高返回排名

zrevrank 反之

很明显,排名从 0 开始计

(5)zrem 删除成员

允许一次删除多个成员。

返回结果为成功删除的个数 

 (6)zincrby 增加成员的分数

         

(7)zrange 和 zrevrange 返回指定排名范围的成员

有序集合是按照分值排名的,zrange 是从低到高返回,zrevrange 反之。如果 加上 withscores 选项,同时会返回成员的分数

第二部分:Redis数据结构_第27张图片

 (8)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 分别 代表无限小和无限大:

第二部分:Redis数据结构_第28张图片

 (9)zcount 返回指定分数范围成员个数

zcount key min max

(10)zremrangebyrank 按升序删除指定排名内的元素

zremrangebyrank key start end

(11)zremrangebyscore 删除指定分数范围的成员

zremrangebyscore key min max

2、集合间操作命令

(1)zinterstore 交集

zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum / min / max]

这个命令参数较多,下面分别进行说明

destination:交集计算结果保存到这个键。

numkeys:需要做交集计算键的个数。 key [key ...]:需要做交集计算的键。

weights weight [weight ...]:每个键的权重,在做交集计算时,每个键中的每个 member 会将自己分数乘以这个权重,每个键的权重默认是 1。

aggregate sum/ min |max:计算成员交集后,分值可以按照 sum(和)、min(最 小值)、max(最大值)做汇总,默认值是 sum。

不太好理解,我们用一个例子来说明。

zadd u2:gaokao 630 av 610 lison 578 leo 720 mark

zinterstore u_sum 2 u:gaokao u2:gaokao

zrange u_sum 0 -1 withscores

第二部分:Redis数据结构_第29张图片

 对 u:gaokao 和 u2:gaokao 进行加权平均计算

zinterstore u_avg 2 u:gaokao u2:gaokao weights 0.9 0.1

zrange u_avg 0 -1 withscores

第二部分:Redis数据结构_第30张图片

(2) zunionstore 并集

zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [ aggregate sunmI minl max]

该命令的所有参数和 zinterstore 是一致的,只不过是做并集计算,大家可以 自行实验。

3、命令的时间复杂度

zadd key score member [score member ...] O(k*log(n)),k 是添加成员的个 数,n 是当前有序集合成员个数

zcard key O(1)

zscore key member O(1)

zrank key member、zrevrank key member O(log(n)),n 是当前有序集合成员 个数

zrem key member [member ...] O(k*1og(n)),k 是删除成员的个数,n 是当前 有序集合成员个数

zincrby key increment member O(log(n)),n 是当前有序集合成员个数

zrange key start end[ withscores]和 zrevrange key start end [ withscores] O(log(n)+k),k 是要获取的成员个数,n 是当前有序集合成员个数

zrangebyscore key min max [ withscores]和 zrevrangebyscore key max min [withscores] O(log(n)+k),k 是要获取的成员个数,n 是当前有序集合成员个数

zcount O(log(n)),n 是当前有序集合成员个数

zremrangebyrank key start end 和 zremrangebyscore key min max O(log(n)+k), k 是要删除的成员个数,n 是当前有序集合成员个数

zinterstore destination numkeys key [key ….] O(n*K)+O(m*log(m)),n是成员数 最小的有序集合成员个数,k 是有序集合的个数,m 是结果集中成员个数

zunionstore destination numkeys key [key…..] O(n)+O(m*log(m)),n 是所有有序 集合成员个数和,m 是结果集中成员个数

4、使用场景

有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上 传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、 按照获得的赞数。

你可能感兴趣的:(redis,数据结构,数据库)