直击Redis面试题

之前的博客已经对Redis的大致内容做了一个总结,但依然有很多没有提及的部分。在这里将做面试题总结,将那些没有提到的内容做一个补充,对已有内容总结成一个面试时可用的腹稿。

1.关于项目中,哪些场景使用了redis呢?

  • 缓存:缓存三兄弟(穿透、击穿、雪崩)、双写一致、持久化、数据过期策略,数据淘汰策略

  • 分布式锁setnx、redisson

  • 消息队列、延迟队列何种数据类型

缓存三剑客

2.什么是缓存穿透?怎么解决?

  • 缓存穿透是指查询一个一定不存在的数据,由于DB查不到数据因此不写入缓存,这将导致每次都查到DB。

  • 解决方案的话:

    • 缓存空值(如null)并设置短期 过期时间(TTL)

    • 布隆过滤器拦截

    • 在 API 入口处判断求请求参数是否合理

3.展开讲一下布隆过滤器

使用过程:使用Redisson实现,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在:将数据源中所有有效 Key(如用户 ID、商品 ID)提前存入布隆过滤器,请求先过过滤器,无效 Key 直接拦截(注意:布隆过滤器有小概率误判,需配合空值缓存兜底)。我用的是Redisson实现

原理:先初始化一个比较大的数组,里面存放的是二进制0或1表示存在与否。用多个哈希函数计算把元素映射到位数组上,通过位置是否全为1来判断存在性,以空间换取查询速度。

布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%。其实这个误判是必然存在的,要不就得增加数组的长度。5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。

4.什么是缓存击穿?怎么解决?

  • 某个热点数据刚好过期,此时大量请求同时访问该 key,导致大量请求穿透缓存,打爆数据库

  • 解决办法:

    • 互斥锁(分布式锁):当缓存未命中,先尝试加锁,只有一个线程能去数据库加载,其它线程等待。锁释放后再从缓存中读取。(如用 Redis 的SET NX实现锁);

    • 逻辑过期 + 异步更新: 存缓存时存一个TTL,查缓存时判断过期时间,如果未过期直接返回;如果已过期,先尝试加锁,成功则异步刷新缓存并返回旧数据,失败则直接返回旧数据等待后台线程更新。这样避免了缓存击穿,同时相比Redis TTL更灵活可控。

5.什么是缓存雪崩?怎么解决?

在同一时间大量缓存key同时失效或者Redis服务宕机,导致所有请求都打到数据库,引起数据库压力剧增,甚至崩溃,系统整体不可用。

解决方案:

  • 将缓存失效时间随机打散: 在失效时间基础上增加一个随机值或者让过期时间错开分布逐步过期不集中失效

  • 使用逻辑过期 + 异步缓存重建:手动控制过期时间, 不让热点 key 自行过期,如果判断逻辑时间已过期,先返回旧数据,再异步重建缓存(防止请求打爆数据库),从而避免因为缓存失效造成的缓存雪崩

    • 👉 这是黑马点评中解决热点缓存一致性的关键方案。

双写一致性——互斥锁

6.redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

需要让数据库与redis高度保持一致,因为要求时效性比较高。

我采用的读写锁保证的强一致性。我使用的是Redisson实现的读写锁。在读的时候添加共享锁,可以保证读读不互斥、读写互斥。当我们更新数据的时候,添加排他锁。它是读写、读读都互斥,这样就能保证在写数据的同时,是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是,读方法和写方法上需要使用同一把锁才行。

7.那这个排他锁是如何保证读写、读读互斥的呢?

其实排他锁底层使用的也是SETNXSETNX(SET if Not eXists) 原子操作,加锁时要带NX参数(0或1),保证同一时间只有一个线程能获取锁;。

8.你听说过延时双删吗?为什么不用它呢?

延时双删指的是:

  1. 先删一次 Redis 缓存;

  2. 更新 MySQL;

  3. 延迟一小段时间,再删一次缓存(防止并发下旧数据回写到缓存)。

延时双删本质是用延迟第二次删除来降低并发下脏数据回写的概率,但它不能彻底保证一致性,延迟时间不好选,过短无效、过长影响性能;而且高并发下依然可能出现竞争导致脏数据,延迟删除还会引入额外的复杂度和不确定性。所以生产环境更多用异步消息、订阅binlog或逻辑过期等方案来保证最终一致性。

redis持久化

9.redis做为缓存,数据的持久化是怎么做的?

在Redis中提供了两种数据持久化的方式: RDB , AOF。

  • RDB(快照式):Redis 会在某些时机fork 出一个子进程,当前内存中的数据生成快照,写入 .rdb 文件,RDB 快照记录的是实际数据。

  • AOF(追加式) :将数据库操作命令,以追加的方式记录到 AOF 文件中,恢复时重写。

