Redis相关底层面试题

一、介绍

Redis是一个开源的高性能键值对存储系统,具有快速、灵活和可扩展的特性。它是一个基于内存的数据结构存储系统,可以用作数据库、缓存和消息代理。Redis支持多种类型的数据结构,如字符串(strings),散列(hashes),列表(lists),集合(sets)等。

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。同时,Redis还支持数据的备份,即master-slave模式的数据备份。

Redis的应用场景非常广泛。虽然Redis是一个key-value的内存数据库,但在实际场景中,Redis经常被作为缓存来使用,如面对数据高并发的读写、海量数据的读写等。具体而言,分布式缓存Redis可用于以下场景:

  1. 页面缓存:Redis可将Web页面的内容片段,包括HTML,CSS和图片等静态数据,缓存到Redis实例,提高网站的访问性能。比如在电商类应用中,热销商品展示、秒杀推荐等数据面临高并发读的压力,分布式缓存Redis的高并发及灵活扩展,可轻松支持此类应用 。

  2. 消息队列:Redis可以作为消息代理,将消息存储在Redis中,然后由消费者来消费这些消息。这种方式可以很好地解决异步处理问题。

  3. 排行榜:Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

  4. 计数器:Redis提供了INCRBY命令来实现原子性的递增,因此可以运用于高并发的秒杀活动、分布式序列号的生成等业务场景。


下面提供了redis的一些面试题,主要是记录,并在以后的面试中不再感到迷茫。

二、面试题

1)持久化策略

关于redis持久化的策略,分为两种一种是RDBAOF,当然可以结合使用

在以前就讲述过

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,是可以使CPUmemory达到最优平衡的效果
  • 定时过期(redis不用):这种就是对每一个key来启动一个定时器,到时间了就进行清除。

    • 由于redis中存在大量的key,这种方案肯定是不能用的,随时导致CPU到达100%

综上所述,redis的最佳使用策略有两种,就是惰性过期定期过期。并且在redis中,这两种过期策略同时在使用。

4)缓存淘汰算策略哪些

在上面一节讲述了,redis的过期删除策略,那么这一节则是redis的淘汰策略,这两种是不一样的。

  • 过期删除策略:指的是缓存key过期后,redis对其删除的策略。

  • 缓存淘汰策略:指的是redis缓存使用的内存超过了maxmemory的设置,触发的一种策略。它会清除缓存,使其使用的内存小于设定的maxmemory

那么缓存淘汰策略一共有下面八种==(在redis4.0前一共有六种,在之后又添加了两种,现在版本一共八种)==

  • 不处理:

    1. noeviction:不会剔除任何数据,拒绝所有写入操作,返回(error)OOM command not allowed when used memory。此时redis只响应读操作
  • 在淘汰时,仅针对设置过期时间的缓存做处理
    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算法进行筛选缓存进行删除

从上面来看,有两个算法LRULFU,分别是什么

  • LRU(Least Recently Used,最小的最近使用):根据访问时间做排序,淘汰掉访问时间越远的缓存
  • LFU(least Frequently Used,最小的频率使用):根据访问次数做排序,淘汰掉访问次数最小的缓存

5)主从、哨兵、集群的优缺点

5.1)主从

主从模式是redis最基本的集群模式,它实现了数据的复制和读写分离。在主从模式中,有一个主服务器master和多个从服务器slave。主服务器负责处理写操作,并将数据变化同步给从服务器。从服务器一般只负责处理读操作,并接收主服务器的数据更新。一个主服务器可以有多个从服务器,但一个从服务器只能有一个主服务器。

  • 优点:

    1. 提提高了数据的可靠性,即使主服务器出现故障,也可以通过从服务器恢复数据
    2. 分担了主服务器的压力,提高了数据的吞吐量和响应速度
  • 缺点:

    1. 不具备自动容错和恢复的功能,当主节点宕机,需要手动切换从节点进行顶替
    2. 可能出现数据不一致的情况,写操作都在master,读操作在slave节点。由于同步需要时间,就会造成读取数据不一致的情况。
    3. master节点的压力会很大,由于写操作全部在master,一旦写入请求过多,会导致master节点压力增大,从而导致宕机
    4. 不支持在线扩容,在集群容量达到上限时,需要停止服务才能增加或减少节点

