Redis面试题汇总

Redis面试题汇总

1:什么是Redis?

Redis官网.png

在Redis官网上有这样一句话:Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. Redis 是一个开源的,基于内存的数据机构存储,可以把它用作数据库,缓存,及消息中间件。

2:为什么要用Redis?

Redis是基于内存的,所以经常会用作于缓存。Redis的存储方式是以key-value形式来进行存储,类似于Java中的Map容器。

既然这样,为什么不使用Map来进行缓存呢?这里我们需要知道缓存分为本地缓存与分布式缓存,自带的Map时本地缓存,生命周期随着JVM销毁而结束,并且在多实例的情况下,每个实例都要保存一份缓存,缓存不具有一致性。

使用Redis被称为分布式缓存,在多实例的情况下,可以公用同一份缓存数据,缓存具有一致性。Redis缓存可以进行持久化,Map时内存中的对象,程序重启数据就没了。Redis可以使用几十个G来进行缓存,Map无法做到这么大量的数据。

一般数据库的读写时需要经过硬盘的,而硬盘的读取速度相较于内存来说时相当慢的。所以使用Redis的目的是为了:不用每次读取的时候,都去查询数据库。

3:Redis的数据结构

对于Redis来说,所有的key都是字符串。

Redis支持丰富的数据结构,较为常见且常用的有String(字符串),List(列表),Hash(哈希),Set(集合),zSet(有序集合)

Redis的5中数据结构.png

4:Redis服务器中的数据库

Redis服务器中也有数据库这样一个概念,默认是有16个数据库。且数据库与数据库之间的数据是隔离的。

5:Redis持久化

Redis是基于内存的,如果没有将数据持久化到磁盘上,一旦重启,内存中的数据将会全部丢失。

Redis提供了两种不同的持久化方式来将数据存储到硬盘中:

  • RDB(快照),即某一个时刻的所有数据进行保存。
  • AOF(追加),在Redis进行写操作时,将执行的写操作保存到AOF文件之中

5.1:RDB(快照持久化)

RDB持久化可以手动进行执行,也可以配置定期执行。RDB持久化生成的RDB文件是一个经过压缩的二进制文件,Redis可以通过这个RDB文件来还原当时数据库中的所有数据。

我们可以通过SAVEBGSAVE命令来手动生成RDB文件。SAVE会阻塞Redis服务的进程,当RDB文件创建完成之前,Redis无法处理任何请求。BGSAVE则会创建一个子进程,由子进程来创建RDB文件,Redis可以继续处理请求。

我们可以配置定期执行来生成RDB文件,如果下面的条件被触发,则会执行BGSAVE命令:

save 900 1               #在900秒(15分钟)之后,至少有1个key发生变化
save 300 10              #在300秒(5分钟)之后,至少有10个key发生变化
save 60 10000           #在60秒(1分钟)之后,至少有10000个key发生变化

Redis服务在启动的时候,如果发现RDB文件,会自动将RDB中文件的数据导入到内存之中。

5.2:AOF(文件追加)

AOF时通过保存Redis服务器所执行的写命令来记录数据库中的数据。

AOF的重写会让AOF文件体积变得更小,AOF的重写并不是对现有的AOF文件进行读取分析,而是读取服务器中当前数据库中的数据来实现的。

Redis将AOF重写程序放在子进程中执行,像BGSAVE一样,会fork出来一个子进程来完成重写AOF的操作,不会影响到主进程。

当AOF文件重写时,新的写命令请求可能会导致当前数据库中的数据与重写后的AOF文件数据不一致。为了解决数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区会在fork出子进程之后使用。

5.3 Redis对过期键的策略

Redis服务器过期键删除策略有两种:
- 定期删除
- 惰性删除
Redis会把每个设置了过期时间的key放置在独立的字典中,定时遍历这个字典来删除到期的Key。除了定时遍历之外,还会使用惰性策略来删除过期的key,就是在客户端访问这个key时,会检查这个key的过期时间,如果过期了就立即删除。

  • RDB持久化对过期键的策略:

    • 执行SAVE或者BGSAVE来创建出新的RDB文件,会对数据库中的过期键进行检查, 已过期的键不会保存在RDB文件中。
    • 载入RDB文件时,同样会对RDB文件中的键进行检查,过期的键会被忽略。
  • AOF持久化对过期键的策略

    • 如果数据库中的键已经过期,但还没有被定期/惰性删除,AOF文件不会因为这个过期键产生任何影响,当过期键被删除之后,会追加一条DEL命令来显示这个键已经被删除了。
    • AOF重写过程中,程序会对数据库中的键进行检查,已经过期的键不会保存到重写后的AOF文件之中。

