Redis基础知识

为什么使用Redis

项目中使用Redis,主要从性能和并发。

 

当然,Redis还具备分布式锁等其他功能,如果只是为了分布式这些其他功能,完全还有其他中间件,比如动物园管理员等代替,并非部署非要使用Redis。

 

redis线程模型是单线程模型,采用的是IO多路复用技术,每个redis-CLI设定设定设定产生的多功能转换插座都会被被监听,当哪一个多功能转换插座准备好了产生事件,都会把产生事件的多功能转换插座放到一个队列里面去,通过这个队列有序,同步,每次读取一个套接字处理多功能转换插座

 

性能

如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

Redis基础知识

题外话:迅速影响的标准,根据交互效果不同,这个响应时间没有固定标准。

 

在理想状态下,我们页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。

另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验

 

并发

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。

 

这个时候,就需要使用Redis做一个缓冲操作,让请求先访问到的Redis,而不是直接访问数据库。

Redis基础知识

使用Redis有什么缺点

常见的四个问题

  • 缓存和数据库双写一致性问题

  • 缓存雪崩问题

  • 缓存击穿问题

  • 缓存的并发竞争问题

 

这四个问题,我个人觉得在项目中是常遇见的,具体解决方案,后文给出。

 

单线程的Redis为什么这么快

这个问题是对的的Redis内部机制的一个考察。根据我的面试经验,很多人都不知道的Redis是单线程工作模型。所以,这个问题还是应该要复习一下的。

 

回答:

  • 纯内存操作

  • 单线程操作,避免了频繁的上下文切换

  • 采用了非阻塞I / O多路复用机制

 

打一个比方:小曲在小号城开了一家快递店,负责同城快送服务小曲因为资金限制,雇佣了一批快递员,然后小曲发现资金不够了,只够买一辆车送快递。

 

经营方式一

客户每送来一份快递,小曲就让一个快递员盯着,然后快递员开车去送快递。

 

慢慢的小曲就发现了这种经营方式存在下述问题:

  • 几十个快递员基本上时间都花在了抢车上了,大部分快递员都处在闲置状态,谁抢到了车,谁就能去送快递。

  • 随着快递的增多,快递员也越来越多,小曲发现快递店里越来越挤,没办法雇佣新的快递员了。

  • 快递员之间的协调很花时间。

综合上述缺点,小曲痛定思痛,提出了下面的经营方式。

 

经营方式二

小曲只雇佣一个快递员。然后呢,客户送来的快递,小曲按送达地点标注好,然后依次放在一个地方。

最后,那个快递员依次的去取快递,一次拿一个,然后开着车去送快递,送好了就回来拿下一个快递。

上述两种经营方式对比,是不是明显觉得第二种,效率更高,更好呢?

 

在上述比喻中:

  • 每个快递员→每个线程

  • 每个快递→每个插座(I / O流)

  • 快递的送达地点→插座的不同状态

  • 客户送快递请求→来自客户端的请求

  • 小曲的经营方式→服务端运行的代码

  • 一辆车→CPU的核数

 

于是我们有如下结论:

  • 经营方式一就是传统的并发模型,每个I / O流(快递)都有一个新的线程(快递员)管理。

  • 经营方式二就是I / O多路复用,只有单个线程(一个快递员),通过跟踪每个I / O流的状态(每个快递的送达地点),来管理多个I / O流

 

Redis基础知识

简单来说,就是我们的Redis的客户端在操作的时候,会产生具有不同事件类型的插槽。

 

在服务端,有一段I / O多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。

 

需要说明的是,这个I / O多路复用机制,Redis还提供了选择,是Epoll,Evport,KQUEUE等多路复用函数库,大家可以自行去了解。

 

Redis数据类型与每种数据类型的使用场景

是不是觉得这个问题很基础?我也这么觉得。然而根据面试经验发现,至少百分之八十的人答不上这个问题。

 

建议,在项目中用到后,再类比记忆,体会更深,不要硬记。基本上,一个合格的程序员,五种类型都会用到。

 

这个没啥好说的,最常规的设置/获取操作,值可以是字符串也可以是数字。一般做一些复杂的计数功能的缓存。

 

哈希

这里的价值存放的是结构化的对象,比较方便的就是操作其中的某个字段。

 

我在做单点登录的时候,就是用这种数据结构存储用户信息,以Cookie编号作为重点,设置30分钟为缓存过期时间,能很好的模拟出类似会议的效果。

 

名单

使用列表的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于Redis分页功能,性能极佳,用户体验好。

 

因为设置堆放的是一堆不重复值的集合,所以可以做全局去重功能,为什么不用JVM自带的设置进行去重

 

因为我们的系统一般都是集群部署,使用JVM自带SET,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。

 

另外,就是利用交集,并集,差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能

 

排序集

排序集多了一个权重参数得分,集合中的元素能够按得分进行排列。

 

可以做排行榜应用,取TOP N操作,Sorted Set可以用来做延时任务,最后一个应用就是可以做范围查找

 

