【Redis 面试】Java面试八股必背系列之Redis(下篇)

关于

目前网络上有许多Redis相关面试题的总结,但基本上都是差不多的题目,本文章内容中的题目参考整理自互联网,每道题目我都在参考答案的基础上做了重新整理和总结!并在一些相对比较重要的、面试几率大的题目前面使用不同数量的(★)进行了标注,三颗星及以上需要牢记哦!本篇文章主要涵盖了Redis缓存、持久化、高可用以及应用相关的面试题。

上篇基础部分看这里:【Redis 面试】Java面试八股必背系列之Redis(上篇) (imyjs.cn)

Redis缓存

如何配置Redis最大内存?(★★)

通过修改配置文件

修改redis.conf配置文件

maxmemory 1024mb //设置Redis最大占用内存大小为1024M

注意:maxmemory默认配置为0,在64位操作系统下redis最大内存为操作系统剩余内存,在32位操作系统下redis最大内存为3GB。

通过动态命令配置

Redis支持运行时通过命令动态修改内存大小:

127.0.0.1:6379> config set maxmemory 200mb //设置Redis最大占用内存大小为200M
127.0.0.1:6379> config get maxmemory //获取设置的Redis能使用的最大内存大小
1) "maxmemory"
2) "209715200"

Redis 是如何判断数据是否过期的呢?(★★)

Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。

Redis 给缓存数据设置过期时间有啥用?(★★★)

因为内存是有限的,如果缓存中的所有数据都是一直保存的话,那么很容易造成内存溢出 Out of memory。Redis 自带了给缓存数据设置过期时间的功能。

Redis 中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 。另外, persist 命令可以移除一个键的过期时间。

过期时间除了有助于缓解内存的消耗,还有什么其他用么?

很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。

过期数据的删除策略了解么?(★★★)

常用的过期数据的删除策略就两个:

  1. 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。

  2. 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除

Redis 内存淘汰机制了解么?(★★★★)

Redis内存淘汰策略是指当缓存内存不足时,通过淘汰旧数据处理新加入数据选择的策略。

一般来说,缓存的容量是小于数据总量的,所以,当缓存数据越来越多,Redis 不可避免的会被写满,这时候就涉及到 Redis 的内存淘汰机制了。我们需要选定某种策略将“不重要”的数据从 Redis 中清除,为新的数据腾出空间。Redis配置文件中可以设置maxmemory,内存的最大使用量,到达限度时会执行内存淘汰机制,没有配置时,默认为no-eviction,对于写请求直接返回错误,不进行淘汰。

在 Redis 4.0 版本之前有 6 种数据淘汰策略,4.0 增加了2种,主要新增了 LFU 算法。

名称 描述
volatile-lru 已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-lfu 从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中挑选任意数据淘汰
allkeys-lru 当内存不足写入新数据时淘汰最近最少使用的Key(这个是最常用的)
allkeys-random 当内存不足写入新数据时随机选择key淘汰
allkeys-lfu 当内存不足写入新数据时移除最不经常使用的Key
no-eviction 当内存不足写入新数据时,写入操作会报错,同时不删除数据

volatile为前缀的策略都是从已过期的数据集中进行淘汰。
allkeys为前缀的策略都是面向所有key进行淘汰。
LRU(least recently used)最近最少用到的。
LFU(Least Frequently Used)最不常用的。
它们的触发条件都是Redis使用的内存达到阈值时。

注意:当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有设置过期的key可以被淘汰,则和noeviction一样返回错误。

扩展

LRU(Least Recently Used),即最近最少使用,是一种缓存置换算法。在使用内存作为缓存的时候,缓存的大小一般是固定的。当缓存被占满,这个时候继续往缓存里面添加数据,就需要淘汰一部分老的数据,释放内存空间用来存储新的数据。这个时候就可以使用LRU算法了。其核心思想是:如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉。

扩展

LFU(Least Frequently Used),是Redis4.0新加的一种淘汰策略,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。

LFU算法能更好的表示一个key被访问的热度。假如你使用的是LRU算法,一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况,因为使用一次并不会使一个key成为热点数据。

什么是缓存击穿?如何解决?(★★★★)

缓存击穿的意思是缓存中没有数据,而数据库中有数据。出现这一情况的原因一般是缓存到期。并且在其这个时候用户访问量很大,导致读缓存没有读到,都去访问数据库,造成数据库压力大。

一个并发访问量比较大的key在某个时间过期,导致所有的请求直接打在数据库上。

