针对Redis出现的面试题汇总

477 阅读14分钟

一.面试准备

二.Redis缓存

2.1 缓存穿透

image.png 缓存穿透:指的是 当在数据库里查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库(DB),可能会导致DB挂掉

解决方案一:缓存空数据,查询返回的数据为空,仍然把这个空结果进行缓存

比如:查询一个id为1的数据,它的value为null,此时依然把null缓存。

这种解决方法的优点是很简单就可以实现,但当你有大量的value都是null,缓存的压力就会很大,而且有可能会发生数据不一致的问题。

解决方案二:布隆过滤器

布隆过滤器:主要用于检索一个元素是否存在一个集合中,它的底层主要是先去初始化一个比较大的数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后,经过三次hash计算,根据数组长度找到数据的下标然后将数组原来的0改成1,这样的话,三个数组的位置就能标明一个key的存在.查找的过程也是一样的。

但是会有误判问题出现,可以设置这个误判,让它大概不会超过5%

image.png 优点:内存占用小,没有多余key 缺点:实现复杂,存在误判

2.2 缓存击穿

缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量并发请求过来,这些并发的请求可能会瞬间把DB压垮

image.png

解决方案一:互斥锁

image.png 当缓存失效时,不立刻去load db,先使用Redis的setnx去设置一个互斥锁,当操作成功返回时再进行load db的操作并回设缓存,否则重试get缓存的方法。

特点:强一致,性能差

解决方案二:逻辑过期

image.png

  1. 在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间.
  2. 当查询的时候,从redis中取出数据后判断时间是否过期
  3. 如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,但这个数据不是最新数据.

特点:高可用,性能优,但是数据不能保证绝对一致

2.3 缓存雪崩

缓存雪崩:指的是同一时间段内大量的缓存key同时失效或者Redis服务宕机 ,导致大量请求到达数据库,带来巨大压力.(Key同时失效主要原因因为大量的key设置了相同的过期时间 )

解决方案一:增加随机失效时间

解决方案二:利用Redis集群提高服务的可用性(解决Redis宕机 哨兵模式,集群模式)

解决方案三:给缓存业务添加降级限流策略(ngxin或Spring cloud gateway)

**解决方案四:给业务添加多级缓存(Guava caffeine) **

2.4 缓存-双写一致性

双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存的数据库和数据要保持一致.

延迟双删不可以解决同步问题(有脏读风险)

面试官:redis作为缓存,mysql的数据如何与redis进行同步呢?

答:一定要设置前提,先介绍自己的业务背景,如果业务的一致性要求高,是一种方案,如果业务允许延迟一致,则是另一种方案

解决方案一:业务要求强一致性:采用redisson提供的读写锁

共享锁:读锁readLock,加锁之后,其他线程可共享读操作,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。

排他锁:独占锁writeLock,加锁之后,阻塞其他线程读写操作,底层使用setnx,保证了同时只能有一个线程操作的方法。

解决方案二:业务允许延迟一致:利用canal中间件

利用canal中间件,不需要修改业务代码,伪装mysql的一个节点,canal通过读取binlog数据更新缓存。

2.5 持久化

Redis作为缓存,数据持久化方式分为RDB和AOF

RDB也被叫做Redis数据快照。把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据

AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。

AOF在Redis中是默认关闭的,需要修改redis.conf配置文件来开启AOF:

image.png AOF的命令记录的频率也可以通过redis.conf文件来配:

image.png RDB执行原理:

image.png

RDB与AOF对比

image.png

2.6 数据过期策略

面试官:假如Redis的key过期之后,会立刻删除吗?

答:Redis设置数据的有效时间,数据过期之后,就需要将数据从内存中删掉,可以按照不同的规则进行删除,这种删除规则就被称为数据的删除策略即数据的过期策略

分为惰性删除和定期删除

惰性删除:设置该key过期时间后,不对该key进行任何操作,当需要该key时,检查它是否过期,如果过期,则删掉,如果未过期,返回该key.

优点:对CPU友好,使用该key的时候才会检查,对于很多用不到的key不用浪费时间进行检查

缺点:对内存不友好,如果一个key很久没有使用,就会一直保存在内存里,内存永远不会被释放。

定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key.

定期清理的两种模式: SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 hz 选项来调整这个次数

FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对cpu的影响.另外定期删除,也能释放过期key占用的内存

缺点:难以确定删除操作的执行时长和频率

Redis的过期删除策略:惰性删除+定期删除两种策略进行配合使用。

2.7 数据淘汰策略

问:假如缓存过多,内存是有限的,内存被占满了怎么办?(Redis的数据淘汰策略)

当Redis中的内存不够用时,此时再向Redis中添加新的key,那么Redis就会按照某种规则将内存中的数据删掉,这种数据的删除规则被称为数据的淘汰策略

Redis支持8种不同的淘汰策略:

image.png

