Redis相关底层面试题
Redis相关底层面试题
一、介绍
Redis是一个开源的高性能键值对存储系统,具有快速、灵活和可扩展的特性。它是一个基于内存的数据结构存储系统,可以用作数据库、缓存和消息代理。Redis支持多种类型的数据结构,如字符串(strings),散列(hashes),列表(lists),集合(sets)等。
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。同时,Redis还支持数据的备份,即master-slave模式的数据备份。
Redis的应用场景非常广泛。虽然Redis是一个key-value的内存数据库,但在实际场景中,Redis经常被作为缓存来使用,如面对数据高并发的读写、海量数据的读写等。具体而言,分布式缓存Redis可用于以下场景:
-
页面缓存:Redis可将Web页面的内容片段,包括HTML,CSS和图片等静态数据,缓存到Redis实例,提高网站的访问性能。比如在电商类应用中,热销商品展示、秒杀推荐等数据面临高并发读的压力,分布式缓存Redis的高并发及灵活扩展,可轻松支持此类应用 。
-
消息队列:Redis可以作为消息代理,将消息存储在Redis中,然后由消费者来消费这些消息。这种方式可以很好地解决异步处理问题。
-
排行榜:Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。
-
计数器:Redis提供了INCRBY命令来实现原子性的递增,因此可以运用于高并发的秒杀活动、分布式序列号的生成等业务场景。
下面提供了redis
的一些面试题,主要是记录,并在以后的面试中不再感到迷茫。
二、面试题
1)持久化策略
关于redis
持久化的策略,分为两种一种是RDB
、AOF
,当然可以结合使用
在以前就讲述过
Redis的持久化策略RDB和AOF | 半月无霜 (banmoon.top)
2)redis是单线程还是多线程的
在redis6.0
以前,单线程是指网络IO
和键值对读写是由一个线程来完成。
在redis6.0
以后,引入了多线程是为了处理网络IO
,而键值对的读写还是单线程来进行执行的。
也就是说redis
的缓存读写一定是单线程,网络请求IO
可能是单线程的。除此以外,像其他的第一节说的持久化,以及主从数据同步等,是由其他额外的线程来完成的。
3)redis的过期删除策略
在使用中,我们可以设置redis
缓存的过期时间。
而redis
的过期删除策略就是指,当缓存过期后,redis
应当如何处理。
一般来说,清除过期的缓存有三种
-
惰性过期:只有当访问一个
key
的时候,会判断这个key
有没有过期,如果过期了,则进行清除。- 这种策略可以最大的节省
CPU
的资源,对内存不太友好。极端情况下可能出现一大堆过期的key
一直存在于内存中,正是因为没有再次访问这些key
,导致他们没有清除
- 这种策略可以最大的节省
-
定期过期:每隔一段时间,
redis
会扫描数据库的expires
字典中一定数量的key
,并清除其中已过期的key
。- 注意是一定数量的
key
,是可以使CPU
和memory
达到最优平衡的效果
- 注意是一定数量的
-
定时过期(redis不用):这种就是对每一个
key
来启动一个定时器,到时间了就进行清除。- 由于
redis
中存在大量的key
,这种方案肯定是不能用的,随时导致CPU
到达100%
- 由于
综上所述,redis
的最佳使用策略有两种,就是惰性过期和定期过期。并且在redis
中,这两种过期策略同时在使用。
4)缓存淘汰算策略哪些
在上面一节讲述了,redis
的过期删除策略,那么这一节则是redis
的淘汰策略,这两种是不一样的。
-
过期删除策略:指的是缓存
key
过期后,redis
对其删除的策略。 -
缓存淘汰策略:指的是
redis
缓存使用的内存超过了maxmemory
的设置,触发的一种策略。它会清除缓存,使其使用的内存小于设定的maxmemory
。
那么缓存淘汰策略一共有下面八种==(在redis4.0
前一共有六种,在之后又添加了两种,现在版本一共八种)==
-
不处理:
- noeviction:不会剔除任何数据,拒绝所有写入操作,返回
(error)OOM command not allowed when used memory
。此时redis
只响应读操作
- noeviction:不会剔除任何数据,拒绝所有写入操作,返回
-
在淘汰时,仅针对设置过期时间的缓存做处理
2. volatile-ttl:根据过期时间的大小进行排序,越快过期的越先被删除(就算缓存还没有过期)
3. volatile-random:随机进行删除
4. volatile-lru:根据LRU
算法进行筛选缓存进行删除
5. volatile-lfu:根据LFU
算法进行筛选缓存进行删除 -
在淘汰时,针对所有的缓存
6. allkey-random:随机进行删除
7. allkkey-lru:根据LRU
算法进行筛选缓存进行删除
8. allkey-lfu:根据LFU
算法进行筛选缓存进行删除
从上面来看,有两个算法
LRU
和LFU
,分别是什么
- LRU(Least Recently Used,最小的最近使用):根据访问时间做排序,淘汰掉访问时间越远的缓存
- LFU(least Frequently Used,最小的频率使用):根据访问次数做排序,淘汰掉访问次数最小的缓存
5)主从、哨兵、集群的优缺点
5.1)主从
主从模式是redis
最基本的集群模式,它实现了数据的复制和读写分离。在主从模式中,有一个主服务器master
和多个从服务器slave
。主服务器负责处理写操作,并将数据变化同步给从服务器。从服务器一般只负责处理读操作,并接收主服务器的数据更新。一个主服务器可以有多个从服务器,但一个从服务器只能有一个主服务器。
-
优点:
- 提提高了数据的可靠性,即使主服务器出现故障,也可以通过从服务器恢复数据
- 分担了主服务器的压力,提高了数据的吞吐量和响应速度
-
缺点:
- 不具备自动容错和恢复的功能,当主节点宕机,需要手动切换从节点进行顶替
- 可能出现数据不一致的情况,写操作都在
master
,读操作在slave
节点。由于同步需要时间,就会造成读取数据不一致的情况。 master
节点的压力会很大,由于写操作全部在master
,一旦写入请求过多,会导致master
节点压力增大,从而导致宕机- 不支持在线扩容,在集群容量达到上限时,需要停止服务才能增加或减少节点
如下图
5.2)哨兵
哨兵模式是在主从模式的基础上增加了哨兵sentinel
进程来实现高可用性。哨兵是一个独立的进程,它可以监控多个redis
服务器的运行状态,包括主服务器和从服务器。哨兵模式的作用有
-
通过发送命令,让
redis
服务器返回监控其运行状态,包括主服务器和从服务器 -
当哨兵监测到主服务器宕机,会自动将从服务器切换为主服务器,并通过发布订阅模式通知其他从服务器和客户端,完成故障转移
-
为了避免单点故障,可以使用多个哨兵进行监控。各个哨兵之间也会相互监控,形成一个哨兵集群
-
优点
- 实现了高可用,当主节点出现宕机的情况,可以通知进行主从切换,无需人工干预
- 支持了动态配置,当主从变化,哨兵会实现自动更新配置信息,并通知其他节点
- 提供了事件通知机制,当监控的
redis
实例发生故障或恢复时,哨兵会执行指定的脚本或发送邮件等方式通知相关人员
-
缺点
- 缓存丢失,同步需要时间,
slave
节点还没来得及同步缓存数据,master
就宕机了。此时sentinel
选举一个slave
节点变成master
,原先的master
恢复后变成slave
,会去新master
同步数据,导致最近的一批缓存数据丢失 - 缓存不一致,和主从结构一样,同步需要时间,可能会出现缓存不一致的情况
master
节点的压力会很大,和主从结构一样,哨兵并没有解决master
节点请求集中的问题,还是可能会造成master
节点压力过大,导致宕机的问题
- 缓存丢失,同步需要时间,
如下图
5.3)集群
集群模式是Redis最高级的集群模式,它实现了数据的分片和负载均衡。在集群模式中,没有明确的主从关系,而是由多个相互协作的节点组成一个集群。每个节点都负责一部分数据,并且可以处理读写操作。当某个节点出现故障时,集群会自动进行数据迁移和故障转移。
引入和hash slot
哈希槽,一共16384
个,每一个写入的缓存key
都会计算其hash
值,从而决定写入到哪个集群节点
-
优点
- 实现了分片存储,突破了
redis
单节点的限制,提高了系统扩展性 - 实现了负载均衡,写请求压力不再是单节点,提高了系统的性能和吞吐量
- 实现了高可用,当某个节点出现故障时,集群会自动进行数据迁移和故障转移,无需人工干预
- 实现了分片存储,突破了
-
缺点
- 不支持多键操作,多键可能落在不同的集群节点上,故不支持操作
- 不支持事务操作,因为要保证连接要在同一个节点上,而集群会导致连接到不同的节点,从而导致事务失效
- 不支持数据库的切换操作,集群模式只能使用数据库
0
6)简述主从同步机制
在redis
中,主从同步主要经过了以下几个流程
上面提到增量复制和全量复制,大家应该都知道是什么意思
-
全量复制:
master
节点会通过bgsave
命令启动子线程进行RDB
的持久化master
节点通过网络IO
将文件发送给slave
节点,这会对消耗网络带宽slave
节点会清空原本的旧数据,将RDB
快照文件进行载入。注意了,在载入的过程中,整个slave
节点是阻塞的,无法响应客户端的命令
-
增量复制:
offset
:执行同步的两方节点,分别会维护一个复制偏移量offset
runid
:每个redis
实例节点,都有其运行ID
。这个ID
由节点在启动运行时自动生成。master
节点会将自己的runid
发送给salve
节点,salve
节点会将master
节点的runid
保存起来。当master
节点发生变更后,salve
节点请求复制时,会携带这个runid
,新的master
节点会进行判断。- 如果
salve
发送过来的runid
与自己的不同,那么只能进行全量复制 - 如果
salve
发送过来的runid
与自己的相同,那么就尝试进行增量复制**(进入第三点再进行判断,最终决定是否增量复制)**
- 如果
- 复制积压缓冲区:在
master
节点中,维护了一个长度固定、先进先出的队列,作为复制积压缓冲区。当主从节点offset
的差距过大,超出了缓冲区大小时,那么slave
节点将无法进行增量复制,只能进行全量复制
7)缓存雪崩、缓存击穿、缓存穿透是什么,怎么避免
7.1)缓存雪崩
当雪崩来临,没有一片雪花是无辜的。当然缓存雪崩也一样,没有一个缓存key
是无辜的。
它指的是,大面积的缓存key
同时过期,导致请求直接略过的缓存,打击到数据库上。故此称为缓存雪崩。
针对上面的现象,我们可以这样进行解决
对于缓存key
的过期时间,我们不要设置成固定统一的,而是在其基础上添加一定的随机值,从而避免大面积过期。
7.2)缓存击穿
缓存击穿,指的是当一个缓存key
存在时,它能抗住大量的请求。一旦缓存key
过期,大量的请求直接击穿到数据库,导致数据库压力瞬间增加。
从上面来看,缓存key
过期才会导致,大量的请求击穿到数据库。一般业务上来讲,这些数据都是高热点的数据,因为业务正常,且请求非常高。
针对上面的现象,我们可以这样进行解决
-
热点缓存永不过期,后台一个线程自动更新缓存
-
添加互斥锁,对读写缓存的代码进行加锁,那么最多也只有一个请求能到数据库
7.3)缓存穿透
当访问一个不存在的缓存key
时,redis
根本拦不住,缓存都没有,所以这些请求都会到数据库。一旦这些请求是恶意大量的,就会使得数据库的压力增加。
比如说我想获取ID=-1000
的用户信息,1s
中请求了3k
次,由于缓存没有拦下请求,就相当于进行了3k
次的数据库查询。
针对上面的现象,我们可以这样进行解决
-
对入参进行校验,对于不合理的入参直接进行过滤
-
如果数据库中查询出不存在的值,我们可以再缓存中设置为
null
,当然过期时间可以给短一下,避免恶意请求 -
采用布隆过滤器
- 当一个
ID
在过滤器中不存在,那么它一定不存在。直接过滤掉即可 - 当一个
ID
在过滤器中存在,那么它可能存在,可能不存在。那么再去读取缓存,读取数据库即可。
- 当一个
-
添加互斥锁,对读写缓存的代码进行加锁,那么最多也只有一个请求能到数据库
三、最后
如果有什么redis
相关的面试题,我都会在此文章中更新。
我是半月,你我一同共勉!!!