常见的解决方案有

  • 设置热点数据永远不过期。

  • 分级缓存

  • 加锁:如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求,防止数据库打死。当然这样会导致系统的性能变差。

什么是缓存穿透?如何解决?(★★★★)

当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。这和缓存击穿有根本的区别,区别在于缓存穿透的情况是传进来的key在Redis中是不存在的

如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至击垮数据库系统。常见的解决方案有

  • 把无效的Key存进Redis中、返回空对象。如果Redis查不到数据,数据库也查不到,我们把这个Key值保存进Redis,设置value="null",当下次再通过这个Key查询时就不需要再查询数据库。为了避免存储过多空对象,通常会给空对象设置一个过期时间。这种处理方式肯定是有问题的:如果有大量的key穿透,缓存空对象会占用宝贵的内存空间;空对象的key设置了过期时间,在这段时间可能会存在缓存和持久层数据不一致的场景。

  • 使用布隆过滤器(推荐)。布隆过滤器专门用来检测集合中是否存在特定的元素。布隆过滤器的作用是某个 key 不存在,那么就一定不存在,它说某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回。

什么是缓存雪崩?如何解决?(★★★★)

当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。造成缓存雪崩的关键在于在同一时间大规模的key失效

常见的解决方案有

  • 提高缓存可用性

    • 集群部署:通过集群来提升缓存的可用性,提高Redis的容灾性,可以利用Redis本身的Redis Cluster或者第三方集群方案如Codis等。

    • 多级缓存:设置多级缓存,第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同。

  • 过期时间

    • 均匀过期:为了避免大量的缓存在同一时间过期,在原有的失效时间上加上一个随机值,比如1-5分钟随机。这样就避免了因为采用相同的过期时间导致的缓存雪崩。

    • 热点数据永不过期。

  • 熔断降级

    • 服务熔断:当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。

    • 服务降级:当出现大量缓存失效,而且处在高并发高负荷的情况下,在业务系统内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。

  • 数据库:提高数据库的容灾能力,可以使用分库分表,读写分离的策略。

缓存击穿、穿透、雪崩三者有什么区别?(★★★)

发生缓存击穿问题的关键是:缓存中的某个热点数据过期

发生缓存穿透问题的关键是:数据既不在缓存中,也不在数据库中,无法构建缓存数据

发生缓存雪崩问题的关键是:大量缓存数据在同一时间过期

什么是缓存预热?(★★)

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候,先查询数据库,然后再将数据回写到缓存。

如果不进行预热, 那么 Redis 初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

缓存预热的操作方法

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;

  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;

  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

什么是缓存降级?(★★)

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。

在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。

降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

Redis持久化

Redis 持久化机制了解吗(★★★★)

什么是持久化?

持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。

还可以从如下两个层面简单的理解持久化 :

  • 应用层:如果关闭(shutdown)你的应用然后重新启动则先前的数据依然存在。

  • 系统层:如果关闭(shutdown)你的系统(电脑)然后重新启动则先前的数据依然存在。

为什么要持久化?

Redis是内存数据库,为了保证效率所有的操作都是在内存中完成。数据都是缓存在内存中,当你重启系统或者关闭系统,之前缓存在内存中的数据都会丢失再也不能找回。因此为了避免这种情况,Redis需要实现持久化将内存中的数据存储起来。

如何实现持久化?

Redis 提供了两种持久化机制:第一种是 RDB,又称快照(snapshot)模式,第二种是 AOF (Append Only File) 日志,也就追加模式。

  • RDB持久化:能够在指定的时间间隔能对你的数据进行快照存储。

  • AOF持久化:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

  • 不使用持久化:如果你只希望你的数据在服务器运行的时候存在,你也可以选择不使用任何持久化方式。

  • 同时开启RDB和AOF:你也可以同时开启两种持久化方式,在这种情况下当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。

RDB

RDB,简单说就是在不同的时间点,将 Redis 存储的数据生成快照并存储到磁盘等介质上;

RDB 持久化提供了两种触发策略:一种是手动触发,另一种是自动触发。手动触发是客户端通过SAVE命令或者BGSAVE命令将内存数据保存到磁盘文件中。自动触发就是在Redis 的配置文件中提前写好触发的条件。

对于 RDB 方式,Redis 会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何 IO 操作的,这样就确保了 Redis 极高的性能。Redis 在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。如果你对数据的完整性非常敏感,那么 RDB 方式就不太适合你,因为即使你每 5 分钟都持久化一次,当 redis 故障时,仍然会有近 5 分钟的数据丢失。