5.4:RDB与AOF的选择

RDB 与 AOF 可以同时使用。

RDB的优缺点:
- 优点:载入数据快,文件体积小。
- 缺点:会一定程度丢失数据。
AOF的优缺点:
- 优点:丢失的数据少,默认最多丢失1s的数据。
- 缺点:载入数据慢,文件体积较大。

如果Redis同时开启了RDB 与 AOF 持久化,服务器优先使用AOF文件来还原数据,保证数据的完善性。

6:Redis单线程为什么这么快?

  • 纯内存操作
  • 基于非阻塞的IO多路复用机制
  • 单线程避免了多线程频繁切换上下文消耗

7:Redis高可用

Redis如果只有一台服务器的情况下,随着请求越来越多,单台的Redis内存是有限的,可能放不下那么多的数据,如果Redis挂了,所有的请求都会走数据库。

为了实现服务的高可用,可以搞多台Redis服务器来当作主从进行管理。

7.1:主架构的特点

  • 主服务器负责接收写请求
  • 从服务器负责处理读请求
  • 从服务器的数据是由主服务器复制过去,保证数据的一致性

实现了读写分离(主服务器负责写,从服务器负责读),高可用(当某台从服务器挂了,其他从服务器海可以接受请求),更高的并发量(多台从服务器都可以接受读请求)。

7.2:复制功能

在Redis中,用户可以通过执行SALVEOF命令或设置salveof选项。来让一个服务器去复制另一台服务器,我们称被复制的服务器为主服务器,进行复制的服务器为从服务器。

复制分为两个操作:
- 同步:将从服务器的数据库状态更新为主服务器的数据库状态。
- 命令传播:当主服务器的数据库状态被修改,导致主从服务器的数据库状态不一致,让主从服务器的数据库状态重新回到一致状态。

主从数据一致性.png

从服务器对主服务器的同步又可以分为两种情况:
- 初次同步:从服务器中没有任何的数据,或者从服务器要复制的主服务器与之前复制的主服务器不同。
- 断线后同步:在命令传播阶段 中断了复制,从服务器重连到主服务器并继续复制主服务器。

在Redis2.8之前,断线后同步这部分缺少的只是部分的数据,但会让主从服务器 重新执行 SYNC 命令,这样会导致所有的数据再次重新同步,效率较低。

在Redis2.8之后,使用PSYNC替代SYNC来执行断线后同步的操作。

当完成了同步之后,主从服务器就会进行命令传播阶段,主服务器只要把自己的写命令发送给从服务器,从服务器接受写命令并执行就可以保证主从服务器一直保持数据一致。

7.3:哨兵机制

如果从服务器挂掉之后,我们一般有多个从服务器,可以将请求移交给其他的从服务器来进行处理,但是主服务器挂了之后,Redis提供了哨兵机制来处理这种情况,会将从服务器升级为主服务器,当旧的主服务器重连之后,会将这个旧的服务器变成从服务器。整个过程就叫做 主备切换。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,形成了多哨兵模式。

判断主服务器是否下线有两种情况:

  • 主观下线

    • 哨兵会以每秒一次的频率向与他创建链接的实例发送Ping命令,通过Ping返回的消息来判断实例是否在线。
    • 如果一个主服务器在设置的时间内没有向哨兵响应服务,那么当前哨兵主观认为这个服务器已经下线了。
  • 客观下线

    • 当一个哨兵判断主服务为主观下线之后,为了确认主服务器是否真的下线,会向其他同样监视主服务器的其他哨兵询问,看他们是否也主观认为该主服务器是否已经下线。
    • 如果足够多的哨兵认为主服务器是下线的,那么就判定主服务器为客观下线,并对主服务器执行故障转移操作。

当一个主服务器被认为客观下线之后,监视这个下线的主服务器的各个哨兵会选举出一个领头的哨兵,领头的哨兵会对下线的主服务器执行故障转移操作。

