1.环境搭建
1.1 在ubuntu上安装redis
1.2 reids客户端介绍
redis也是一个客户端-服务器结构的程序。redis客户端和服务器可以在同一份主机上,也可以在不同的主机上,因为二者是通过网络进行发送和接收请求的。
redis服务器是负责存储和管理数据的。
redis的客户端有很多形态:
1、redis自带的命令行客户端 redis-cli
也可以使用下面指令来连接其他主机的redis客户端:
redis-cli -h 主机名 -p 端口号
2、图形化界面的客户端(桌面程序,web程序)
对于后续业务开发有很多的局限性;
3、基于redis的api自行开发客户端(主要形态),类似于mysql的c语言和jdbc;
1.3 redis快的理解
应用程序要存储一些数据,使用键值对的格式来进行存储。使用redis存储和在内存中搞hashmap存储,redis存储还是比较慢的。因为使用hashmap是直接操作内存,使用redis是要通过网络在操作内存。
但是使用redis之后能够将数据单独存储,后续应用服务器重启之后,不会影响redis'中的数据。同时使用redis可以方便后续将系统扩展为分布式系统。
2. 通用指令
通过redis-cli和redis服务器交互会涉及到很多redis命令。
redis中最核心的命令get和set。redis是按照键值对的方式来存储数据的,get根据key来取value,set吧key和value存储进去。只有先进入redis客户端才能使用redis命令。
key和value都是字符串,对于key和value在set时可以加引号,也可以不加引号(引号单引号和双引号都行),其命令是不分大小写的。
get命令直接输入key就能得到value,如果当前的key不存在,就会返回nil,类似于null;
2.1 通用命令keys
全局命令,就是能够搭配任意一个数据结构来使用的命令。
keys用来查询当前服务器上匹配的key, 通过一些特殊符号(通配符)来描述 key 的模样,匹配上述模样的 key 就能被查询出来。
keys pattern;
pattern:包含特殊符号的字符串,意味(样式or模式),存在的意义就是去描述另外的字符串长啥样式。
?:可以匹配任意一个字符
* :可以匹配零个或者多个任意字符
[ae]:只能匹配到ae,即只可以配置括号里面含有的字符
[^e]:只有e匹配不了,其他的字符都能匹配
[a-b]:表示匹配a-b这个范围内的字符,包含两侧的边界
keys命令的时间复杂度是0(n),所以在生产环境上一般都是不是呀keys。keys*就是查找redis中所有的key。生产环境上的key可能会非常多,而redis 是一个单线程的服务器.执行 keys*的时间非常长,就使 redis 服务器被阻塞了,从而无法给其他客户端提供服务了。
2.2 生产环境(线上环境)
办公环境、开发环境、测试环境统称为线下环境,外界用户无法访问到,线上环境则是外界用户能够访问到的。
2.3 exists
exists key [key 。。。]
判定某个key是否存在,返回值是key存在的个数。针对多个key来说是非常有限的。时间复杂度为o(1)。查询几个k,时间复杂度就是o(几)。
redis的很多命令都是支持一次就能操作多个 key 多种操作。
2.4 del
delete 删除指定的key,可以一次性删除一个或多个。时间复杂度为o(1)。返回值是成功删除key的个数。
2.5 expire
给指定的key设置过期的时间,key存活的时间超过这个指定的时间,就会被自动删除。
expire key seconds
pexpire key ms
返回1表示设置成功,返回0表示设置失败。切设定过期时间,必须是针对阴影存在的key设置的。时间复杂度也是o(1)。
2.6 ttl
time to live:查询过期时间,即查询当前key的过期时间还剩多少。返回值表示剩余的过期时间,返回负数表示该key已经不存在了。
redis 的 key 的过期策略是怎么实现的?
q:redis中可能同时存在很多key,这些key中可能有很大一部分都有过期时间,redis服务器如何知道这些key那些已经过期要被删除, 那些还没有过期。
a:redi的整体策略:
1、定期删除
每次抽取一部分进行验证过期时间,保证这个抽取检查的过程足够快。
对于每次定期删除是有时间限制,因为redis是单线程的程序,如果扫描过期key消耗的时间太多了,可能会导致正常处理请求的命令就会被阻塞。
2、惰性删除:
假如这个key已经到过期时间了,但是暂时没有删除,该key还存在,紧接着后面在访问的时候访问到了这个key,于是redis服务器触发了删除这个key的操作,同时返回一个nill
虽然有了上述两种策略的结合,但是整体的效果一般,任然会有很多过期的key没有被清理掉。redis为了对上述进行补充,还提供了一系列的内存淘汰策略。
2.7 定时器
定时器:在某个时间到达之后,执行指定的任务。
1、基于优先级队列/堆实现
在redis过期key的场景中,就可以通过“过期时间越早,就是优先级越高”,队首元素就是时间最早过期的key。定时器只要分配一个线程,让这个线程去检查对首元素,查看其是否过期,如果对首元素没有过期,则后续元素一定没有过期。另外扫描线程检查对首元素不能太频繁。可以根据当前时刻和队首元素的过期时间设置一个等待,当时间差不多时,系统在唤醒这个扫描队列。
在线程进行休眠的时候,来了一个新任务,可以在新任务添加的时候唤醒一下这个线程,重新检查一下队首元素,在根据市监察局重新调整阻塞时间。
2、基于时间轮实现的定时器
将时间划分为很多小段(划分的粒度看实际的需求),
redis并没有采取上述的这两种方案。
2.8 type
查看key对应value的数据类型。在redis中,对于不同数据类型的操作,其使用的命令是不同的。type的时间复杂度为o(1)。
3. 常用数据类型
redis底层在实现上述数据结构的时候,会在原码层面针对上述实现进行特定的优化(内部具体实现的数据结构不一定是标准的数据结构),来达到节省时间和空间的效果。
同一个数据类型,背后可能的编码实现方式是不同的, 会根据特定场最优化。
object encoding key:查看key对应的value实际的编码方式。
redis 会自动根据当前的实际情况自动适应的选择内部的编码方式.
3.1 redis 单线程模型
redis只使用一个线程处理所有的命令请求。不是说一个redis服务器进程内部真的只有一个线程,其实也有很多线程,多个线程是在处理网络io。redis服务器单线程的模型保证了当前收到的多个请求都是按照串行执行的。即多个请求同时到达redis服务器,也是要在队列中排队,等待redis服务器从队列中一个一个的取出来里面的命令在执行。
redis使用单线程模型的主要原因是redis的核心业务逻辑是短平快的,不太消耗cpu资源。
q:redis虽然是单线程模型,但是效率快的原因
a: 1、redis访问内存,数据库是访问硬盘。
2、redis的核心功能比数据库的核心功能更加简单。数据库对于数据的增删改查,都有更复杂的功能支持,这样的功能势必都要花费更多的开销,像针对插入数据,数据库中的各种约束都会使数据库做额外的工作。
3、单线程模型,避免了一些不必要的锁竞争开销。
4、处理网络 I0 的时候,使用了 epoll这样的多路复用的机制。(一个线程可以管理多个socket,针对tcp协议来说,服务器每次服务一个客户端都会给这个客户安排一个socket,一个服务器上有很多客户端,就会有很多相对用的socket,但是很多情况下客户端和服务器之间的联系不是很频繁,则大部分时间这些socket都是静默的,是不需要传输数据的)(基于上述原因,io多路复用,一个线程来处理多个socket。多路复用是操作系统提供了一套api,其内部的功能都是操作系统内核实现的,linux提供的io多路复用的三个api,select ,poll,epoll)
3.2 string类型
redis中的字符串,直接就是按照二进制数据的方式存储的(不会做任何的编码转换,存啥取啥),不仅可以存储文本数据,还可以存储整数,普通的文本字符串,json,xml,二进制数据(图片,视频,音频),redis对于string类型限制了大小,最大是512m。
set命令:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
nx:表示这个key不存在的话才进行set,如果存在的话就不在set。
xx:表示这个key不存在的话不进行set,如果存在的话就才进行set。
如果key不存在,创建新的键值对,如果这个key存在,则是让新的value覆盖旧的value,可能会改变原来的数据类型。
FLUSHALL 可以把redis 上所有的键值对都带走。
get命令:
获取当前key的value,只支持字符串类型的value,如果value是其他类型,使用get获取就会出错。
3.3 mset和mget
能一次操作多次键值对
mset:
MSET key value [key value ...]
mget:
MGET key [key ...]
这两个指令的是时间复杂度为0(n),n不是整个redis中key的数量,而是当前命令中k的数量。
3.4 setnx setex psetex
setnx:不存在才能设置,存在反而设置不了。
setex:设置当前key的过期时间。单位是s
psetex:设置当前key的过期时间。单位是ms,查询key的剩余时间要使用pttl。
3.5 加减操作
下面的操作的时间复杂度为o(1)。由于处理redis命令的时候,是单线程模型,多个客户端同时针对同一个key进行和incr的操作,不会引起“线程安全问题”。
incr:针对value+1;
incr key:此时的key对应的value必须得是整数,且在范围规定之内,返回的数值就是+1之后的值。8个字节的整数,java中的long。incr操作的key不存在的话,就默认该key的value为0.
incrby:针对value+n,不能针对字符串的value进行操作。可以加-1.
decr:针对value-1
decrby:针对value-n,key对应的value是整数。范围是8个字节的整数。
incrbyfloat:针对value+/-小数。
3.6 string字符串的操作
append:在尾部进行添加。如果key已经存在并且是⼀个string,命令会将value追加到原有string的后边。如果key不存在, 则效果等同于SET命令。返回值是追加完成之后string的⻓度,长度单位是字节。一个汉字在utf8字符集中,通常是三个字节。
APPEND KEY VALUE
getrange:返回key对应的string的⼦串,由start和end确定(左闭右闭)。可以使⽤负数表⽰倒数。-1代表 倒数第⼀个字符,-2代表倒数第⼆个,其他的与此类似。超过范围的偏移量会根据string的⻓度调整 成正确的值。redis的下标支持负数。如果字符串中是汉字,对字符串进行切割可能切出来的不是完整的汉字。(java中字符串的基本单位是字符,java的字符是占两个字节的字符)
GETRANGE key start end
在启动redis服务器的时候加上--raw,可以是服务器客户端能够自动的把二进制数据尝试翻译。xshell中ctrl+s冻结页面。ctrl+q结束冻结。
setrange:覆盖字符串的⼀部分,从指定的偏移开始。返回值是新的字符串的长度
SETRANGE key offset value
对空字符串使用该指令,就会从offset的位置上添加value,之前的位置使用oxoo填充。
strlen:获取到字符串的长度,单位是字节。 获取key对应的string的⻓度。当key存放的类似不是string时,报错、
mysql中varchar(N),N的单位是字符,mysql中的字符也是完整的汉字,这样的字符也可能是多个字节。
java中的一个char是两个字节,是基于unicode的编码方式。
java的string基于utf8编码编码方式,一个汉字三个字节,java的标准库内部,在进行上述的操作过程中,程序员一般是无法感知到编码方式的变换的。
all in all:命令⼩结
3.7 string的内部编码
字符串类型的内部编码有3种:
• int:8个字节的⻓整型。
• embstr:压缩字符串,⼩于等于39个字节的字符串。
• raw:普通字符串,⼤于39个字节的字符串。
Redis 会根据当前值的类型和⻓度动态决定使⽤哪种内部编码实现。
redis存储小数,本质上还是当字符串来存储,和整数存储的差别较大,整数直接使用int来存。小数使用字符串存储,每次进行算术运算都需要将字符串转化为小数,进行运算之后再转回字符串进行保存。
3.8 string类型的运用场景
整体思路如下:
应用服务器访问数据的时候,先访问redis,如果redis上的数据库存在,直接从redis中获取数据提交给应用服务器,不继续访问数据库了。如果redis上数据也不存在,在读取mysql,把读到的数据结果返回给应用服务器同时也把这个数据填写到redis中。
计数功能:视频的观看量。
session会话。
ps:谢谢观看。本文壁纸来源于【PV】下次旅行(又名大理风景片)-刘力菲_哔哩哔哩_bilibili。