AOF

AOF,英文是 Append Only File,即只允许追加不允许改写的文件。那就是将 Redis 执行过的所有写指令记录下来,在下次 Redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。

我们通过配置 redis.conf 中的 appendonly yes 就可以打开 AOF 功能。如果有写操作(如 SET 等),Redis 就会被追加到 AOF 文件的末尾。

默认的 AOF 持久化策略是每秒钟 fsync 一次(fsync 是指把缓存中的写指令记录到磁盘中),因为在这种情况下,Redis 仍然可以保持很好的处理性能,即使 Redis 故障,也只会丢失最近 1 秒钟的数据。

在进行 AOF 重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响 AOF 文件的可用性。

虽然优点多多,但 AOF 方式也同样存在缺陷,比如在同样数据规模的情况下,AOF 文件要比 RDB 文件的体积大。而且,AOF 方式的恢复速度也要慢于 RDB 方式。

对于我们应该选择 RDB 还是 AOF,官方的建议是两个同时使用,这样可以提供更可靠的持久化方案。同时使用这种情况下,如果 Redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。

RDB和AOF的优缺点

RDB优点

  • RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。

  • RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心,非常适用于灾难恢复

  • RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能

  • 与AOF相比,在恢复大的数据集的时候,RDB 方式会更快一些

RDB缺点

  • Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。

  • RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候, fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。

优点:RDB 是紧凑的二进制文件,比较适合备份,全量复制等场景;RDB 持久化方式可以最大化 Redis 的性能;RDB 恢复数据远快于 AOF。

缺点:RDB 无法实现实时或者秒级持久化;新老版本无法兼容 RDB 格式。

