Redis相关底层面试题

2023-10-18 16:28:35 浏览数 (1)

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有没有过期,如果过期了,则进行清除。
代码语言:txt复制
- 这种策略可以最大的节省`CPU`的资源,对内存不太友好。极端情况下可能出现一大堆过期的`key`一直存在于内存中,正是因为没有再次访问这些`key`,导致他们没有清除
代码语言:txt复制
- 注意是一定数量的`key`,是可以使`CPU`和`memory`达到最优平衡的效果
代码语言:txt复制
- 由于`redis`中存在大量的`key`,这种方案肯定是不能用的,随时导致`CPU`到达`100%`

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

4)缓存淘汰算策略哪些

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

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

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

  • 不处理:
代码语言:txt复制
1. **noeviction**:不会剔除任何数据,拒绝所有写入操作,返回`(error)OOM command not allowed when used memory`。此时`redis`只响应读操作在淘汰时,仅针对设置过期时间的缓存做处理
  • 在淘汰时,针对所有的缓存
  • allkey-random:随机进行删除
  • allkkey-lru:根据LRU算法进行筛选缓存进行删除
  • allkey-lfu:根据LFU算法进行筛选缓存进行删除

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

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

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

5.1)主从

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

  • 优点:
代码语言:txt复制
1. 提提高了数据的可靠性,即使主服务器出现故障,也可以通过从服务器恢复数据
2. 分担了主服务器的压力,提高了数据的吞吐量和响应速度缺点:
代码语言:txt复制
1. **不具备自动容错和恢复的功能**,当主节点宕机,需要手动切换从节点进行顶替
2. **可能出现数据不一致的情况**,写操作都在`master`,读操作在`slave`节点。由于同步需要时间,就会造成读取数据不一致的情况。
3. **`master`****节点的压力会很大**,由于写操作全部在`master`,一旦写入请求过多,会导致`master`节点压力增大,从而导致宕机
4. **不支持在线扩容**,在集群容量达到上限时,需要停止服务才能增加或减少节点

如下图

5.2)哨兵

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

  1. 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器
  2. 当哨兵监测到主服务器宕机,会自动将从服务器切换为主服务器,并通过发布订阅模式通知其他从服务器和客户端,完成故障转移
  3. 为了避免单点故障,可以使用多个哨兵进行监控。各个哨兵之间也会相互监控,形成一个哨兵集群
  • 优点
代码语言:txt复制
1. 实现了高可用,当主节点出现宕机的情况,可以通知进行主从切换,无需人工干预
2. 支持了动态配置,当主从变化,哨兵会实现自动更新配置信息,并通知其他节点
3. 提供了事件通知机制,当监控的`redis`实例发生故障或恢复时,哨兵会执行指定的脚本或发送邮件等方式通知相关人员缺点
代码语言:txt复制
1. **缓存丢失**,同步需要时间,`slave`节点还没来得及同步缓存数据,`master`就宕机了。此时`sentinel`选举一个`slave`节点变成`master`,原先的`master`恢复后变成`slave`,会去新`master`同步数据,导致最近的一批缓存数据丢失
2. **缓存不一致**,和主从结构一样,同步需要时间,可能会出现缓存不一致的情况
3. **`master`****节点的压力会很大**,和主从结构一样,哨兵并没有解决`master`节点请求集中的问题,还是可能会造成`master`节点压力过大,导致宕机的问题

如下图

5.3)集群

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

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

  • 优点
代码语言:txt复制
1. 实现了分片存储,突破了`redis`单节点的限制,提高了系统扩展性
2. 实现了负载均衡,写请求压力不再是单节点,提高了系统的性能和吞吐量
3. 实现了高可用,当某个节点出现故障时,集群会自动进行数据迁移和故障转移,无需人工干预缺点
代码语言:txt复制
1. 不支持多键操作,多键可能落在不同的集群节点上,故不支持操作
2. 不支持事务操作,因为要保证连接要在同一个节点上,而集群会导致连接到不同的节点,从而导致事务失效
3. 不支持数据库的切换操作,集群模式只能使用数据库`0`

6)简述主从同步机制

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

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

  1. 全量复制
代码语言:txt复制
1. `master`节点会通过 `bgsave`命令启动子线程进行`RDB`的持久化
2. `master`节点通过`网络IO`将文件发送给`slave`节点,这会对消耗网络带宽
3. `slave`节点会清空原本的旧数据,将`RDB`快照文件进行载入。**注意了,在载入的过程中,整个****`slave`****节点是阻塞的,无法响应客户端的命令**
代码语言:txt复制
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,当然过期时间可以给短一下,避免恶意请求
  • 采用布隆过滤器
代码语言:txt复制
- 当一个`ID`在过滤器中不存在,那么它一定不存在。直接过滤掉即可
- 当一个`ID`在过滤器中存在,那么它可能存在,可能不存在。那么再去读取缓存,读取数据库即可。添加互斥锁,对读写缓存的代码进行加锁,那么最多也只有一个请求能到数据库

三、最后

如果有什么redis相关的面试题,我都会在此文章中更新。

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

0 人点赞