Redis 安装部署,基本数据类型介绍
本文将介绍Redis
基本安装操作,以及基本指令,以及基础数据类型的介绍。
安装部署
您可以在Redis
官网上下载最新的 Redis https://redis.io/
源码安装方式
使用 tar xzf
用于解压下载好的Redis
安装包。我们一般会通过ln -s
的方式指定一个工作目录,有利于后续版本升级。接下来使用make
进行安装的操作,(此命令需要依赖gcc
环境,如果您的系统没有安装的话,需要先进行安装的操作)。安装完成后可以使用redis-cli -v
的指令查询所安装的Redis
的版本。
配置, 启动,操作,停止
接下来我们将介绍一下Redis
安装成功之后的几个重要的可执行文件,您可以在当前的目录下的src
中或者/usr/local/bin
目录下找到它们。
可执行文件 | 作用 |
---|---|
redis-server | 启动 Redis 服务 |
redis-cli | Redis 命令行客户端 |
redis-benchmark | Redis 基准测试工具 |
redis-check-aof | Redis AOF持久化文件检测和修复工具 |
redis-check-dump | Redis RDB持久化文件检测和修复工具 |
redis-sentinel | 启动 Redis Sentinel |
如上所见,就是几个重要的可执行文件的简单介绍,我们通常称它们为Redis shell
。
启动
接下来我们尝试启动一个Redis
服务,您可以执行以下指令
redis-server
看到如下日志,证明您的Redis-server
服务端已经启动成功了
4454:M 23 Dec 2021 09:06:58.756 * Ready to accept connections
现在您可以新开一个terminal
窗口来使用它了
在新的窗口您可以使用如下指令连接到刚刚启动的服务
redis-cli
您可以看到已经成功连接到Redis
服务
[root@d4d3d679abb8 shell]# redis-cli 127.0.0.1:6379>
这样重新开启新的窗口然后连接,是不是觉得有点麻烦呢,这是因为当前的Redis
是默认前台启动的,我们可以使它后台启动,这样它就会在后台挂起,并且不影响您正常使用。首先我们先关闭刚刚打开的客户端,然后执行ctrl+c
的命令,可以退出当前正在运行的Redis
服务端。
我们可以通过如下几种方式修改Redis
的启动方式,您可以使用上述的方式来测试它是否启动成功。
-
1, 我们可以在启动的时候配置启动参数,没有指定的话它会使用默认的配置
redis-server --daemonize yes
当然我们还可以指令其他的一些配置,我们在最后会进行介绍
-
2,(推荐)我们还可以修改配置文件,并且在启动的时候指定我们修改的配置文件启动。
现在修改
Redis
的启动方式为后台启动 将daemonize no
修改为daemonize yes
然后执行启动的指令redis-server /home/shell/redis/redis.conf
配置
接下来我们简单的介绍一些常见`Redis`的配置项(`Redis`共有60多个配置 详情可见本章节的附件部分)
配置名 | 配置说明 |
---|---|
port | 端口 |
logfile | 日志文件 |
dir | Redis 工作目录(存放持久化文件和日志文件) |
daemonize | 是否以守护进程的方式启动 |
以下是我们的配置文件所在的目录:
/home/shell/redis/redis.conf
操作
接下来我们将介绍它的两种连接客户端的方式
-
1,交互式方式
通过
redis-cli -h { host } -p { port }
的方式连接到Redis
服务,之后的所有操作都是通过交互式的方式实现,例如:(-h
默认为 127.0.0.1-p
默认为 6379 所以刚刚的redis-cli
是此命令的缩写)redis-cli -h 127.0.0.1 -p 6379
-
2,命令方式
使用
redis-cli -h { host } -p { port } {command}
可以直接得到命令的返回结果[root@86318cd7ea26 shell]# redis-cli -h 127.0.0.1 -p 6379 select 1OK
停止
您可以通过如下指令停止redis-sever
服务
redis-cli shutdown
常用指令,基本特性介绍
本章节主要介绍一下Redis
的一些全局命令,数据库的基本操作,key的基本操作,以及了解一下Redis
的单线程命令处理机制,主要是为了后面内容的学习打下一个比较好的基础。主要体现在两个方面:
- 第一、
Redis
的命令上百个,如果纯靠死记硬背比较困难,但是如果理解Redis
的一些机制,会发现这些命令有很强的通用性。 - 第二、
Redis
不是万金油,有些数据结构和命令必须在特定场景下使用,一旦使用不当可能会对Redis
本身或者应用本身造成致命伤害。
全局命令
为了您可以更好的学习Redis
接下来的课程,我们先简单介绍一些通用指令。 如果您执行了关闭redis-server
服务操作的话,您需要重新启动redis-server
服务,并且使用redis-cli
进入客户端才能继续以下的课程。
1,数据库操作
首先我们先了解一下Redis
默认拥有16个数据库,默认使用的是第一个也就是0号数据库。各个数据库之间是相互独立的,互不影响,没有任何关联。Redis
提供了几个面向Redis
数据库的操作,它们分别是select
,flushdb/flushall
命令,我们依次来介绍一下
-
1,切换数据库
我们已经了解到 使用
redis-cli
进入Redis
客户端后,默认所处的数据库为0号数据库,如果我们想切换所处的数据库的话可以使用如下命令(默认配置是拥有16个数据库,您如果不修改配置文件的话能切换的选择范围是(0-15))select 1
我们可以通过查看当前的输入行前缀查看当前当前所处的数据库,当不位于0号数据库之下时,会在前面[ ]里边显示数据库的编号。
目前
Redis
已经逐渐弱化这个功能,例如Redis
的分布式实现RedisCluster
只允许使用0号数据库, 只不过为了向下兼容老版本的数据库功能,该功能没有完全废弃掉, 下面分析一下为什么要废弃掉这个“优秀”的功能呢? 总结起来有三点-
Redis
是单线程的,如果使用多个数据库的话,这些数据库仍然是使用的同一个cpu
彼此间还是会受到影响的 -
多数据库的使用方式, 会让调试和运维不同业务的数据库变的困难,假如有一个慢查询存在, 依然会影响其他数据库, 这样会使得别的业务方定位问题非常的困难 。
-
部分
Redis
的客户端根本就不支持这种方式。 即使支持, 在开发的时候来回切换数字形式的数据库, 很容易弄乱。笔者建议如果要使用多个数据库功能, 完全可以在一台机器上部署多个
Redis
实例, 彼此用端口来做区分, 因为现代计算机或者服务器通常是有多个CPU
的。 这样既保证了业务之间不会受到影响, 又合理地使用了CPU
资源。
-
-
2,
flushdb/flushall
flushdb/flushall
命令用于清除数据库,两者的区别的是flushdb
只清除当前数据库,flushall
会清除所有的数据库。*建议大家使用这两个命令的时候一定要谨慎,慎重。
2,键操作
Redis
有5种基本数据结构分别是String
,Hash
,List
,Set
,Zset
, 它们是键值对中的值, 对于键来说有一些通用的命令
-
1,查看所有键
我们可以通过
key*
来查询当前数据库下的所有键值*注意生产环境禁止使用此命令,此命令会检索所有的
key
如果Redis
存储的量过多的话会造成Redis
的阻塞,在实验中我们会对Redis
的遍历键做简单的分析说明 -
键总数
我们可以通过
dbsize
来查询当前数据库下的键总数*
dbsize
指令在计算键总数的时候不会遍历所有键,而是直接获取Redis
内置的键总数变量,所以dbsize
的时间复杂度是 0(1) 。 -
3,检查键是否存在
我们可以用
exists
来检查指定键是否存在,如果键存在则返回 1 否则返回 0 -
4,删除键
我们可以通过
del
来删除指定键值,操作结果为删除键的个数,支持删除多个。 -
5,键过期
Redis
支持对指定键添加过期事件,当超过指定时间后,会自动删除如下(单位时间为秒)expire key3 100
此方法会返回受响应的键的数量,我们可以通过
ttl
命令来查询当前键的剩余过期时间,如下ttl key3
返回字段为剩余时间,它有两种特殊的返回
- 如果返回-1,证明当前查询的键没有设置过期事件
- 如果返回-2,则证明当前的键不存在
-
6 ,值的数据结构类型
我们可以通过
type
命令来查询指定键的对应值的数据类型如:type key3
我们将得到返回值的基本数据类型
数据结构和内部编码
如上type
指令返回的就是当前值的基本数据类型。基本数据类型分别是 string
(字符串)、hash
(哈希)、list
(列表)、set
(集合)、zset
(有序集合),但这些仅仅是 Redis
对外的数据结构,如下图。
![redis数据结构和内部编码](https://lab-cdn.naixuejiaoyu.com/redis/redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E5%86%85%E9%83%A8%E7%BC%96%E7%A0%81.png)
如图所示,这样设计的好处
-
1.可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令。
-
2.多重内部编码实现可以在不同场景下发挥各自的优势,例如
zipList
比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis
会根据配置选项将列表类型的内部实现转换为linkedList
。
单线程架构
Redis
使用了单线程架构和I/O
多路复用模型,来实现高性能的内存数据库服务。本节简单介绍一下Redis
的单线程命令处理机制,接着简单分析一下Redis
单线程模型为什么性能如果之高,最终您会对Redis
的单线程模型有初步的了解。
-
一.单线程处理机制
Redis
是单线程来处理命令的,所以一条命令从客户端到达服务端,会进入一个队列中,然后逐个执行,不会存在两条指令同时执行的情况,如图:
-
二.为什么单线程还能这么快。
-
1,纯内存访问,
Redis
将所有数据都放在内存中,内存的响应时长大约为100纳秒 这是 Redis 达到每秒百万级的重要基础 -
2,非阻塞 IO ,
Redis
使用epoll
作为I/O
多路复用技术的实现,再加上Redis
本身的时间处理模型将epoll
中的连接,读写,关闭都转换为事件,不在网络I/O
上浪费过多的时间 -
3,单线程避免了线程切换和竞态产生的消耗。
*单线程可以简化数据结构和算法的实现,其次避免了线程切换和竞态产生的消耗。但是单线程对于每个命令的执行时间是有要求的,如果某个指令执行时间过长的话,会造成其他命令的阻塞,对于
Redis
这种高性能的服务是致命的,所以Redis
是面向快速执行场景的数据库。
-
Redis
基础数据类型 String
首先介绍的是Sring
类型,首先键都是字符串类型的,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串能为其他四种数据结构的学习奠定基础。 如图所示,字符串类型的值实际可以是字符串(简单的字符串,复杂的字符串(例如 JSON
,XML
),数字(整数,浮点数),甚至二进制(图片,音频,视屏))但是值最大不能超过 512MB
。
常用命令
我们这里只介绍一些详细的命令,在实验中提供了String
类型所有的命令,以及示例。
-
1,单个设置值,获取值
Redis
可以通过set
命令来创建一个String
类型的键值对:set key value
我们可以通过
get
命令来获取我们保存的数据:get key
我们也可以为我们的数据设置过期事件,我们只需要在
set
命令后添加EX[过期时间]
;set key value ex 5
这里的时间单位是秒,我们可以等待5秒之后再去查询,就会发现
get key
的结果为(nil)
这个意思是不存在的意思。当然我们也可以使用毫秒为单位来设置过期时间,如果我们希望使用毫秒作为过期的时间,我们需要使用
px
:set key value px 5000
有些情况下,我们希望设置一个具体的时间,比如说
2021-12-31 23:59:59
, 很可惜我们不能直接使用这个时间,我们需要用所谓的时间戳。时间戳是计算机用来表达时间的一种方式,它是从1970年1月1日(UTC/GMT
的午夜)开始所经过的秒数,不考虑闰秒。2021-12-31 23:59:59
用时间戳表示就是:1640966399
:set key value EXAT 1640966399
这样,我们键为
key
的这个数据会在2021-12-31 23:59:59
到了后过期。同样,我们也可以设置毫秒级别的到期时间,这样会更加准确一些,毫秒级别的到期时间用的是
PXAT
:set key value PXAT 1640966399000
有时候为了避免覆盖已经存在的数据,我们可以在设置数据的时候,增加一个
NX
的标志,NX
也就是NOT EXISTS
的意思。这时候如果我们准备保存的键值对在Redis
数据库中已经存在,它就会报错set key value NX
与
NX
相反的是,还有一个标志是XX
,它的作用是要求这个键必须存在:set key-not-exist value XX
(nil)
的意思就是没有成功的意思。相反,我们重新设置一下键为
key
的值:set key new-value XX
当然,
Redis
也为我们提供了语法糖,比如set key value NX
等价于setnx key value
,set key value XX
等价于setxx key value
。 -
2,批量设置值,获取值
Redis
提供了mset
来批量创建多个键值对:mset demo1 dsds demo2 dededed demo3 ededede
它会返回你创建成功的个数。同时
Redis
也提供了mget
用于批量获取值,我们可以用来获取获取我们刚刚存储的所有值:mget demo1 demo2 demo3
批量操作可以有效的提高开发效率,可以替换
n
次的get
命令的操作,下述我们简单做了两种命令的时间消耗对比操作 时间 1000次get 1000 * 1+1000 * 0.1=1000毫秒=1.1秒 1次mget(组装1000和键值对) 1*1+1000 * 0.1=101毫秒=0.101秒 *需要的注意的是每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成
Redis
阻塞或者网络阻塞。 -
3,计数
我们在开头的时候了解到
String
类型可以存储整数或者浮点数,Redis
对于值的类型为整数或者浮点数的键值对,提供了计数的相关命令,可以对值进行递增,递减的操作。给大家介绍的是
incr
命令,它可以对值进行递增的操作。incr key
如果我们递增了一个不存在的键,它会先创建一个键值对值为0,随后对这个键值对进行递增的操作,返回递增后的结果。
内部编码
接下来我们来了解一下字符串类型的内部编码。
- 1,
int
:8个字节的长整型 - 2,
embstr
:小于等于39个字节的字符串 - 3,
raw
:大于39个字节的字符串
Redis
会根据你存储的值的字符串长度,选取合适的内部编码实现。这里我们只做一下基础的了解。
常见使用场景
-
1.缓存功能
Redis
具有支撑高并发的特性,所以一些热点信息使用缓存,可以起到加速读写和降低后端压力的作用例如,存储用户信息,商品信息等一些热点信息。一般采用
Json
格式存储 -
2.计数
很多应用会使用
Redis
作为计数的基本工具,它可以实现快速计数,查询缓存的功能。同时数据可以异步落地到其他持久化的数据源。例如:电商系统中,商品的浏览量,购买量等都是以天为单位 在
Redis
中计数,然后使用定时任务同步到ES
分布式搜索引擎 或者Mysql
中持久化保存。 -
3.共享
Session
分布式
Web
服务为了 实现单点登录 可以采用 分布式Session
共享的方式实现。其原理就是将用户的Session
进行集中管理,而不是分布在各个服务端 进行管理如图: -
4.限速,降频
Redis
可以给指定key
设置过期时间,通过检查指定key
的存在情况,可以实现一下定时的业务场景。例如:常见的
app
进行短信登录的时候会对多次短信发送,做时间间隔的操作,常见的实现方式,发送短信之前,在Redis
中查看上次发送的短信是否过期。
Redis基本数据类型 Hash
这里只介绍一些常用的命令,在实验中提供了Hash
类型所有的命令,以及示例。
-
1,单个设置值,获取值
Redis
可以通过hset
命令来创建一个Hash
类型的键值对,它可以一次设置多个field
:hset hash1 field1 val1 field2 val2
返回的是您新建成功的个数,我们可以通过
hget
来获取具体的field
的值hget hash1 field1
当前如果您想获取到指定
key
下边所有的filed和value
的话,您也可以使用如下的命令hgetall hash1
-
2,删除操作
我们在开始的基础设置以及了解到
del
可以删除指定的key
,Redis
针对Hash
类型来提供了删除指定field
的命令:hdel hash1 field1
您可以使用这个命令删除指定的
field
,当然也支持同时删除多个hdel hash1 field1 field2
-
3,计算
field
的个数Redis
也提供了指定key
计算field
个数的方法hlen hash1
返回的为
field
的个数。 -
4,批量设置,获取
field-value
同样
Redis
也提供了Hash
类型批量设置和获取的方法hmset
和hmget
,我们来简单使用一下hmset hash1 field1 val1 field2 val2
大家可能会发现这个方法使用和
hset
方法一致。这个方法在Redis 4.0.0
的时候弃用了,如果您的Redis
版本低于4.0.0
还是需要使用hmset
来存储多个field-value
的,如下是官方文档介绍As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code. 根据Redis 4.0.0,HMSET被视为已弃用。请在新代码中使用HSET。
获取多个
field-value
目前还需要使用hmget
如下hmget hash1 field1 field2
内部编码
接下来我们来了解一下Hash
类型的内部编码。
-
1,
zipList
(压缩列表)当哈希类型元素个数小于
hash-max-ziplist-entries
配置(默认512个)、同时所有值小于hash-max-ziplist-value
配置(默认64字节)时,Redis
会使用ziplist
作为哈希的内部实现,ziplist
使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hshtable
更加优秀 -
2,
hashtable
(哈希表)当哈希类型无法满足
ziplist
的所有条件时,Redis
会使用hashtable
作为哈希的内部实现,因为此时ziplist
的读写效率会下降,而hashtable
的读写时间复杂度为0(1)
常见使用场景
-
1,用于缓存
Redis
的hash
类型的(key,field,value)的结构与对象的(对象id,属性,值)的结构相似,可以用来存储热点的对象数据。与 string 类型存储的区别如下String+json
hash
效率 很高 高 容量 低 低 灵活性 低 高 序列化 简单 复杂 当对象的某个属性需要频繁修改时,不合适用
string + json
,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值,如果使用 hash 类型则可以针对某个属性单独修改。另外使用string + json
格式存储查询检索方式单一,只能通过key
的方式查询,如果使用hash
类型存储则可以使用key +field
的方式进行检索。综上: 一般对象使用
string + json
存储,对象中如果有某些频繁变化的属性建议使用hash
存储
Redis
基本数据类型List
List
类型,列表(List
) 类型是用来存储多个有序的字符串, 如图所示, a、b、 c、 d、 e五个元素从左到右组成了一个有序的列表, 列表中的每个字符串称为元素(element
) , 一个列表最多可以存储232-1个元素。 在Redis
中, 可以对列表两端插入(push
) 和弹出(pop
) , 还可以获取指定范围的元素列表、 获取指定索引下标的元素等。 列表是一种比较灵活的数据结构, 它可以充当栈和队列的角色, 在实际开发上有很多应用场景。
列表类型有两个特点:
- 列表中的元素是有序的;
- 列表中的元素是可以重复的;
常用命令
我们先按照对 列表的5种操作类型 ,对所有的api
进行了一个分类,然后按照分类进行介绍
操作类型 | 操作 |
---|---|
增加 | rpush 、lpush 、linsert |
查 | lrange 、lindex 、llen |
删除 | lpop 、rpop 、lrem 、ltrim |
修改 | lset |
阻塞操作 | blpop 、brpop |
-
1,添加操作
首先我们了解到,
Redis
中的List
是一个有序的列表,我们可以在列表的右侧,左侧,或者某个元素的位置上进行插入,相对的Redis
给我们提供了以上三个场景的命令,分别是rpush
、lpush
、linsert
三个命令,我们依次来演示一下-
1,
rpush
从右侧新增元素(支持单个或者多个),该命令会返回当前所有元素的个数,此时新建的元素在队列的末尾:rpush list1 value1 value2 value3
您可以通过
lrange list1 0 -1
指令来查询列表:lrange list1 0 -1
可以看到新插入的数据在列表的末尾。
-
2,
lpush
从左侧新增元素(支持单个或者多个),该命令会返回当前所有元素的个数,此时新增的元素在队列的开头位置:lpush list1 left1 left2
我们再来查询一下列表:
lrange list1 0 -1
可以发现新增的元素按照插入时间的倒叙在列表的开头位置。
-
3,
linsert
是在指定元素的前或者后插入元素,该命令会返回当前所有元素的个数。首先我们先在上述list1
的value1
前方插入一个元素linsert1
linsert list1 before value1 linsert1
再次查询列表:
lrange list1 0 -1
可以看到
linsert1
确实位于位于value1
的上方我们再向上述
list1
的value1
后方插入一个元素linsert1
linsert list1 after value1 linsert1
再查询一下:
lrange list1 0 -1
如您所见
value1
的下方也出现了一个linsert1
,这也只能的List
中的元素是可以重复的
-
-
2,查找
Redis
提供了多种检索List
列表的方法,同上述我们使用到的lrange
检索指定范围内的元素列表,还提供了获取指定索引元素的方法lindex
,以及查询List
列表长度的方法llen
,下述我们依次介绍一下-
1,
lrange
获取指定范围内的元素列表这个方法我们在上边已经使用过了,我们来解析一下刚刚使用的命令
lrange list1 0 -1
其中list1
是查询的指定键,后面跟的两个参数分别是开始的索引start
,和结束的索引end
。Redis
中的索引下标有两个特:第一, 索引下标从左到右分别是0
到N-1
, 但是从右到左分别是-1
到-N
;第二,lrange
中的end
选项包含了自身, 这个和很多编程语言不包含end
不太相同。 例如想获取列表的第2到第4个元素, 可以执行如下操作:lrange list1 1 3
-
lindex
可以获取指定索引的元素这个方法简单,就不赘述了,我们来一个示例
lindex list1 1
-
llen
这个方法可以获取列表长度这个方法同我们前面课程介绍的
dbsize
一样获取的系统参数,并不是检索的内容,时间复杂度为0(1)llen list1
-
-
3,删除
同样的
Redis
也提供了多重删除List
中元素的方法分别是:lpop
从左侧弹出,rpop
从右侧弹出,lrem
删除指定元素,ltrim
删除指定范围内的元素。老规矩我们还是依次来介绍一下:-
1,
lpop
从列表的左侧弹出元素,可以指定弹出元素的个数,此结果会返回删除的元素值先查询当前的列表
lrange list1 0 -1
执行指令
lpop list1 1
再次查询列表:
lrange list1 0 -1
-
2,
rpop
从列表的右侧弹出元素,同样可以指定弹出元素的个数rpop list1 1
查询列表:
lrange list1 0 -1
可以发现在列表的末尾部分,少了一个
-
lrem
删除指定元素lrem
可以删除指定的元素,它的语法是这样的lrem key count value
这个命令会从列表中找到等于value的元素进行删除, 根据count的不同分为三种情况:- count>0, 从左到右, 删除最多count个元素。
- count<0, 从右到左, 删除最多count绝对值个元素
- count=0, 删除所有。
例如:新建一个列表中从左向右插入5个a和两个b。
lpush list2 a a a a a b b
通过查询可以得知那么当前列表变为“ b b a a a a a”:
lrange list2 0 -1
下面操作将从列表左边开始删除4个为a的元素:
lrem list2 4 a
随后我们再次查询列表,看看是否和我们猜想的一样变成了
"b b a"
lrange list2 0 -1
-
修改操作
Redis
提供了修改指定索引下标元素的方法lset list1 0 newval
-
阻塞操作
blpop key [key ...] timeout brpop key [key ...] timeout
阻塞式弹出有两个命令`blpop`和`brpop`这个两个方法是`lpop`和`rpop`的阻塞版本,它们除了弹出方向不同之后,使用方法基本相同,下面以`blpop`为例子 首先解释一下上述语法:这个方法就是当给定的列表内没有任何元素可供弹出的时候,连接将被 `BLPOP `命令阻塞,直到等待超时或发现可弹出元素为止。
- key [key …] 多个列表的键
- timeout 阻塞时间(单位秒)
我们来一个简单的例子实践一下
首先有两种情况(列表为空和列表不为空的情况)
我们先创建一个列表
lpush list3 val3
可以查看一下当前
list3
中有一个元素val3
lrange list3 0 -1
我们执行一下
BLPOP
命令让他阻塞10秒blpop list3 10
我们可以发现当列表不为空的时候,它会立即返回结果,并没有进入阻塞等待,返回值为,指定的
key
以及弹出的值。我现在再来看一下list3
的情况lrange list3 0 -1
可以发现当前的
list3
是空的,这时候我们再执行BLPOP
命令:blpop list3 10
可以看到它在阻塞了10秒之后返回了空,在这期间如果我们插入了元素,那么它会停止阻塞,立即返回,我们来试一下。
首先我们还是使它处理阻塞等待状态,这次时间稍微长点设置30秒
blpop list3 30
首先打开新的
terminal
窗口点击我打开使用指令给
list3
新建一个元素redis-cli lpush list3 new
随后切换为成我们原先的
terminal
窗口可以看到,命令并没有阻塞等待30秒自动返回,而是立即返回了我们刚刚新增的new
元素
-
内部编码
接下来我们来了解一下List
类型的内部编码。
-
ziplist
(压缩列表):当列表的元素个数小于list-max-ziplist-entries
配置(默认512个),同时列表中每个元素都小于list-max-ziplist-value
配置时(默认64字节),redis
会选取ziplist
来作为列表的内部实现来减少内存的使用。 -
linkedlist
(链表):当列表类型无法满足ziplist
的条件是,Redis
会使用linkedlist
作为列表的内部实现。Redis 3.2
版本提供了quicklist
内部编码,简单地说它是一个ziplist
为节点的linkedlist
,它结合了ziplist
和linkedlist
两者的优势,为列表类型提供了一种更加优秀的内部编码实现,它的设置原理可以参考Redis
的另一个作者Matt Stancliff
的博客: https://matt.sh/redis-quicklist
使用场景
-
消息队列
Redis
的lpush + brpop
命令组合即可实现阻塞队列,生产者客户端使用lrpush
从列表左侧插入元素,多个消费者客户端使用brpop
命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。 -
文章列表
对于热点数据的文章列表可以考虑使用
Redis
的List
类型存储,因为列表不但是有序的,同时支持按照索引范围获取元素。可能存在问题如下- 如果每次获取的文章个数较多,需要执行多次
hgetall
操作 ,此时可以考虑使用 pipeline (管道)批量获取 lrange
命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此时可以考虑将列表作二级拆分,或者使用Redis3.2
的quicklist
内部编码实现,它结合ziplist
和linkedlist
的特点,获取列表中间范围的元素时也可以高效完成。
- 如果每次获取的文章个数较多,需要执行多次
实际上列表的使用场景很多,下述是指令组合能达到的 类似数据类型特点
-
lpush + lpop
=Stack
(栈) -
lpush + rpop
=Queue
(队列) -
lpush + ltrim
=Capped Collection
(有限集合) -
lpush + brpop
=Message Queue
(消息队列)
Redis
基本数据类型Set
Set
类型,集合(Set
) 类型也是用来保存多个的字符串元素,但是和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,也不是通过索引下标获取元素。如图所示 集合user
: 1: follow
包含着"it"
、 "music"
、 "his"
、 "sports"
四个元素, 一个集合最多可以存储232-1个元素。 Redis
除了支持集合内的增删改查, 同时还支持多个集合取交集、 并集、 差集, 合理地使用好集合类型, 能在实际开发中解决很多实际问题
常用命令
下面将按照集合内和集合之间两个维度对集合的常用命令进行介绍
-
一,集合内操作
-
1,单个添加,查询元素
Redis
提供了sadd
用户添加Set
类型的元素sadd set1 demo1
您可以使用
smembers
命令来返回当前Set
中的所有元素,返回的元素是无序的。smembers set1
*类似
smembers
和List
中的Lrange
,Hash
中的hgetall
都是比较重的命令,如果元素过多的话存在阻塞Redis
的可能性,这时候可以使用scan
来完成,在实验的附件中介绍,可以参考。 -
2,计算元素的个数
使用
scard
可以计算当前集合中的元素的个数scard set1
scard
的时间复杂度为0(1),它不会遍历集合所有元素,而是直接使用Redis
内部的变量 -
3,判断元素是否在集合中
Redis
提供的键是否存在的函数为exists
,同时提供了sismemeber
用于判断集合中是否有指定的元素sismember set1 demo1
如果给定元素
element
在集合内返回1,反之返回0 -
4,随机从集合中返回指定个数的元素
Redis
提供srandmember
从集合中随机返回指定个数的元素srandmember set1 1
-
5,从集合中随机弹出元素
Redis
提供spop
从集合中随机弹出指定个数的元素(注意这里边是弹出就是删除)(最后的个数可以选择不填默认是1个)spop set1 1
-
6,删除元素
Redis
提供的删除键的方法是del
,Redis
提供了srem
用于删除集合中的指定元素(我们先添加一个)sadd set1 demo1 demo2
srem set1 demo1
此方法会返回删除成功的个数
-
-
二,集合间的操作
Redis
提供了多个集合间求交集,并集,差集,以及将这些结果保存的命令,下面我们依次介绍一下-
1,求多个集合的交集
sinter
首先我们先新建两个集合
sadd set4 a h b hb
sadd set5 b e berb rer ebrer
然后我们可以使用
sinter
来得到两个集合的交集sinter set4 set5
-
2,求多个集合的并集
sunion
可以得到多个集合的并集sunion set4 set5
-
3,求多个集合的差集
sdiff
可以得到多个集合的差集,sdiff set4 set5
-
4,将交集、并集、差集的结果保存
sinterstore destination key [key ...] sunionstore destination key [key ...] sdiffstore destination key [key ...]
集合间的运算在元素较多的情况下比较耗时,所以
Redis
提供了上面三个命令,(原命令+ store)将集合间交集,并集,差集的结果保存在destination key
中。我们先执行
sunionstore
方法将结果输出到myset1
中sunionstore myset1 set5 set4
我们查询一样结果是否输出到指定键中
smembers myset1
这里仅仅列举了一个的使用方法,其他两者使用方法一致。
-
内部编码
接下来我们来了解一下Set
类型的内部编码。
集合的内部编码有两种:
intset
( 整数结合 ) :当集合中的元素都是整数且元素个数小于set-max-intset-entries
配置(默认512个)时,Redis
会选用intset
来作为集合的内部实现,从而减少内存的使用。hashtable
( 哈希表):当集合类型无法满足intset
条件时,Redis
会使用hashtable
作为集合的内部实现
使用场景
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、 体育比较感兴趣, 另一个用户可能对历史、 新闻比较感兴趣, 这些兴趣点就是标签。 有了这些数据就可以得到喜欢同一个标签的人, 以及用户的共同喜好的标签, 这些数据对于用户体验以及增强用户黏度比较重要。 例如一个电子商务的网站会对不同标签的用户做不同类型的推荐, 比如对数码产品比较感兴趣的人, 在各个页面或者通过邮件的形式给他们推荐最新的数码产品, 通常会为网站带来更多的利益 。
下面使用集合类型实现标签功能的若干功能示例。大家可以尝试一下。
-
给用户添加标签
sadd user1:tages tag1 tag3
sadd user2:tages tag3 tag3
-
给标签添加用户
sadd tag1 user1 user2 user3
-
删除用户下的标签
srem user1:tages tag1
-
删除标签下的用户
srem tag1 user2 user3
-
计算用户共同感兴趣的标签
sinter user1:tages user2:tages
上述只是给出了使用
Redis
结合类型实现标签的基本思路,实际上会复杂很多,不过集合类型的应用场景通常为以下几种:-
sadd
=Tagging
(标签) -
spop/srandmember
=Random item
(生成随机数,比如抽奖) -
sadd + sinter
=Social Graph
(社交需求)
-
Redis
基本数据类型 ZSet
章节叙述
本章节我们简单介绍一下`Zset`(有序集合)类型,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。下图给出了列表、集合、有序集合三者的异同点。
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 时间轴、消息对列表等 |
集合 | 否 | 否 | 无 | 标签、社交等 |
有序结合 | 否 | 是 | 分值 | 排行榜系统、社交等 |
常用命令
本节依旧按照集合内和集合外两个维度对有序集合的命令进行介绍。
-
一,集合内操作
-
1,设置元素
Redis
提供了zadd
的方法用于添加Zset
类型的键值对。我们先介绍一下它的基本语法zadd key score member [sore memeber ...]
score代表元素的分值,member列表存储的元素,它支持一次性设置多个元素,返回结果为成功添加成员的个数
下面的操作将向有序集合
zset:user
中添加用户tom
和他的分数251 ,以及他的小伙伴tom1
和tom2
他两的分数分别为262和263zadd zset:user1 200 tom 201 tom1 253 tom2
有关add命令有两点需要注意的:
-
Redis
后续版本对zadd
方法添加了nx
,xx
,ch
,incr
,gt
,lt
;六个可选项我们来简单了解一下-
1)首先
nx
与xx
这两个选项我们在string
的章节中已经介绍过了,nx
表示目标的元素必须不存在才可以设置成功,而xx
表示只有目标元素存在才可以设置成功,用于元素的更新。我们简要的使用下述指令测试其中
nx
使用我们先存储一个已经存在的
zadd zset:user1 nx 261 tom
可以看到没有新增成功返回结果为0 接下来我们新增一个不存在的键
zadd zset:user1 nx 264 tom4
-
2)其次
incr
在之前的章节中了解到用于自增的命令,这里是用于对score
做增加并且返回增加成功的值,增加的数量我们可以自己指定,下述指令中为1。我们简单使用一下zadd zset:user1 incr 1 tom
Redis
也提供了相应的语法糖zincrby key score value member
下面是简单的示例zincrby zset:user1 1 tom
通过返回值可以观察到在上次增加后的结果上又增加了1。我们也可以使用
zrange
的命令查询指定key
所有元素,这个指令我们在接下来会详细介绍zrange zset:user1 0 -1 withscores
我们可以观察到
tom
的分值确实是263 -
3)接下来我们了解一下
ch
选项,这个选项会返回成功修改的个数,请注意原始命令返回的是成功新增的元素个数,我们了解到zset
是有序集合,集合中的元素是唯一的,如果添加了相同的元素,但是分值不同会进行替换的操作,这时新增的个数为0,我们可以通过如下来进行测试首先设置一个值
zadd zset:user2 123 tom
通过
ch
设置和刚刚相同的值zadd zset:user2 ch 124 tom
我们再使用普通的方式重新设置一下值
zadd zset:user2 125 tom
我们可以发现,使用
ch
设置值会返回覆盖操作的元素个数,而普通方式不会。 -
4)
gt
以及lt
这两个选项代表的含义是:如果元素存储在的话,修改的分数必须大于或者小于原先的分数才能修改成功。其中gt
表示大于,lt
表示小于。请注意这两个选项,与[nx/xx]
不可以共用。同样我们来进行简单的测试首先设置一个值
zadd zset:user3 166 tom
然后我们使用
gt
方式来修改刚刚新建的值为165zadd zset:user3 gt ch 165 tom
最后我们使用
zrange
来查询集合的所有元素zrange zset:user3 0 -1 withscores
最后我们可以发现
tom
的分数还是166
可以确定由于修改的分值小于原先的分值,所以修改并没有生效。
-
-
2,计算成员个数
我们可以通过
zcard
来查询指定键的元素的个数,当然您值的类型必须是zset
类型,否则查询会报错。和集合类型的scard
命令一样,zcard
的时间复杂度为0(1)例如下面的操作我们将返回有序集合
zset:user1
的成员数。zcard zset:user1
-
3,计算某个成员的分数
您可以通过
zscore
命令来查询指定成员的分数。例如
zset:user3
中tom
的分数为166,:zscore zset:user3 tom
我们再查询
zset:user3
中不存在的用户,会返回(nil)空zscore zset:user3 test
-
4,计算成员的排名
Redis
提供了两个查询成员排名的命令:分别是zrank
(将分数从低到高返回排名)和zrevrank
(将分数从高到低返回排名)。例如下边的操作,查询
tom
在zset:user1
中的两种排名从低到高查询排名 请注意排名是从0开始计数的,通过存在分值一样的会根据元素的字典值来进行排序,具体的可见附件部分。
zrank zset:user1 tom1
从高到低查询排名
zrevrank zset:user1 tom1
-
5,删除成员
Redis
提供了zrem
用于删除指定成员(支持删除多个)以下操作会将
tom
从zset:user1
中删除,返回的为成功删除的个数zrem zset:user1 tom
-
6,返回指定排名范围的成员
有序集合是按照分值排名的,`zrange`是从低到高返回,`zrevrange`反之。它的索引排序依旧是`[0,-1]`。如果加上`withscores`选项,同时返回成员的分数。
例如下面示例返回
zset:user1
中排名最低的三个成员zrange zset:user1 0 2 withscores
-
7,返回指定分数范围的成员或个数
同时
Redis
还提供了查询指定分数范围的方法zrangebyscore
以及zrevrangebyscore
其中前者是按照分数从低到高排序,后者反之。同样提供了winthscores
选项用于返回成员的分数。同样我们先查询一下
zset:user1
中分数范围是500 -600 的元素名称,发现返回空zrangebyscore zset:user1 500 700
我们来查询和260-262中的元素。返回为此区间的元素个数
zrangebyscore zset:user1 200 300
-
8,我们还可以使用
zcount
查询指定分数范围内的成员个数。我们使用它的查询260-265中的元素个数
zcount zset:user1 200 300
-
-
-
二,集合间的操作
-
首先插入两个有序集合在 Redis 中如图所示
zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom
zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
-
1,交集
首先介绍一下语法
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
。
下面我们列举几个场景来对这些参数加深一下了解
-
1)下面操作对
user: ranking: 1
和user: ranking: 2
做交集,zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
我们可以发现返回结果为3 这是因为
weights
和aggregate
使用了默认配置,对目标user: ranking: 1_inter_2
对分值做了sum
操作我们来通过
zrange
命令来查询是否输出到指定键zrange user:ranking:1_inter_2 0 -1 withscores
-
2)如果想让
user: ranking: 2
的权重变为0.5
, 并且聚合效果使用max
, 可以执行如下操作:zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5
同样我们查询一下是否输出到指定键
zrange user:ranking:1_inter_2 0 -1 withscores
-
-
2,并集
我们还是先来了解一下语法
zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
该命令的所有参数和
zinterstore
是一致的, 只不过是做并集计算, 例如下面操作是计算user: ranking: 1
和user: ranking: 2
的并集,weights
和aggregate
使用了默认配置, 可以看到目标键user: ranking: 1_union_2
对分值做了sum
操作:zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
我们可以发现返回结果为7 这是因为
weights
和aggregate
使用了默认配置,对目标user: ranking: 1_inter_2
对分值做了sum
操作我们来通过
zrange
命令来查询是否输出到指定键zrange user:ranking:1_union_2 0 -1 withscores
-
内部编码
接下来我们来了解一下Zset
类型的内部编码。
有序结合类型的内部编码有两种
ziplist
(压缩列表):当有序集合的元素个数小于zet-max-ziplist-entries
配置(默认128个),同时每个元素的值都小于zet-max-ziplist-value
配置 (默认64字节)时,Redis
会用ziplist
来作为有序集合的内部实现,ziplist
可以有效减少内存的使用skiplist
(跳跃表) : 当ziplist
条件不满足时,有序列表会使用skiplist
作为内部实现,因为此时ziplist
的读写效率会下降。
使用场景
有序结合比较典型的应用场景就是排行榜系统,例如视屏网站需要对用户上传的视屏做排行榜,榜单的维度可能是多个方面的: 按照时间、按照播放数量、按照获得的赞数、本节使用赞数这个维度,记录每天用户是上传视屏的排行榜。主要需要实现以下4个功能。
-
1,添加用户赞数
用户 mike 上传一个视屏,并获得了3个赞,可以使用有序集合的
zadd
和zincrby
功能:zadd user:ranking:2021_12_20 3 mike
如果之后再获得了一个赞,可以使用 zincrby
zincrby user:ranking:2021_12_20 1 mike
2,取消用户赞数
由于各种原因(例如用户注销,用户作弊)需要将用户删除,可以使用
zrem
zrem user:ranking:2021_12_20 mike
-
3,展示获取赞数最多的十个用户
此功能使用
zrevange
命令实现:zervangebyrank user:ranking:2021_12_20 0 9
4,展示用户信息以及用户分数
此功能将用户名作为键的后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用
zscore
和zrank
两个功能hgetall user:info:tom zscore user:ranking:2021-12_20 mike zrank user:ranking:2021_12_20 mike