AOF优点

  • 你可以使用不同的 fsync 策略:无 fsync、每秒 fsync 、每次写的时候 fsync .使用默认的每秒 fsync 策略, Redis 的性能依然很好( fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据。

  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题。

  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。

  • 数据恢复(load)时AOF比RDB慢,通常RDB 可以提供更有保证的最大延迟时间。

总结

优点:可以更好地保护数据不丢失;appen-only 模式写入性能比较高;适合做灾难性的误删除紧急恢复。

缺点:对于同一份文件,AOF 文件要比 RDB 快照大;AOF 开启后,会对写的 QPS 有所影响,相对于 RDB 来说 写 QPS 要下降;

数据库恢复比较慢, 不合适做冷备。

持久化机制AOF重写了解吗(★★★)

如果采用了追加方式进行持久化,如果不做任何处理的话,AOF 文件会变得越来越大,为此,Redis 提供了 AOF 文件重写(rewrite)机制,即当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集。

重写的目的:

  • 减小AOF文件占用空间;

  • 更小的AOF 文件可以更快地被Redis加载恢复。

举个例子或许更形象,假如我们调用了 100 次 INCR 指令,在 AOF 文件中就要存储 100 条指令,但这明显是很低效的,完全可以把这 100 条指令合并成一条 SET 指令,这就是重写机制的原理。

AOF重写可以分为手动触发和自动触发:

  • 手动触发:直接调用bgrewriteaof命令。

  • 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。

在重写即将开始之际,Redis 会创建(fork)一个“重写子进程”,这个子进程会首先读取现有的 AOF 文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。

与此同时,主工作进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的 AOF 文件中,这样做是保证原有的 AOF 文件的可用性,避免在重写过程中出现意外。

当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新 AOF 文件中。

当追加结束后,Redis 就会用新 AOF 文件来代替旧 AOF 文件,之后再有新的写指令,就都会追加到新的 AOF 文件中了。

如何选用持久化机制?(★★★)

  • 一般来说, 如果想达到足以媲美数据库的 数据安全性,应该 同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。

  • 如果 可以接受数分钟以内的数据丢失,那么可以 只使用 RDB 持久化

  • 有很多用户都只使用 AOF 持久化,但并不推荐这种方式,因为定时生成 RDB 快照(snapshot)非常便于进行数据备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免 AOF 程序的 bug。

  • 如果只需要数据在服务器运行的时候存在,也可以不使用任何持久化方式。

Redis 4.0 的混合持久化了解吗?(★★★)

重启 Redis 时,我们很少使用 RDB 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。

Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 RDB 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小,于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

简单说说Redis的数据恢复?(★★★)

当Redis发生了故障,可以从RDB或者AOF中恢复数据。恢复的过程也很简单,把RDB或者AOF文件拷贝到Redis的数据目录下,如果使用AOF恢复,配置文件开启AOF,然后启动redis-server即可。

Redis 启动时加载数据的流程:

  1. AOF持久化开启且存在AOF文件时,优先加载AOF文件。

  2. AOF关闭或者AOF文件不存在时,加载RDB文件。

  3. 加载AOF/RDB文件成功后,Redis启动成功。

  4. AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。

Redis高可用

什么是Redis主从复制,有什么作用?(★★★)

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

  • 主从复制的作用主要包括:

    • 读写分离:主节点写,从节点读,提高服务器的读写负载能力。

    • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

    • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。但是,单纯的Redis主从复制架构在当主节点发生故障时,无法自动切换从节点对外提供服务,需要借助哨兵机制来完成。

    • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

    • 高可用基石:主从复制还是哨兵机制和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

Redis主从有几种常见的拓扑结构?(★★)

Redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从、树状主从结构。

  1. 一主一从结构是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持。

  2. 一主多从结构(又称为星形拓扑结构)使得应用端可以利用多个从节点实现读写分离。对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。

  3. 树状主从结构(又称为树状拓扑结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层,可以有效降低主节点负载和需要传送给从节点的数据量。

Redis主从复制的同步策略?(★★★)

Redis主从复制可以根据是否是全量分为全量同步和增量同步。

全量同步

Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。Redis全量复制一般发生在初次复制场景,也就是在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:

  • 从服务器连接主服务器,发送SYNC命令;

  • 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;

  • 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;

  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;

  • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;

  • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。

增量同步

增量同步也可以说是部分复制,主要是Redis针对全量复制的过高开销做出的一种优化措。 使用Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。

增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

Redis的主从复制原理了解吗?(★★★)

Redis主从复制的工作流程大概可以分为如下几步:

  1. 保存主节点(master)信息 这一步只是保存主节点信息,保存主节点的ip和port。

  2. 主从建立连接 从节点(slave)发现新的主节点后,会尝试和主节点建立网络连接。

  3. 发送ping命令 连接建立成功后从节点发送ping请求进行首次通信,主要是检测主从之间网络套接字是否可用、主节点当前是否可接受处理命令。

  4. 权限验证 如果主节点要求密码验证,从节点必须正确的密码才能通过验证。

  5. 同步数据集 主从复制连接正常通信后,主节点会把持有的数据全部发送给从节点。

  6. 命令持续复制 接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。

主从复制存在哪些问题呢?(★★★)

主从复制虽好,但也存在一些问题:

  • 一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预

  • 主节点的写能力受到单机的限制。

  • 主节点的存储能力受到单机的限制。

第一个问题是Redis的高可用问题,第二、三个问题属于Redis的分布式问题。

什么是哨兵机制?(★★★)

主从复制模式的一个缺点,就在于无法实现自动化地故障恢复 。Redis后来引入了哨兵机制,哨兵机制大大提升了系统的高可用性。

Sentinel(哨兵)机制是Redis 的高可用性解决方案,它由两部分组成,哨兵节点和数据节点:

  • 哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据,不提供读写服务,主要用来监控Redis实例节点,对数据节点进行监控。也就是说,哨兵在启动时,不会去加载RDB文件。

  • 数据节点: 主节点和从节点都是数据节点。

哨兵架构下客户端第一次从哨兵找出Redis的主节点,后续就直接访问Redis的主节点,不会每次都通过sentinel代理访问Redis的主节点,当Redis的主节点挂掉时,哨兵会第一时间感知到,并且在从节点中重新选出来一个新的主节点,然后将新的主节点信息通知给客户端,从而实现高可用。这里面Redis的客户端一般都实现了订阅功能,订阅sentinel发布的节点变动消息。简单的说哨兵就是带有自动故障转移功能的主从架构

但是哨兵机制仍然无法解决: 1.单节点并发压力问题 2.单节点内存和磁盘物理上限,需要使用集群架构解决这些问题。

哨兵机制中,哨兵的任务(作用)?(★★★)

哨兵主要具有三个作用, 监控、自动故障迁移与通知

  • 监控:哨兵会利用心跳机制,周期性不断地检测主库与从库是否运作正常。

  • 自动故障迁移:当一个Master不能正常工作时,哨兵会进行自动故障迁移操作,将失效Master的其中一个Slave升级为新的Master,并让失效Master的其他Slave改为复制新的Master;当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用新Master代替失效Master。

  • 通知:当被监控的某个Redis节点出现问题时,哨兵会将新主库的地址通知到所有从库,使得所有从库与旧主库slaveof新主库,也会将新主库的地址通知到客户端上。

领导者Sentinel节点选举了解吗?(★★★)

Redis使用了Raft算法实现领导者选举,大致流程如下:

  1. 每个在线的Sentinel节点都有资格成为领导者,当它确认主节点主观 下线时候,会向其他Sentinel节点发送sentinel is-master-down-by-addr命令, 要求将自己设置为领导者。

  2. 收到命令的Sentinel节点,如果没有同意过其他Sentinel节点的sentinel is-master-down-by-addr命令,将同意该请求,否则拒绝。

  3. 如果该Sentinel节点发现自己的票数已经大于等于max(quorum, num(sentinels)/2+1),那么它将成为领导者。

  4. 如果此过程没有选举出领导者,将进入下一次选举。

新的主节点是怎样被挑选出来的?(★★★)

选出新的主节点,大概分为这么几步:

  1. 过滤:“不健康”(主观下线、断线)、5秒内没有回复过Sentinel节点ping响应、与主节点失联超过down-after-milliseconds*10秒。

  2. 选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续。

  3. 选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续。

  4. 选择runid最小的从节点。

Redis 集群了解吗?(★★★)

Redis 的哨兵模式基本已经可以实现高可用,读写分离,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在 redis3.0 上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容

Redis 集群模式的出现是为了解决 Redis 单机容量有限的问题的。该种模式会将 Redis 中数据按照一定规则划分到多台机器上。这种模式有两个特点:

  • 能够在多个节点之间自动拆分数据集。

  • 当节点的子集遇到故障或无法与群集的其余部分通信时,能够继续操作。

Redis 集群中有 16384 个散列槽。而 Redis 群集中的每个节点只负责哈希槽的一个子集。

例如,您可能拥有一个包含 3 个节点的集群,其中:

  • 节点 A 包含从 0 到 5500 的散列槽。

  • 节点 B 包含从 5501 到 11000 的散列槽。

  • 节点 C 包含从 11001 到 16383 的散列槽。

这种结构很容易添加或者删除节点。比如如果我想新添加个节点 D ,我需要从节点 A, B, C 中得部分槽到 D 上。如果我想移除节点 A ,需要将 A 中的槽移到 B 和 C 节点上,然后将没有任何槽的 A 节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。

在 Redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的取值范围是:0-16383。还有一个就是 cluster,可以理解为是一个集群管理的插件。当我们的存取的 Key到达的时候,Redis 会根据 CRC16 的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

如何保证缓存和数据库数据的一致性?(★★★★)

根据CAP理论,在保证可用性和分区容错性的前提下,无法保证一致性,所以缓存和数据库的绝对一致是不可能实现的,只能尽可能保存缓存和数据库的最终一致性。

选择合适的缓存更新策略

1. 删除缓存而不是更新缓存

当一个线程对缓存的key进行写操作的时候,如果其它线程进来读数据库的时候,读到的就是脏数据,产生了数据不一致问题。

相比较而言,删除缓存的速度比更新缓存的速度快很多,所用时间相对也少很多,读脏数据的概率也小很多。

2.先更数据,后删缓存先更数据库还是先删缓存?

更新数据,耗时可能在删除缓存的百倍以上。在缓存中不存在对应的key,数据库又没有完成更新的时候,如果有线程进来读取数据,并写入到缓存,那么在更新成功之后,这个key就是一个脏数据。毫无疑问,先删缓存,再更数据库,缓存中key不存在的时间的时间更长,有更大的概率会产生脏数据。

目前最流行的缓存读写策略cache-aside-pattern就是采用先更数据库,再删缓存的方式。

缓存不一致处理

如果并发不是特别高,对缓存的依赖性不是很强,其实一定程度的不一致是可以接受的。但是如果对一致性要求比较高,那就得想办法保证缓存和数据库中数据一致。缓存和数据库数据不一致常见的两种原因:

  • 缓存key删除失败

  • 并发导致写入了脏数据

消息队列保证key被删除:可以引入消息队列,把要删除的key或者删除失败的key丢尽消息队列,利用消息队列的重试机制,重试删除对应的key。这种方案看起来不错,缺点是对业务代码有一定的侵入性。

数据库订阅+消息队列保证key被删除:可以用一个服务(比如阿里的 canal)去监听数据库的binlog,获取需要操作的数据。然后用一个公共的服务获取订阅程序传来的信息,进行缓存删除操作。这种方式降低了对业务的侵入,但其实整个系统的复杂度是提升的,适合基建完善的大厂。

延时双删防止脏数据:还有一种情况,是在缓存不存在的时候,写入了脏数据,这种情况在先删缓存,再更数据库的缓存更新策略下发生的比较多,解决方案是延时双删。简单说,就是在第一次删除缓存之后,过了一段时间之后,再次删除缓存。这种方式的延时时间设置需要仔细考量和测试。

设置缓存过期时间兜底:这是一个朴素但是有用的办法,给缓存设置一个合理的过期时间,即使发生了缓存数据不一致的问题,它也不会永远不一致下去,缓存过期的时候,自然又会恢复一致。

Redis应用

如果有大量的key需要设置同一时间过期,一般需要注意什么?(★★★)

如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象(因为Redis是单线程的)。严重的话可能会导致服务器雪崩,所以我们一般在过期时间上加一个随机值,让过期时间尽量分散

如果这个Redis正在给线上的业务提供服务,那使用keys指令会有什么问题?(★★)

Redis的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?(★★)

使用 keys 指令可以扫出指定模式的 key 列表。但是要注意 keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

Redis报内存不足怎么处理?(★★★)

Redis 内存不足有这么几种处理方式:

  • 修改配置文件 redis.conf 的 maxmemory 参数,增加 Redis 可用内存;

  • 也可以通过命令set maxmemory动态设置内存上限;

  • 修改内存淘汰策略,及时释放内存空间;

  • 使用 Redis 集群模式,进行横向扩容。

大key问题了解吗?(★★★)

Redis使用过程中,有时候会出现大key的情况, 比如:

  • 单个简单的key存储的value很大,size超过10KB

  • hash, set,zset,list 中存储过多的元素(以万为单位)

大key会造成什么问题呢?

  • 客户端耗时增加,甚至超时

  • 对大key进行IO操作时,会严重占用带宽和CPU

  • 造成Redis集群中数据倾斜

  • 主动删除、被动删等,可能会导致阻塞

如何找到大key?

  • bigkeys命令:使用bigkeys命令以遍历的方式分析Redis实例中的所有Key,并返回整体统计信息与每个数据类型中Top1的大Key

  • redis-rdb-tools:redis-rdb-tools是由Python写的用来分析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成报表用来分析Redis的使用详情。

如何处理大key?

  • 删除大key

    • 当Redis版本大于4.0时,可使用UNLINK命令安全地删除大Key,该命令能够以非阻塞的方式,逐步地清理传入的Key。

    • 当Redis版本小于4.0时,避免使用阻塞式命令KEYS,而是建议通过SCAN命令执行增量迭代扫描key,然后判断进行删除。

  • 压缩和拆分key

    • 当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。

    • 当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取。

    • 当value是list/set等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片。

Redis常见性能问题和解决方案?(★★★)

  1. Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化。

  2. 如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。

  3. 为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。

  4. 尽量避免在压力较大的主库上增加从库。

  5. Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。

  6. 为了 Master 的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现 Slave 对 Master 的替换,也即,如果 Master 挂了,可以立马启用 Slave1 做 Master,其他不变。

Redis 如何实现延时队列?(★★★)

使用zset,利用排序实现:可以使用 zset这个数据结构,用设置好的时间戳作为score进行排序,使用 zadd score1 value1 ....命令就可以一直往内存中生产消息。再利用 zrangebysocre 查询符合条件的所有待处理的任务,通过循环执行队列任务即可。

微信关注

                     编程那点事儿

参考:

本站为非盈利性站点,所有资源、文章等仅供学习参考,并不贩卖软件且不存在任何商业目的及用途,如果您访问和下载某文件,表示您同意只将此文件用于参考、学习而非其他用途。
本站所发布的一切软件资源、文章内容、页面内容可能整理来自于互联网,在此郑重声明本站仅限用于学习和研究目的;并告知用户不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
如果本站相关内容有侵犯到您的合法权益,请仔细阅读本站公布的投诉指引页相关内容联系我,依法依规进行处理!
作者:理想
链接:https://www.imyjs.cn/archives/1108
THE END
二维码
【Redis 面试】Java面试八股必背系列之Redis(下篇)
<<上一篇
下一篇>>
文章目录
关闭
目 录