10.这两种方式,哪种恢复的比较快呢?

RDB快

  • RDB因为是二进制文件,保存时体积也比较小,所以它恢复得比较快。但它有可能会丢数据。

  • 我们通常在项目中也会使用AOF来恢复数据。虽然AOF恢复的速度慢一些,但它丢数据的风险要小很多。在AOF文件中可以设置刷盘策略。我们当时设置的就是每秒批量写入一次命令。

过期策略

11.Redis的数据过期策略有哪些?

在redis中提供了两种数据过期删除策略。

  • 惰性删除:只有当请求查询某个 key 时,才检查它是否过期,过期则删除。

    • 优点是节省 CPU,缺点是可能内存中堆积大量过期 key。

  • 定期删除:Redis 每隔一段时间(默认 100ms)随机抽查部分过期 key 并删除。

    • 优点是平衡 CPU 和内存,缺点是可能有少量过期 key 未及时删除。

淘汰策略

12.Redis的数据淘汰策略有哪些?

这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足时直接报错。

这个可以在redis的配置文件中进行设置。里面有两个非常重要的概念:

  • 一个是LRU,淘汰「最近最少使用」的 key(适合热点数据场景,如商品详情)。

  • 另外一个是LFU,淘汰「最近访问频率最低」的 key(适合长期有访问但频率不均的场景,如用户登录记录)。

策略名

含义

说明

noeviction

不删除,直接报错

默认策略,写入失败

allkeys-lru

所有键中淘汰最近最少使用

智能

allkeys-lfu

所有键中淘汰使用频率最低的

更先进的推荐策略

allkeys-random

所有键中随机淘汰

简单快速

volatile-lru

仅 TTL 键中 LRU 淘汰

有 TTL 控制

volatile-lfu

仅 TTL 键中 LFU 淘汰

类似上面

volatile-random

仅 TTL 键中随机淘汰

比较极端

volatile-ttl

淘汰 TTL 时间最短的

优先过期键

13.数据库有1000万数据,Redis只能缓存20w数据。如何保证Redis中的数据都是热点数据?

嗯,我想一下,可以使用allkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略。那留下来的都是经常访问的热点数据。

14.Redis的内存用完了会发生什么?

不同的redis数据淘汰策略情况式不一样的。默认是noeviction,,redis内存用完以后则直接报错。我当时设置的是allkeys-lru策略,把最近最常访问的数据留在缓存中。

分布式锁

15.Redis分布式锁如何实现?

SET NX PX 实现加锁,NX 保证只有 key 不存在时才能成功(互斥),UUID 标识锁归属,Lua 脚本原子解锁,并控制过期时间防死锁,必要时配合自动续期。

16.那你如何控制Redis实现分布式锁的有效时长呢?

redis的SETNX指令不好控制这个问题。我当时采用的是redis的Redisson框架实现的。

在Redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间。当锁住的一个业务还没有执行完成的时候,Redisson会引入一个看门狗机制。就是说,每隔一段时间就检查当前业务是否还持有锁。如果持有,就增加加锁的持有时间。当业务执行完成之后,需要使用释放锁就可以了。还有一个好处就是,在高并发下,一个业务有可能会执行很快。客户1持有锁的时候,客户2来了以后并不会马上被拒绝。它会自旋不断尝试获取锁。如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。

触发看门狗机制非常简单,当你调用 Redisson 的 lock() 方法并不传入过期时间时,会自动触发看门狗机制

触发该机制Redisson 会:

  1. 默认加锁 30 秒

  2. 创建一个后台守护线程(看门狗线程)

  3. 每隔 10 秒 自动将锁的过期时间 重置为 30 秒

  4. 持续续期,直到你 unlock() 释放锁。

17.Redisson实现的分布式锁是可重入的吗?

是可以重入的。这样做是为了避免死锁的产生。Redisson 提供的RLock基于 Redis 的 Hash 结构 实现的可重入锁

那么底层实现原理就可以通过判断次数来解决重入问题:

🔁 加锁流程:

  1. 客户端生成唯一标识(UUID + 线程ID);

  2. 使用 Lua 脚本判断锁是否存在;

  3. 如果是同一个线程,则只 自增重入次数,并刷新过期时间;

  4. 否则尝试抢锁。

🧹 解锁流程:

  1. 判断锁是否是当前线程加的;

  2. 如果是,减少重入次数;

  3. 如果次数为 0,删除 key,释放锁

  4. 否则只是更新剩余次数。

18.Redisson实现的分布式锁能解决主从一致性的问题吗?

并没有完全解决,项目中提到的办法是使用Redisson 的 multiLock(多锁)机制,它通过在多个独立 Redis 节点上加锁,绕开主从同步问题,避免因主从延迟造成“锁丢失”或“误释放”。

这个是不能的。比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时如果当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。