领头哨兵被选举出来之后会执行一下三个步骤:

  • 在已下线的主服务器属下的从服务器中,挑选出一个转换为主服务器。
  • 让已下线的主服务器属下的所有从服务器改为 复新新的主服务器。
  • 已下线的主服务器重新连接时,会让他成为新的主服务器属下的从服务器。

7.4:数据丢失

目前的主从架构 + 哨兵架构可以说时高可用的,但是Redis还是会丢失数据。

  • 异步复制导致数据丢失

    • 有部分数据还没复制到从服务器上,主服务器下线了,那么这部分数据就丢失了。
  • 脑裂导致的数据丢失

    • 所谓的脑裂就是:有时候主服务器脱离了正常的网络,与其他的从服务器不能连接,此时哨兵就会认为主服务器已经下线了,然后选举将某个从服务器切换为了主服务器。但是实际上主服务器还在运行,这个时候,集群中有两个主服务器。
    • 虽然某个从服务器被切换成了主服务器,但是可能客户端还没来得及切换为新的主服务器,客户端还继续向旧的主服务器写数据,旧的服务器重新连接时,会作为从服务器来复制新的主服务器,这样导致了数据的丢失。

可以通过以下两个配置来尽量减少数据丢失的可能:

min-slaves-to-write 1
min-slaves-max-lag 10

这样的配置确保了,如果跟任何一个slave丢失了连接,在10秒后发现没有salve给自己ack,那么就拒绝新的写请求。

要求至少有一个slave,数据复制与同步的延迟不能超过10秒,如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒,那么这个时候,master就不会再接受任何请求。

  • 减少异步复制的数据丢失

    • 有了min-slaves-max-lag 这个配置,就可以说一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样就可以把master宕机时由于部分数据位同步到salve导致的数据丢失降低到可控制的范围内。
  • 减少脑裂的数据丢失

    • 如果一个master出现了脑裂,与其他的slave丢失了连接,那么这两个配置就可以确保,如果不能继续给指定熟练的salve发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求。这样脑裂后的就master就不会接受client的新数据,避免了数据丢失。

8:缓存雪崩

如果我们的Redis缓存挂掉或缓存同时失效,导致所有的请求全部访问数据库,这种情况被称为缓存雪崩。

  • 当Redis缓存同时失效的解决方案

    • 在缓存的时候给过期时间加上一个随机值,这样就会大幅度减少缓存在同一时间过期。
  • 当Redis缓存挂掉的解决方案

    • 在事发前,采用高可用方案(主从架构 + 哨兵机制),尽量避免Redis挂掉。
    • 在事发时,设置本地缓存及限流,保证数据库不被干掉,服务能够正常工作。
    • 在事发后,redis持久化,从磁盘加载数据,尽快恢复缓存数据。

9:缓存穿透

缓存穿透指的是一个 一定不存在的数据,由于缓存不命中,并且出于容错考虑,会向数据库读取数据,如果数据库读取不到数据则不写入缓存,这将导致这个不存在的数据每次都去数据库进行查询,导致缓存失去意义。

  • 缓存穿透的解决方案
    • 当发生一个 一定不存在的数据时,我们就可以将该数据提前拦截,不合法不让这个请求到数据库层面。
    • 当我们从数据库中查询不到这个数据时,我们也将这个空对象放置在缓存中去,下次再发生请求时,就可以直接在缓存中直接获取了。一般将这个空对象设置一个较短的过期时间。

10:如何确保数据库与Redis双写一致

一般来说,我们对于读的操作会时这样的流程:
- 如果缓存中存在,则直接获取缓存中的数据并进行返回。
- 如果缓存中没有这个数据,会查询数据库并把查询到的数据防止在缓存中,并把数据进行返回。

如果仅仅时查询,缓存的数据和数据库的数据时没有问题的,但是当我们需要进行更新的时候,很有可能会造成缓存与数据库中的数据不一致。

从理论上来说,只要设置了键的过期时间,我们就能保证缓存和数据库中的数据最终都是一致的,因为只要缓存的数据过期了之后,就会被删除,随后进行读取时,因为缓存没有这条数据,就会去查询数据库,并把数据库查询出来的结果写入到缓存中。

一般来说执行更新操作时,会有两中方案,先操作缓存,再操作数据库 或 先操作数据库再操作缓存,无论是那方案,我们都希望这两部操作要成功同时成功,要失败同时失败,所以这就会演变成一个分布式事务的问题。