如下图

image-20230826104646812

5.2)哨兵

哨兵模式是在主从模式的基础上增加了哨兵sentinel进程来实现高可用性。哨兵是一个独立的进程,它可以监控多个redis服务器的运行状态,包括主服务器和从服务器。哨兵模式的作用有

  1. 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器

  2. 当哨兵监测到主服务器宕机,会自动将从服务器切换为主服务器,并通过发布订阅模式通知其他从服务器和客户端,完成故障转移

  3. 为了避免单点故障,可以使用多个哨兵进行监控。各个哨兵之间也会相互监控,形成一个哨兵集群

  • 优点

    1. 实现了高可用,当主节点出现宕机的情况,可以通知进行主从切换,无需人工干预
    2. 支持了动态配置,当主从变化,哨兵会实现自动更新配置信息,并通知其他节点
    3. 提供了事件通知机制,当监控的redis实例发生故障或恢复时,哨兵会执行指定的脚本或发送邮件等方式通知相关人员
  • 缺点

    1. 缓存丢失,同步需要时间,slave节点还没来得及同步缓存数据,master就宕机了。此时sentinel选举一个slave节点变成master,原先的master恢复后变成slave,会去新master同步数据,导致最近的一批缓存数据丢失
    2. 缓存不一致,和主从结构一样,同步需要时间,可能会出现缓存不一致的情况
    3. master节点的压力会很大,和主从结构一样,哨兵并没有解决master节点请求集中的问题,还是可能会造成master节点压力过大,导致宕机的问题

如下图

image-20230826111352361

5.3)集群

集群模式是Redis最高级的集群模式,它实现了数据的分片和负载均衡。在集群模式中,没有明确的主从关系,而是由多个相互协作的节点组成一个集群。每个节点都负责一部分数据,并且可以处理读写操作。当某个节点出现故障时,集群会自动进行数据迁移和故障转移。

引入和hash slot哈希槽,一共16384个,每一个写入的缓存key都会计算其hash值,从而决定写入到哪个集群节点

  • 优点

    1. 实现了分片存储,突破了redis单节点的限制,提高了系统扩展性
    2. 实现了负载均衡,写请求压力不再是单节点,提高了系统的性能和吞吐量
    3. 实现了高可用,当某个节点出现故障时,集群会自动进行数据迁移和故障转移,无需人工干预
  • 缺点

    1. 不支持多键操作,多键可能落在不同的集群节点上,故不支持操作
    2. 不支持事务操作,因为要保证连接要在同一个节点上,而集群会导致连接到不同的节点,从而导致事务失效
    3. 不支持数据库的切换操作,集群模式只能使用数据库0

image-20230826114519095

6)简述主从同步机制

redis中,主从同步主要经过了以下几个流程

123

上面提到增量复制和全量复制,大家应该都知道是什么意思

  1. 全量复制

    1. master节点会通过 bgsave命令启动子线程进行RDB的持久化
    2. master节点通过网络IO将文件发送给slave节点,这会对消耗网络带宽
    3. slave节点会清空原本的旧数据,将RDB快照文件进行载入。注意了,在载入的过程中,整个slave节点是阻塞的,无法响应客户端的命令
  2. 增量复制

    1. offset:执行同步的两方节点,分别会维护一个复制偏移量offset
    2. runid:每个redis实例节点,都有其运行ID。这个ID由节点在启动运行时自动生成。master节点会将自己的runid发送给salve节点,salve节点会将master节点的runid保存起来。当master节点发生变更后,salve节点请求复制时,会携带这个runid,新的master节点会进行判断。
      1. 如果salve发送过来的runid与自己的不同,那么只能进行全量复制
      2. 如果salve发送过来的runid与自己的相同,那么就尝试进行增量复制**(进入第三点再进行判断,最终决定是否增量复制)**
    3. 复制积压缓冲区:在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相关的面试题,我都会在此文章中更新。

我是半月,你我一同共勉!!!