我们可以利用Redisson提供的红锁来解决这个问题,RedLock 使用多个 完全独立的 Redis 实例(推荐 5 个) 来保证分布式锁的一致性。它并非依赖 Redis 的主从架构,而是多个主节点构成的“集群”。

  1. 客户端使用唯一值尝试依次向多个 Redis 实例发起加锁操作同时客户端获取【当前时间T1】

  2. 如果客户端从大多数(如 5 个节点中至少 3 个)以上Redis实例加锁成功,则再次获取【当前时间戳T2】, 如果 T2 - T1 < 锁的过期时间,此时,认为客户端加锁成功,否则加锁失败

  3. 加锁成功,去操作共享资源

  4. 加锁失败,向【全部节点】发起释放锁请求

但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变得非常低,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁。

19.如果业务非要保证数据的强一致性,这个该怎么解决呢?

Redis本身就是支持高可用的,要做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用ZooKeeper实现的分布式锁,它是可以保证强一致性的。

redis集群

20.Redis集群有哪些方案,知道吗?

在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群。

21.那你来介绍一下主从同步

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一个主节点(Master)处理操作,多个从节点(Slave)通过复制主节点数据承担读操作,数据异步同步。从节点通过复制保持与主节点一致。

22.能说一下,主从同步数据的流程吗?

实现过程:

1. 初始同步(全量复制)

当从节点第一次连接主节点时,会进行全量复制:

  1. 从节点发送 PSYNC 请求;

  2. 主节点执行 bgsave 创建 RDB 快照,同时将写命令缓存在复制缓冲区中;

  3. 主节点将 RDB 发送给从节点;

  4. 从节点加载 RDB 文件;

  5. 主节点将缓冲区中的写命令发送给从节点执行,完成增量补偿。

2. 增量同步

  • 当从节点已经复制过一次,只是短暂断连时,会尝试继续同步主节点的复制积压缓冲区中的写操作,避免全量复制;

  • 使用复制偏移量(replication offset)和主节点的 backlog buffer 实现。

23.怎么保证Redis的高并发高可用?

首先可以搭建主从集群,再加上使用Redis中的哨兵模式,哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。哨兵主要负责三件事情:监控、选主、通知

  • ✅ 监控节点状态(哨兵持续向主节点和从节点发送 PING命令 ,无回应PONG则出事了)

  • ✅ 主节点故障自动转移 ,自动将某个从节点提升为主节点(Sentinel 会优先选择 优先级数值最小的从节点(priority 越小,优先级越高))

  • ✅ 通知客户端更新连接地址(通过 Redis 的PUB/SUB(发布者/订阅者)机制来实现

24.你们使用Redis是单点还是集群,哪种集群?

候选人:我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。

尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用Lua脚本和事务。

25.Redis集群脑裂,该怎么解决呢?

什么是脑裂
主节点因网络分区与大多数从节点失联,但它仍对外提供写服务;与此同时,集群选出了新的主节点,也对外写数据,导致数据不一致甚至丢失。

这个在项目中很少见,不过脑裂的问题是这样的,我们现在用的是Redis的哨兵模式集群的。

解决思路

  1. min-replicas-to-write

    • 设置主节点必须有至少 N 个从节点同步确认才能执行写操作,减少孤立主节点写入的可能。

  2. min-replicas-max-lag

    • 限制主从数据延迟时间,超过延迟时间就拒绝写,避免长时间滞后的孤立主节点继续写数据。

26.Redis的分片集群有什么作用?

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

27.Redis分片集群中数据是怎么存储和读取的?

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

28.Redis是单线程的,但是为什么还那么快?

嗯,这个有几个原因吧~~~

  1. 完全基于内存的,避免了磁盘 I/O 瓶颈。。

  2. 采用单线程,避免不必要的上下文切换和竞争条件。

  3. 使用多路I/O复用模型,非阻塞IO。

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

I/O多路复用

29.能解释一下I/O多路复用模型?


I/O多路复用是指利用单个线程来同时监听多个Socket(文件描述符,并且在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源,单线程就能高效处理成百上千并发连接,常见实现有 select、poll、epoll,Redis 就是基于 epoll 实现的。

📌 工作原理(简化)

  1. 把多个 socket 注册到内核(如 epoll)。

  2. 内核监视这些 socket。

  3. 某个 socket 有事件(可读/可写/异常)时,内核一次性返回所有就绪的 socket 列表。

  4. 应用程序逐个处理这些就绪的 socket。

30.为什么需要 I/O多路

  • 如果用阻塞 I/O:一个连接在等待数据时,线程会被阻塞,浪费 CPU

  • 如果用多线程/多进程:每个连接一个线程/进程,开销大,切换频繁

  • I/O 多路复用单线程就能管理成百上千连接,不用开很多线程,也不会阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值