Redis的过期策略以及内存淘汰机制

这个问题相当重要,到底Redis的有没用到家,这个问题就可以看出来。

 

比如你的Redis只能存5G的数据,可是你写了10G,那会删5G的数据。怎么删的,这个问题思考过么?

 

还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么?

 

回答:Redis的采用的是定期删除+惰性删除策略。

 

为什么不用定时删除策略

定时删除,用一个定时器来负责监视重点,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。

 

如果在大并发请求下,CPU要将时间应用在处理请求,而不是删除键,因此没有采用这一策略。

 

定期删除+惰性删除是如何工作?

定期删除,Redis的默认每个100ms的检查,是否有过期的KEY,有过期KEY责要删除

惰性删除:在获取某个键时进行检查,判断是否过期,如果过期就会删除。

 

采用定期删除+惰性删除就没有其他问题了么?

注意:如果定期删除没有删除键,然后也没能及时去获取请求的KEY,也就是说惰性删除也没有生效,这样,Redis的的内存会越来越高,那么就应答采用内存淘汰机制。

 

在redis.conf中有一行配置:

#maxmemory-policy volatile-lru

 

该配置就是配内存淘汰策略的:

allkeys-LRU:当内存不足以容纳新写入数据时,在健空间中移除最近最少使用的关键。

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错应该没人用吧。

  • allkeys-LRU:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的重点推荐使用,目前项目在用这种。

  • allkeys随机:当内存不足以容纳新写入数据时,在键空间中,随机移除某个关键应该也没人用吧,你不删最少使用钥匙,去随机删。

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的Key。这种情况一般是把Redis既当缓存,又做持久化存储的时候才用。不推荐。

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个Key。依然不推荐。

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key优先移除。不推荐。

数据持久化

Redis提供了将数据定期自动持久化至硬盘的能力,包括RDB和AOF两种方案,两种方案分别有其长处和短板,可以配合起来同时运行,确保数据的稳定性。

必须使用数据持久化吗?

 

Redis的数据持久化机制是可以关闭的。如果你只把Redis作为缓存服务使用,Redis中存储的所有数据都不是该数据的主体而仅仅是同步过来的备份,那么可以关闭Redis的数据持久化机制。

但通常来说,仍然建议至少开启RDB方式的数据持久化,因为:

  • RDB方式的持久化几乎不损耗Redis本身的性能,在进行RDB持久化时,Redis主进程唯一需要做的事情就是fork出一个子进程,所有持久化工作都由子进程完成

  • Redis无论因为什么原因crash掉之后,重启时能够自动恢复到上一次RDB快照中记录的数据。这省去了手工从其他数据源(如DB)同步数据的过程,而且要比其他任何的数据恢复方式都要快

  • 现在硬盘那么大,真的不缺那一点地方

RDB

采用RDB持久方式,Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:

save [seconds] [changes]

意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存,例如

save 60 100

 

会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。

可以配置多条保存指令,让Redis的执行多级的快照保存策略。

Redis的默认开启RDB快照,默认的RDB策略如下:

节省900 1

节省300 10

节省60 10000

也可以通过BGSAVE命令手工触发RDB快照保存。

RDB的优点:

  • 对性能影响最小。如前文所述,Redis的在保存RDB快照时会叉出子进程进行,几乎不影响Redis的处理客户端请求的效率。

  • 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。

  • 使用RDB文件进行数据恢复比使用AOF要快很多。

RDB的缺点:

  • 快照是定期生成的,所以在Redis的崩溃时或多少少会丢失一部分数据。

  • 如果数据集非常大且CPU不够强(比如单核CPU),Redis的在叉子进程时可能会消耗相对较长的时间(长至1秒),影响这期间的客户端请求。

AOF

采用AOF持久方式时,Redis的会把每一个写请求都记录在一个日志文件里。在Redis的重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。

AOF默认是关闭的,如要开启,进行如下配置:

附带是的

AOF提供了三种FSYNC配置,始终/ everysec /无,通过配置项[appendfsync]指定:

  • appendfsync no:不进行fsync,将冲洗文件的时机交给OS决定,速度最快

  • appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢

  • appendfsync everysec:折中的做法,交由后台线程每秒fsync一次

 

随着AOF不断地记录写操作日志,必定会出现一些无用的日志,例如某个时间点执行了命令SET key1“abc”,在之后某个时间点又执行了SET key1“bcd”,那么第一条命令很显然是没有用的。大量的无用日志会让AOF文件过大,也会让数据恢复的时间过长。

所以Redis提供了AOF重写功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。

AOF重写可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

上面两行配置的含义是,Redis在每次AOF重写时,会记录完成重写后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF重写。同时如果增长的大小没有达到64MB,则不会进行重写。

AOF的优点:

  • 最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据。

  • AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用Redis的检查,AOF工具轻松修复。

  • AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有改写,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。

AOF的缺点:

  • AOF文件通常比RDB文件更大

  • 性能消耗比RDB高

  • 数据恢复速度比RDB慢