Java 面试篇-Redis 专题(Redis 常见的面试专题:缓存击穿、缓存雪崩、缓存穿透、什么是布隆过滤器、什么是延时双删、持久化的方式、Redis 分布式锁、I/O 多路复用等等)
🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
文章目录
1. 什么是缓存穿透?是如何解决的?
2. 可以介绍一下布隆过滤器吗?
3. 什么是缓存击穿?怎么解决?
4. 什么是缓存雪崩?怎么解决?
5. Redis 作为缓存,MySQL 的数据如何与 Redis 进行同步呢?(双写一致性)
6. 排他锁是如何保证读写、读读互斥的呢?
7. 什么是延时双删?为什么不使用它呢?
8. redis 作为缓存,数据的持久化是怎么做的?
9. 这两种持久化方式有什么区别?
10. 这两个方式,哪种恢复的比较快呢?
11. Redis 的数据过期策略有哪些?
12. redis 的数据淘汰策略有哪些?
13. 数据库有 1000 万数据,redis 只能缓存 20 万数据。如何保证 redis 中的数据都是热点数据?
14. redis 的内存用完了会发生什么?
15. redis 分布式锁如何实现?
16. 如何控制 redis 实现分布式锁的有效时长?
17. Redisson 实现的分布式锁是可重入的吗?
18. Redisson 实现的分布式锁能解决主从一致性问题吗?
19. Redis 集群有哪些方案,知道吗?
20. 可以介绍一下主从同步吗?
21. 可以说一下主从同步数据的流程吗?
22. 怎么保证 Redis 的高并发高可用?
23. Redis 集群脑裂,该怎么解决呢?
24. Redis 的分片集群有什么作用?
25. Redis 分片集群中数据是怎么存储和读取的?
26. Redis 是单线程的,但是为什么还那么快?
27. 能解释一下 I/O 多路复用模型?
1. 什么是缓存穿透?是如何解决的?
缓存穿透是指查询一个一定不存在的数据,由于存储层查不到数据,因此不写入到缓存中,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。解决方案的话,一般来说有两种:第一种是缓存空对象;第二种是使用布隆过滤器来解决。
2. 可以介绍一下布隆过滤器吗?
布隆过滤器主要是用来检索一个元素是否在一个集合中。当时使用的是 Redisson 实现的布隆过滤器。它的底层原理是,先是初始化一个比较大的数组,里面存放的是二进制 0 或 1。一开始数组中存放的都是 0,当一个 key 来了之后,经过 3 次 hash 计算,模数组长度找到数组的下标,然后把数组中原来的 0 改为 1。这样,三个数组的位置就能标明一个 key 的存在。查找的过程中也是一样的。当然,布隆过滤器有可能会产生误判,可以设置这个误判率,一般是不超过 5% 。其实这个误判率是必然存在的,要不就增加数组的长度。5% 以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。
3. 什么是缓存击穿?怎么解决?
缓存击穿的意思是,对于设置了过期时间的 Key,缓存在某个时间点过期的时候,恰好这个时间点有大量的请求过来。这些请求发现缓存过期,一般都会从后端 DB 加载数据并会重新将数据写回到缓存中,这也就是缓存重建。对于 DB 来说,大量的请求来进行缓存重建,可能会导致瞬间将 DB 压垮。
解决的方案有两种方式:
第一种方式,使用互斥锁:当缓存失效时,当大量请求来进行缓存重建之前,先获取锁,只有获取锁成功的请求,才能进行缓存重建。而没有获取锁成功的请求,则需要等待一段时间,再去查询缓存,直到缓存重建完成之后,最终才能查询到数据。这种解决的方式符合数据的强一致性,不过会消耗过多的 cpu 资源,导致性能低。
第二种方式,使用 key 逻辑过期:1)先设置 key 的时候,设置一个过期时间字段一块存入缓存中,不给当前 key 设置过期时间;2)当查询的时候,从 redis 取出的数据后判断时间是否过期;3)如果过期了,则开通另外一个线程进行数据同步,当前线程正常返回数据,当然这个数据不是最新的。
两种方案各有利弊:如果选择数据的强一致性,建议使用分布式锁的方案,但性能上可能没有那么高,且有可能会产生死锁的问题。如果选择 key 的逻辑删除,则优先考虑高可用性,性能比较高,但是数据同步做不到强一致性。
4. 什么是缓存雪崩?怎么解决?
缓存雪崩的意思是,设置缓存时采用了相同的过期时间,导致缓存在某一时间同时失效,请求全部转发到 DB,DB 瞬间压力过重而雪崩。与缓存击穿的区别是:雪崩是很多 key,而击穿是某一个 key 缓存。解决方案主要是将缓存失效时间分散开。比如,可以在原有的失效时间基础上增加一个随机值,比如 1 - 5 分钟随机。这样,每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
5. Redis 作为缓存,MySQL 的数据如何与 Redis 进行同步呢?(双写一致性)
需要让数据库与 redis 高度保持一致性,因为要求时效性比较高。当时采用的读写锁保证的强一致性。使用的是 Redisson 实现的读写锁。在读的时候添加共享锁,可以保证读读不互斥、读写互斥。当更新数据的时候,添加排他锁,它是读写、读读都是互斥的,这样就能保证在写数据的同时,是不会让其他线程读取数据的,避免了脏数据。这里面需要注意的是,读方法和写方法上需要使用同一把锁才行。
除了使用 redisson 提供的读写锁来保证数据的同步,还可以使用异步的方案同步数据。允许延时一致的业务,采用异步通知:使用 MQ 中间件,在更新数据之后,通知缓存删除。
6. 排他锁是如何保证读写、读读互斥的呢?
排他锁底层使用的也是 setnx,它保证了同时只能有一个线程操作锁住的方法。
7. 什么是延时双删?为什么不使用它呢?
延时双删,如果是写操作,先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据。其中,找个延时多久不太好确定。在延时的过程中,可能会出现脏数据,并不能保证强一致性,所以没有采用。
8. redis 作为缓存,数据的持久化是怎么做的?
在 redis 中提供了两种数据持久化的方式:1)RDB;2)AOF
9. 这两种持久化方式有什么区别?
RDB 是一个快照文件。它是把 redis 内存存储的数据写到磁盘上。当 redis 实例宕机恢复数据的时候,可以从 RDB 快照文件中恢复数据。AOF 的含义是追加文件。当 redis 执行写命令的时候,都会存储到找个文件中。当 redis 实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。
10. 这两个方式,哪种恢复的比较快呢?
RDB 因为是二进制文件,保存时体积比较小,所以恢复得比较快。但是有可能会丢失数据。通常在项目中也会使用 AOF 来恢复数据,虽然 AOF 恢复的速度慢一些,但是丢失数据的风险要小得多。在 AOF 文件中可以设置刷盘策略,可以设置为每秒批量写入一次命令。
11. Redis 的数据过期策略有哪些?
在 redis 中提供了两种数据过期删除策略。第一种是惰性删除。在设置该 key 过期时间后,不去管它。当需要该 key 的时候,首先会判断是否过期,如果过期了,则会删除 key;反之,返回该 key。第二种是定期删除。也就是说,每隔一段时间,就会对 key 进行检查,并删除里面过期的 key。定期清理的两种模式是:1)SLOW 模式,是定时任务,执行频率默认为 10 hz,每次事件循环会尝试执行,每次不超过 25 ms,可以通过修改配置文件 redis.conf 的 hz 选项来调整这个次数;2)FAST 模式,执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于 2ms,每次耗时不超过 1ms。redis 的过期删除策略是:惰性删除 + 定期删除两种策略配合使用。
12. redis 的数据淘汰策略有哪些?
在这个 redis 中提供了很多种,默认是 noeviction,不删除任何数据,内部不足时直接报错。这个可以在 redis 的配置文件中进行设置。里面有两个非常重要概念:一个是 LRU,另一个是 LFU。LRU 的意思是最少最近使用。它会用当前时间减去最后一次访问时间。这个值越大,则优先淘汰优先级越高。LFU 的意思是最少频率使用。它会统计每个 key 的访问频率。值越小,淘汰优先级越高。常用在项目中设置的是 allkeys-lru,它会挑选最近最少使用的数据进行淘汰,把一些经常访问的 key 留在 redis 中。
13. 数据库有 1000 万数据,redis 只能缓存 20 万数据。如何保证 redis 中的数据都是热点数据?
可以使用 allkeys-lru 最近最少使用的淘汰策略。留下来的都是经常访问的热点数据。
14. redis 的内存用完了会发生什么?
这个需要看 redis 的数据淘汰策略是什么。如果是默认的配置,redis 内存用完之后,则直接报错。当时设置的是 allkeys-lru 策略,把最近最常访问的数据留在缓存中。
15. redis 分布式锁如何实现?
在 redis 中提供了一个命令 setnx。由于 redis 是单线程的,用了这个命令之后。只能有一个客户端对某一个 key 设置值。在没有过期或删除 key 的时候,其他客户端时不能设置这个 key 的。
16. 如何控制 redis 实现分布式锁的有效时长?
redis 的 setnx 指令不好控制这个问题。可以采用 redis 的一个框架 redisson 实现的。在 redisson 中需要手动加锁,并且可以控制锁的失效时间和等待时间。当锁住的一个业务还没有执行完成的时候,Redisson 会引入一个看门狗机制,就是说,每隔一段时间就检查当前业务是否还持有锁。如果持有,就增加加锁的持有时间。当业务执行完成之后,需要使用释放锁就可以了。还有一个好处就是,在高并发下,一个业务可能会执行很快。客户 1 持有锁的时候,客户 2 来了以后并不会马上被拒绝,它会自旋不断尝试获取锁。如果客户 1 释放之后,客户 2 就可以马上持有锁,性能也得到了提升,这就是可重试机制。
17. Redisson 实现的分布式锁是可重入的吗?
是可以重入的,这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计数上减 1。在存储数据的时候采用的 hash 结构,大 key 可以按照自己的业务进行定制,其中小 key 是当前线程唯一标识, value 是当前线程重入的次数。
18. Redisson 实现的分布式锁能解决主从一致性问题吗?
不能解决主从一致性问题。比如,当线程 1 加锁成功后,master 节点数据会异步复制到 slave 节点,此时如果当前持有 redis 锁的 master 节点宕机,slave 节点被提升为新的 master 节点,假如现在来了一个线程 2,再次加锁,会在新的 master 节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题了。
可以利用 redisson 提供的红锁来解决这个问题,它的主要作用是,不能只在一个 redis 实例上创建锁,而是在多个 redis 实例上创建锁,并且要求在大多数 redis 节点上都成功创建锁,红锁中要求是 redis 的节点数量要过半。这样就能避免线程 1 加锁成功后 master 节点宕机导致线程 2 成功加锁到新的 master 节点上的问题了。
但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变得非常低,并且运维维护成本也非常高,所以,一般在项目中也不会直接使用红锁,并且官方也暂时废弃这个红锁。
19. Redis 集群有哪些方案,知道吗?
在 redis 中提供的集群方案总共有三种:主从复制、哨兵模式、Redis 分片集群。
20. 可以介绍一下主从同步吗?
单节点的 redis 的并发能力是有上限的,要进一步提高 redis 的并大能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。
21. 可以说一下主从同步数据的流程吗?
主从同步分为两个阶段,一个是全量同步,一个是增量同步。
全量同步时指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:
1)从节点请求主节点同步数据,其中从节点会携带自己的 replication id 和 offset 偏移量。
2)主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个 replication id,如果不是,就说明是第一次同步,那主节点就会把自己的 replication id 和 offset 发送给从节点,让从节点与主节点的信息保持一致。
3)同时主节点会执行 bgsave,生成 RDB 文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的 RDB 文件,这样就保持了一致。
4)当然,如果在生成 RDB 文件期间,依然有请求到主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点的数据完全一致了,后期再同步数据的时候,都是依赖这个日志文件,这就是全量同步。
增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点根据 replication id 进行判断,判断的结果不是第一个请求,就直接获取 offset 值,然后主节点从命令日志文件中获取 offset 值之后的数据,发送给从节点进行数据同步。
22. 怎么保证 Redis 的高并发高可用?
首先可以搭建主从集群,再加上使用 redis 中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了:监控、自动故障恢复、通知;如果 master 故障,sentinel 会将一个 slave 提升为 master。当故障恢复后也是以新的 master 为主;同时 sentinel 也充当 redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 redis 客户端,所以一般项目都会采用哨兵模式来保证 redis 的高并发高可用。
23. Redis 集群脑裂,该怎么解决呢?
现在使用的是 Redis 的哨兵模式集群的。有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于 Redis master 节点和 Redis slave 节点和 Sentinel 处于不同的网络分区,使得 sentinel 没有能够心跳感知到 master,所以通过选举的方式提升一个 slave 为 master,这样就存在两个 master,就像大脑分裂了一样,这样会导致客户端还在 old master 那里写入数据,新节点无法同步数据,当网络恢复后,sentinel 会将 old master 降为 salve,这是再从新 master 同步数据,这会导致 old master 的大量数据丢失。
关于解决的话,可以再 redis 的配置中设置:第一可以设置最小的 slave 节点个数,比如设置至少要有一个从节点才能同步数据;第二可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量数据丢失了。
24. Redis 的分片集群有什么作用?
分片集群主要解决的是海量数据存储问题,集群中有多个 master,每个 master 保存不同的数据,并且还可以给每个 master 设置多个 slave 节点,就可以继续增大集群的高并发能力。同时每个 master 之间通过 ping 监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确的节点。
25. Redis 分片集群中数据是怎么存储和读取的?
在 redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围,key 通过 CRC16 校验后,再进行对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。取值的逻辑也是一样的。
26. Redis 是单线程的,但是为什么还那么快?
主要是因为:1)完全基于内存的,c 语言编写的;2)采用单线程,避免了不必要的上下文切换和竞争条件。;3)使用多路 I/O 复用模型。
比如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞。
27. 能解释一下 I/O 多路复用模型?
I/O 多路复用是指利用单个线程来同时监听多个 socket,并且在某个 socket 可读、可写时得到通知,从而避免无效的等待,充分利用 cpu 资源。目前的 I/O 多路复用都是采用 epoll 模式实现,它会在通知用户进程 socket 就绪的同时,把已就绪的 socket 写入到用户空间,不需要挨个遍历 socket 来判断是否就绪,提升了性能。
其中 Redis 的网络模型就是使用 I/O 多路复用结合事件的处理器来应对多个 socket 请求,比如,提供了连接应答处理器、命令回复处理器、命令请求处理器;
在 Redis6.0 之后,为了提升更好的性能吗,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。
希望文章可以帮助到您,如果还需要想详细了解更多 Redis 内容,可以关注一下:Redis 篇_小扳的博客-CSDN博客