- ncr期已经有完整的视频讲解,请到网盘中下载观看,并自行安装。如安装过程出错,请保证安装包完整无误,依赖包无误,并仔细阅读安装错误日志和检查操作系统层面的用户、用户组、文件和目录是否存在,各种权限是否正确等!
- Redis的官方文档相当丰富和齐全,地址:
https://redis.io/documentation
Redis概述
- Redis一个开源的基于键值对(Key-Value)NoSQL数据库。使用ANSI C语言编写、支持网络、基于内存但支持持久化。性能优秀,并提供多种语言的API。
- 我们要首先理解一点,我们把Redis称为KV数据库,键值对数据库,那就可以把Redis内部的存储视为存在着一个巨大的Map,对Map的操作无非就是get和put,然后通过key操作这个key所对应的value,而这个value的类型可以多种多样,也就是Redis为我们提供的那些数据结构,比如字符串(String)、哈希(Hash)等等。
- Redis会将所有数据都存放在内存中,所以它的读写性能非常惊人。不仅如此,Redis还可以将内存的数据利用快照和日志的形式保存到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据不会“丢失”。除了上述功能以外,Redis还提供了键过期、发布订阅、事务、流水线、Lua脚本等附加功能。
Redis应用场景
1.缓存
- 缓存机制几乎在所有的大型网站都有使用,合理地使用缓存不仅可以加快数据的访问速度,而且能够有效地降低后端数据源的压力。Redis提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够为一个网站的稳定保驾护航。
- mysql写600/s,读2000/s
- redis10w+/s
- 性能瓶颈-缓存+多线程
2.排行榜系统
- 排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。
3.计数器应用
- 计数器在网站中的作用至关重要,例如视频网站有播放数、电商网站有浏览数,为了保证数据的实时性,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择。
4.社交网络
- 赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功能。
5.消息队列系统
- 消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务解耦、非实时业务削峰等特性。Redis提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。
Redis特性
1.速度快
-
正常情况下,Redis执行命令的速度非常快,官方给出的数字是读写性能可以达到10万/秒,当然这也取决于机器的性能,但这里先不讨论机器性能上的差异,只分析一下是什么造就了Redis除此之快的速度,可以大致归纳为以下四点:
-
Redis的所有数据都是存放在内存中的,按照谷歌公司2009年给出的各层级硬件执行速度,所以把数据放在内存中是Redis速度快的最主要原因。
-
Redis是用C语言实现的,一般来说C语言实现的程序“距离”操作系统更近,执行速度相对会更快。
-
Redis使用了单线程架构,预防了多线程可能产生的竞争问题。
2.基于键值对的数据结构服务器
- 几乎所有的编程语言都提供了类似字典的功能,例如Java里的map,类似于这种组织数据的方式叫作基于键值的方式,与很多键值对数据库不同的是,Redis 中的值不仅可以是字符串,而且还可以是具体的数据结构,这样不仅能便于在许多应用场景的开发,同时也能够提高开发效率。Redis 的全称是REmote Dictionary Server,它主要提供了5种数据结构:字符串、哈希、列表、集合、有序集合,同时在字符串的基础之上演变出了位图(Bitmaps)和HyperLogLog两种“数据结构”,并且随着LBS (Location BasedService,基于位置服务)的不断发展,Redis中加入有关GEO(地理信息定位)的功能。
3.丰富的功能
-
除了5种数据结构,Redis 还提供了许多额外的功能:提供了键过期功能,可以用来实现缓存。
-
提供了发布订阅功能,可以用来实现消息系统。支持Lua脚本功能,可以利用Lua创造出新的 Redis命令。提供了简单的事务功能,能在一定程度上保证事务特性。提供了流水线( Pipeline)功能,这样客户端能将一批命令一次性传到Redis,减少了网络的开销。
4.简单稳定
-
Redis的简单主要表现在三个方面。首先,Redis 的源码很少,早期版本的代码只有2万行左右,3.0版本以后由于添加了集群特性,代码增至5万行左右。其次,Redis使用单线程模型,这样不仅使得 Redis服务端处理模型变得简单,而且也使得客户端开发变得简单。最后,Redis不需要依赖于操作系统中的类库。
-
Redis虽然很简单,但是不代表它不稳定。实际的运行中很少出现因为Redis自身bug而宕掉的情况。
5.持久化
- 通常看,将数据放在内存中是不安全的,一旦发生断电或者机器故障,重要的数据可能就会丢失,因此Redis提供了两种持久化方式:RDB 和 AOF,即可以用两种策略将内存的数据保存到硬盘中,这样就保证了数据的可持久性。
6.主从复制
- Redis提供了复制功能,实现了多个相同数据的Redis副本,复制功能是分布式Redis的基础。。
8.高可用和分布式
- Redis Sentinel,它能够保证Redis 节点的故障发现和故障自动转移。Redis 从3.0版本正式提供了分布式实现 Redis Cluster,它是Redis真正的分布式实现,提供了高可用、读写和容量的扩展性。
9.客户端语言多
- Redis提供了简单的TCP通信协议,很多编程语言可以很方便地接人到Redis。
Redis版本和可执行文件
-
在Redis的版本计划中,版本号第二位为奇数,为非稳定版本,如2.7、2.9、3.1;版本号第二为偶数,为稳定版本如2.6、2.8、3.0;一般来说当前奇数版本是下一个稳定版本的开发版本,如2.9是3.0的开发版本。
-
Redis编译完成后,会生成几个可执行文件,这些文件各有各的作用,我们现在先简单了解下,后面的课程会陆续说到和使用这些可执行文件。
-
redis-trib融入到redis-cli中了
可执行文件 | 作用 |
---|---|
*redis-server* | 启动redis |
*redis-cli* | redis命令行客户端 |
*redis-benchmark* | 基准测试工具 |
*redis-check-aof* | AOF持久化文件检测和修复工具 |
*redis-check-rdb* | RDB持久化文件检测和修复工具 |
*redis-sentinel* | 启动哨兵 |
*redis-trib* | cluster集群构建工具(Redis3、4) |
启动、操作和停止Redis
启动
- 有三种方法启动Redis:默认配置、带参数启动、配置文件启动。
默认配置
-
使用Redis的默认配置来启动,控制台直接输入 ./redis-server
-
可以看到直接使用redis-server启动Redis后,会打印出一些日志,通过日志可以看到一些信息:
-
当前的Redis版本的是64位的6.2.4,默认端口是6379。Redis建议要使用配置文件来启动。后面的输出的日志信息的含义,我们将在后面的课程学习到。
-
因为直接启动无法自定义配置,所以这种方式是不会在生产环境中使用的。
带参数启动
-
redis-server加上要修改配置名和值(可以是多对),没有设置的配置将使用默认配置,例如:如果要用6380作为端口启动Redis,那么可以执行:
-
./redis-server --port 6380
-
这种方式一般我们也用得比较少。
配置文件启动
-
将配置写到指定文件里,例如我们将配置写到了/home/redis/redis-6.2.4/conf中,同时将端口改为6880:
-
那么只需要执行如下命令即可启动Redis:
-
./redis-server …/conf/redis-6880.conf
-
注意:这里对配置文件使用了相对路径,绝对路径也是可以的。
-
在Redis中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改。如果要Redis将配置持久化到本地配置文件,需要执行config rewrite命令。
操作
-
Redis服务启动完成后,就可以使用redis-cli连接和操作Redis服务。redis-cli可以使用两种方式连接Redis服务器。
-
控制台方式
-
通过redis-cli -h (host} -p {port}的方式连接到Redis服务,之后所有的操作都是通过控制台进行,例如:
-
我们没有写-h参数,那么默认连接127.0.0.1;如果不写-p,那么默认6379端口,也就是说如果-h和-p都没写就是连接127.0.0.1:6379这个 Redis实例。
-
单次命令方式get
-
第二种是命令方式:用redis-cli -h ip {host} -p{port} {command}就可以直接得到命令的返回结果,例如:
-
那么下一次要操作redis,还需要再通过redis-cli。
停止
-
Redis提供了shutdown命令来停止Redis服务,例如我们目前已经启动的Redis服务,可以执行:
-
./redis-cli -p 6880 shutdown
-
redis服务端将会显示:
-
除了可以通过shutdown命令关闭Redis服务以外,还可以通过kill进程号的方式关闭掉Redis,但是强烈不建议使用kill -9强制杀死Redis服务,不但不会做持久化操作,还会造成缓冲区等资源不能被优雅关闭,极端情况会造成AOF和复制丢失数据的情况。
-
shutdown还有一个参数,代表是否在关闭Redis前,生成持久化文件
-
缺省是save,生成持久化文件,如果是nosave则不生成持久化文件
Redis全局命令
- Redis有一些全局命令,需要我们提前了解,因为在后面ex的课程中可以用的到。
keys
- 查看所有键:
keys *
- 同时也支持通配符
keys *school
dbsize
- dbsize命令会返回当前数据库中键的总数。
dbsize
-
dbsize命令在计算键总数时不会遍历所有键,而是直接获取 Redis内置的键总数变量,所以dbsize命令的时间复杂度是O(1)。
-
而keys命令会遍历所有键,所以它的时间复杂度是o(n),当Redis保存了大量键时线上环境禁止使用keys命令。
exists
- 检查键是否存在,存在返回1,不存在返回0。
exists key
del
- 删除键,无论值是什么数据结构类型,del命令都可以将其删除。返回删除键个数,删除不存在键返回0。同时del命令可以支持删除多个键。
del key
expire
- Redis支持对键添加过期时间,当超过过期时间后,会自动删除键,时间单位秒。
expire key seconds
-
ttl命令会返回键的剩余过期时间,它有3种返回值:
-
大于等于0的整数:键剩余的过期时间。
- -1:键没设置过期时间。
- -2:键不存在
ttl key
-
除了expire、ttl命令以外,Redis还提供了expireat、pexpire,pexpireat、pttl、persist等一系列命令。
- 连续两次set 一个字符串类型的值,如果第一次输入了expire,第二次set后,相当于执行了persist命令,就没有过期时间了
-
expireat key timestamp: 键在秒级时间截timestamp后过期。
-
ttl命令和pttl都可以查询键的剩余过期时间,但是pttl精度更高可以达到毫秒级别,有3种返回值:
-
大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)。
- -1:键没有设置过期时间。
- -2:键不存在。
-
pexpire key milliseconds:键在milliseconds毫秒后过期。
-
pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期。
-
在使用Redis相关过期命令时,需要注意以下几点。
- 1)如果expire key 的键不存在,返回结果为0:
- 2)如果过期时间为负值,键会立即被删除,犹如使用del命令一样:
- 3 ) persist命令可以将键的过期时间清除:
- 4)对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易在开发中被忽视。
- 5 ) Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置。
type
- 返回键的数据结构类型,例如键hello是字符串类型,返回结果为string。键mylist是列表类型,返回结果为list,键不存在返回none
type key
randomkey
- 随机返回一个键,这个很简单,请自行实验。
rename
- 键重命名
rename oldkey newkey
- 但是要注意,如果在rename之前,新键已经存在,那么它的值也将被覆盖。
renameenx oldkey newkey
-
为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖。
-
从上面我们可以看出,由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性。
键名的生产实践
-
Redis没有命令空间,而且也没有对键名有强制要求。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用“业务名:对象名: id : [属性]”作为键名(也可以不是分号)。
-
例如MySQL 的数据库名为enjoy,用户表名为edu,那么对应的键可以用"enjoy:edu:1", "enjoy:edu:1:name"来表示,如果当前Redis 只被一个业务使用,甚至可以去掉“enjoy:”。
-
如果键名比较长,例如“lesson: {lid} :student :subject : {sid}”,可以在能描述键含义的前提下适当减少键的长度,例如变为“l : {lid) :st :su : {sid}”,从而减少由于键过长的内存浪费。
Redis常用数据结构
- Redis提供了一些数据结构供我们往Redis中存取数据,最常用的的有5种,字符串(String)、哈希(Hash)、列表(list)、集合(set)、有序集合(ZSET)。
字符串(String)
- 字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
操作命令
set 设置值
-
set key value [ex seconds] [px milliseconds] [nxlxx]
-
例如:
-
set hello redis
-
设置键为hello,值为redis的键值对,返回结果为OK代表设置成功。
-
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 incr-key
get incr-key
- 除了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的值。
-
业务名:对象名:id:属性
hset设值
-
hset user:1 name mark
-
如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。
hget取值
-
hget user:1 name
-
如果键或field不存在,会返回nil。
hdel删除field
- hdel会删除一个或多个field,返回结果为成功删除field的个数。
hlen计算field个数
hmset批量设值
- hmset user:1 name james age 26 city beijing name king age 19
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的字符串长度
hgetall 获取所有的field-value
命令的时间复杂度
- 哈希类型的操作命令中,hdel,hmget,hmset的时间复杂度和命令所带的field的个数相关O(k),hkeys,hgetall,hvals和存储的field的总数相关,O(N)。其余的命令时间复杂度都是O(1)。
使用场景
-
哈希类型比较适宜存放对象类型的数据,我们可以比较下,如果数据库中表记录为:
-
使用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的32次方-1个元素。在Redis 中,可以对列表两端插入( push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
-
列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。第二、列表中的元素可以是重复的。
操作命令
lrange 获取指定范围内的元素列表
-
key start end
-
索引下标特点:从左到右为0到N-1
-
lrange 0 -1命令可以从左到右获取列表的所有元素
rpush 从右向左插入
lpush 从左向右插入
linsert 在某个元素前或后插入新元素
- 这三个返回结果为命令完成后当前列表的长度,也就是列表中包含的元素个数,同时rpush和lpush都支持同时插入多个元素。
lpop 从列表左侧弹出
rpop 从列表右侧弹出
- 上面的操作将列表最左侧的元素b被弹出。rpop将会把列表最左侧的元素a弹出。
lrem 对指定元素进行删除
-
lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:
-
count>0,从左到右,删除最多count个元素。
-
count<0,从右到左,删除最多count绝对值个元素。
-
count=0,删除所有。
-
返回值是实际删除元素的个数。
ltirm 按照索引范围修剪列表
- 例如想保留列表中第0个到第1个元素
lset修改指定索引下标的元素
-
: key index newvalue
-
下面操作会将列表listkey中的第3个元素设置为python
lindex 获取列表指定索引下标的元素
llen 获取列表长度
阻塞式的弹出元素
-
blpop
-
brpop
-
list可以命令停在这里,直到有元素才弹出,所以可以用于实现消息队列