LRU和LFU较为主要,面试中需要描述出来.

数据淘汰策略建议:

  1. 优先使用allkeys-lru,把最近最常访问的数据留在缓存中。如果业务有明显冷热数据区分,建议使用
  2. 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,对全体key进行随机淘汰
  3. 如果业务中有置顶的需求,可以使用volatile-lru策略,对需要置顶的数据不设置TTL,这些数据就一直不会被删除,只会淘汰其他设置过期时间的数据
  4. 如果业务中有短时高频访问的数据,使用allkeys-lfu或者volatile-lfu策略

关于数据淘汰策略其他的面试题

问:数据库中有1000万数据,Redis只能缓存20万数据,如何保证Redis中的数据都是热点数据

答:数据淘汰策略使用allkeys-lru策略,挑选最近最少使用的数据进行淘汰,剩下的都是热点数据

问:Redis中的内存用完了会发生什么?

答:主要看给Redis设置的什么数据淘汰策略,如果数据淘汰策略是默认的noeviction,那么会报错

三.Redis分布式锁

3.1 Redis分布式锁的使用场景

集群情况下的定时任务、抢单、幂等性

抢券场景实现:

image.png 但是这种方法是存在问题的;如果恰巧此时库存就剩一个了,此时两个线程同时读取库存,线程1读取到之后,进行扣减库存,此时库存已经为0,但线程2之前查询到库存不为0,线程2也会进行扣减,此时库存为-1,这种现象叫超卖

可以使用分布式锁来解决这个问题

image.png

3.2 Redis分布式锁的实现原理

面试官:redis分布式锁,是如何实现的?

先按照自己简历上的业务进行描述分布式锁使用的场景

当时使用的是redisson实现的分布式锁,底层是setnx和lua脚本 (lua脚本保证原子性)

面试官:Redisson实现分布式锁如何合理控制锁的有效时长

在redisson的分布式锁重,提供了一个WatchDog(看门狗),一个线程获取锁成功后,WatchDog会给持有锁的线程续期(默认是每隔10秒续期一次)

面试官:Redisson实现的锁,可以重入吗?

可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用的hash结构,来存储线程信息和重入次数.

面试官:Redisson锁能解决主从数据一致问题吗?

不能解决,但是可以使用redisson提供的红锁来解决,但是一旦使用红锁,性能会大大下降,如果业务中非要保持数据的强一致性,建议使用zookeeper实现的分布式锁.

3.3 Redis集群方案

Redis提供的集群方案总共有三种

  • 主从复制
  • 哨兵模式
  • 分片集群

主从复制(主从同步)

问:介绍一下redis的主从同步

答:单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就要搭建主从集群,实现读写分离.一般都是一主多从,主节点负责写数据,从节点负责读数据.

问:主从同步的流程

答:image.png

image.png

image.png

Redis集群的哨兵模式

哨兵模式

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵结构和作用如下:

image.png

哨兵是如何实现监控服务状态呢?

Sentinel基于心跳机制监测服务状态,每隔一秒向集群的每个实例发送ping命令:

  • 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
  • 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好设置成超过Sentinel实例数量的一半。

哨兵选主规则:

  • 首先判断主与从节点断开时间长短,如超过指定值(即从节点和主节点断开时间过长)就不选择该从节点为主节点,因为断开时间太长丢失数据太多
  • 然后判断从节点的slave-priority值,越小优先级越高
  • 如果slave-prority一样,则判断slave节点的offset值,越大优先级越高
  • 最后判断slave节点的运行id大小,越小优先级越高.

面试题:

问:怎么保证Redis的高并发高可用

详细回答:首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式 可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动 故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。 当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的 服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用

问:你们使用redis是单点还是集群,哪种集群?

答:我们当时使用的是主从(1主1从)加哨兵。一般单节点不超 过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节 点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务

集群脑裂

集群脑裂:由于redis master节点和redis salve节点和sentinel处于不同的网络分区(或者直接说主节点挂了),使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve master,这样就存在了两个master,就像大脑分裂了一样

解决办法:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失


问:redis集群脑裂,该怎么解决呢?

答:有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redismaster节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没 有能够心跳感知到master,所以通过选举的方式提升了一个salve为master, 这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在 old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会 将old master降为salve,这时再从新master同步数据,这会导致old master中 的大量数据丢失。 关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的salve 节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主 从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失

分片集群

image.png

分片集群的作用

分片集群主要解决的是,海量数据存储的问题,集群中有多个 master,每个master保存不同数据,并且还可以给每个master设置多个slave 节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点

分片集群的读写

Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放 置哪个槽,通过槽找到对应的节点进行存储。取值的逻辑是一样的

3.4 redis是单线程,但为什么那么快

  1. 完全基于内存的,C语言编写
  2. 采用单线程,避免不必要的上下文切换可竞争条件
  3. 使用多路I/O复用模型,非阻塞IO

例如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