Redis面试相关问题总结
前言:
我觉得无论做什么还是要知行合一,至少对于我来说只看理论是记忆不长久的,底层的细节还是无法真正体会的。前些日子也是还在焦虑实习的事情,但是也是看到了一句话:“让他们卷去吧!我躺平了,至少以后是饿不死的。”我在想确实是饿不死的,我卷也是为了以后生活质量的提高,但是无论干什么事情还是自己心里喜欢,自己愿意才能事半功倍,我喜欢坐在电脑桌前写着这些代码,与其焦虑,不如真正的放慢速度,好好的去干一些自己喜欢的事情...
于是我便想着,反正要背八股,不如真正的放慢速度,把每一章的知识重新复习一遍,其中的场景都实现一遍,每一个知识点都去做个笔记,每一个面试题都用自己的话说一遍,真正的去享受知识...
以下笔记都是基于黑马程序员的面试题写的:
Redis中关于缓存三兄弟(穿透、击穿、雪崩)和数据一致性问题的解决方案实例-CSDN博客
Redis 持久化机制_redis save 机制-CSDN博客
Redis数据过期策略_redis 如何解决数据过期-CSDN博客
Redis数据淘汰策略-CSDN博客
Redis分布式锁_redis锁-CSDN博客
Docker搭建主从同步集群-CSDN博客
Redis集群方案——主从复制-CSDN博客
Docker搭建Redis哨兵集群-CSDN博客
Redis集群方案——哨兵机制-CSDN博客
Docker搭建Redis分片集群-CSDN博客
Redis集群方案——Redis分片集群-CSDN博客
Redis网络模型-CSDN博客
Redis面试相关问题总结-CSDN博客
什么是缓存穿透?怎么解决?
缓存穿透就是一个本不存在的key,穿过缓存直接访问数据库数据库,由于数据本就不存在,这样的访问是没有意义的,会对数据库造成不必要的压力。
有以下两种常见的解决方案:缓存空对象和布隆过滤器过滤。但是一般我们会选择布隆过滤器过滤这种解决方案。
首先,缓存空对象方案,就是当数据库查询返回空时,也将这个空结果进行缓存,并设置一个较短的过期时间。
其次,布隆过滤器方案,就是在缓存查询和数据库查询前先进行布隆过滤器过滤掉不存在的key。
请你介绍一下什么是布隆过滤器?
布隆过滤器的主要作用是用于检索一个元素是否存在于一个集合中。
我最长使用的是redisson实现的布隆过滤器,它的底层原理采用的是位图。初始化时会先创建一个很大的数组,里面存放的是0、1数据,一开始是0,key会经过3次哈希计算再模以数组长度来确定的存储下标,将0改为1,这样3个下标就能标明一个key的存在。当然布隆过滤器也是会有误判率的,这个我们是可以设置的,一般不会超过5%,这也是我们能够接受的误判率。
什么是缓存击穿?怎么解决?
缓存击穿就是对于一个热点key,在过期的一瞬间,有大量的请求进来,这些请求发现缓存中数据过期,就会直达数据库,导致数据库压力过大可能就会崩溃。
常见的解决方法有以下两种:互斥锁和逻辑过期时间。
首先,互斥锁方案适用于强一致场景:当发现key过期,其中一个线程就会获取互斥锁去进行缓存重建逻辑,其余线程获取锁失败则会休眠重试请求。
其次,逻辑过期方案适用于追求高性能且允许短暂不一致场景:设置缓存永不过期,但是设置缓存逻辑过期时间,当发现key逻辑时间过期,就会异步开启一个线程进行缓存重建逻辑并返回过期数据,异步重建逻辑中仍需要使用互斥锁,这样保证了每次逻辑过期的缓存重建不会被多次执行。
什么是缓存雪崩?怎么解决?
缓存雪崩是指同一时段大量的缓存key同时过期或者redis服务宕机,导致大量的请求
到达数据库,带来巨大压力。
主要的解决方案是给不同的key设置不同的过期时间,比如可以设置过期时间为30分钟,再在其基础上设置1-5分钟的随机过期时间。这样每一个缓存的过期时间不同,就会大大降低失效的重复率。
你是如何保证Redis与MySQL的双写一致性?
无论是先操作数据库还是先删除缓存,都会导致Redis与MySQL数据不一致问题。针对于不同的场景,应采用不同的方案进行解决,我常用的的有以下几种解决方案:旁路缓存模式、基于Redission的读写锁和消息中间件方案。
首先,对于旁路缓存模式,也就是先更新DB再删除Cache,适用于读多写少且允许短暂不一致的场景。此方案并非完全避免不一致,但是通过压缩异常发生的时间窗口降低概率,同时不一致可被后序操作自动修改。
其次,对于基于Redission的读写锁方案,适用于强一致性的场景。在执行写操作时读写互斥,给线程加上写锁,则其余线程无法进行读写操作,只能等待写锁释放。在执行读操作时读不互斥,写互斥,线程可以同时加读锁,但是有读锁就不可以加写锁。这样就保证了对MySQL和Redis的更新操作具有原子性,实现强一致性。
最后,对于消息中间件方案,我最常使用的是Redis的Stream流,适用于写入频率较高且允许短暂不一致的场景。在执行写操作时,操作完数据库后将需要更新缓存的消息投放到stream流中发送给消费者,在业务消费者启动前要先加载消费者组,消费者从消费者组中读取消息,执行缓存更新业务。
你听说过延迟双删吗?为什么不使用它?
延迟双删就是在执行业务前先删除一次缓存,然后再修改数据库,最后延迟一段时间后再执行一次删除缓存操作。这样确实能降低双写不一致的概率,但是延迟的时间我们不好确实,仍然会导致脏读的风险。
你知道redis的持久化机制吗?
redis中主要提供了两种持久化机制:RDB和AOF。
首先,RDB又叫做redis数据备份文件或者redis数据快照,也就是说把内存中的所有数据都保存到磁盘中,当redis重启服务时数据会重新从磁盘中加载,从而实现了数据的持久化。
然后,AOF又叫做追加文件,也可以看作是命令日志文件,其中记录的是redis中每一次写操作的命令,当redis服务宕机恢复时会重新执行一遍AOF中的所有命令来恢复数据,从而实现了数据的持久化。
这两种方式你使用过吗?或者它们的使用场景分别是什么?
首先,RDB是一个二进制文件,能够更快速的存储和恢复数据,但是服务器停机可能会导致最后一次备份数据的丢失,因此如果可以容忍数分钟的数据丢失,追求更快的启动速度,可以选择该方式。
然后,AOF相对与RDB文件体积相对较大,且恢复速度较慢,但是设置合适的刷盘策略,如每秒刷盘,最多导致丢失1秒的数据,数据的安全性更高,因此如果追求更高的数据安全性,可以选择该方式。
最后,在实际的开发中,我使用最多且更倾向于两者混合持久化。
Redis的数据过期策略有哪些?你能详细解释一下吗?
redis中提供了两种数据过期策略:惰性删除和定期删除。
首先,惰性删除就是对于设置TTL的key,当我们访问的时候才去检查是否过期,过期则删除该key。
然后,定期删除就是每隔一段时间redis就会抽取一些key检查是否过期,过期则删除该key。其中定期删除又分为两种模式:SLOW模式和FAST模式。首先,SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms;然后,FAST模式执行频率不固定,但两次间隔不低于2ms,每次执行不超过1ms。
最后,redis的过期删除策略是:惰性删除+定期任务两种配合使用。
redis的内存淘汰策略有哪些?
首先,在了解redis的内存淘汰策略之前,我们需要了解到2个核心的算法概念:LRU最近最少使用算法,优先淘汰最近最少使用的数据;LFU最少频率使用算法,优先淘汰最少频率使用的数据。
然后,redis中提供了8种内存淘汰策略,其可分为三大类:不淘汰策略、全体键淘汰策略和过期键淘汰策略。首先,redis默认就是不淘汰策略noeviction,不淘汰数据,但是内存满时也无法填加新数据;然后,全体键淘汰策略主要包含有allkeys-lru、allkeys-lfu和allkeys-random;其次,过期键淘汰策略主要包含有volatile-ttl、volatile-lru、volatile-lfu和volatile-random。
最后,在redis的config配置文件中,我们可以根据不同的业务场景来选择适合的淘汰策略
数据库中有1000万条数据,redis中只能存储20w条数据。如何保证redis中的数据都是热点数据?
我们可以使用allkeys-lru内存淘汰策略,挑选出最近最少使用的数据淘汰,那么留下来的就是热点数据。
Redis的分布式锁是如何实现的?
在redis中提供了一个命令setnx,由于redis是单线程的,当一个客户端对某一个key使用了命令后,在相应的key没有过期或删除时,其余的客户端是无法设置该key的。
你是如何控制redis分布式锁的有效时长呢?
redis的setnx指令并不好控制锁的有效时长,我最上使用的是redisson框架来实现这一问题的。
redisson框架中提供了一种锁的续期机制——看门狗机制。
首先,当一个线程获取分布式锁成功后,锁的默认持有时间为30秒,看门狗机制会每隔锁的持有时间%3,也就是10s来检测当前业务是否还持有锁,若持有锁则继续增加锁的持有时间,完成业务后会自动释放锁。
然后,若其他线程获取锁失败,则会一直循环获取,直到成功获取锁,当然这种循环是有一个阈值的。这样的好处就是在高并发情况下,能够很好的提高分布式锁的使用性能。
Redisson实现的分布式锁是可重入的吗?
redisson实现的分布式锁是可重入的,它的底层使用的是hash数据结构,记录的是线程的id和重入的次数。这个重入在内部就是判断是否是当前线程持有的锁,若是则重入次数+1,释放锁则-1,这样做是为了避免死锁的发生。
Redisson实现的分布式锁能解决主从一致问题吗?
不能解决,因为会有这种问题:线程1加锁成功后master节点宕机,线程2在新的master节点再次加锁,导致两个节点共持一把锁的问题。
但是可以使用redisson提供的红锁来解决,但是这样的话,性能就太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁
请你介绍一下主从同步
单节点redis的并发能力是有上线的,为了提升redis高并发下的性能,我们可以搭建redis的主从复制集群,实现读写分离。最常用的主从同步集群是一主多从,主节点负责写数据,从节点只负责读数据。
从节点是如何保持与主节点数据的一致的或者说一下主从同步的流程
主从同步主要是包含了两个阶段:全量同步和增量同步。
首先对于全量同步,它主要发生在从节点与主节点第一次连接时或从节点断开连接太久offset被覆盖时。
第一,slave携带replication id和offset向master发送数据同步请求,master通过replication id判断是否是第一次连接,若是第一次则返回master的节点信息,slave保存返回的信息。
第二,master执行bgsave命令生成RDB文件发送给slave,slave清除自己的数据,然后执行RDB文件。
第三,若master生成RDB期间有其他写命令传入,会被master保存到缓冲区日志中,最后发送给slave,slave执行缓冲区日志中的命令,这样就实现了主从节点的同步。
然后对于增量同步,它主要发生在slave重启或后期数据变化时。
第一,slave携带replication id和offset向master发送数据同步请求,master通过replication id判断是否是第一次连接,不是第一次连接则进行增量同步。
第二,master在缓冲区日志中获取offset之后的数据并发送给slave节点,slave节点执行命令完成同步。
怎么保证redis的高并发高可用?
redis提供了主从同步+哨兵模式保证了redis的高并发和高可用性。
首先,主从同步保证了redis的高并发性:单节点redis的并发能力是有上线的,我们可以搭建主从同步集群实现redis的读写分离:master负责写数据,slave只负责读数据。
然后,哨兵机制保证了redis的高可用性:哨兵机制可以实现主从集群的自动故障恢复,里面就包含了对主从服务的检测、自动故障恢复和通知;如果master故障,sentinel会重新选取一个slave作为新的master,当master恢复会自动下降为slave。同时当redis实现故障转移,sentinel会向redis客户端通知信息变化。
如何解决redis的集群脑裂问题
redis的哨兵模式一般会因为网络等原因出现脑裂问题。也就是,master、slave和sentinel处于不同的网络分区,sentinel心跳机制检测不到master,会重新选举一个slave作为新的master,但是旧的master并未下线,仍在写入数据,新的master无法同步,当网络恢复,旧的master下降为slave,就会导致丢失大量数据。
我知道的有以下几种方法可以避免和减轻脑裂问题:
第一,设置合适的哨兵quonum,一般为N/2+1(其中N为哨兵节点数)。
第二,启用主节点写入保护,在redis.conf中添加:至少要有1个从节点连接(min-slaves-to-write 1)和从节点复制延迟不超过10秒(min-slaves-max-lag 10)才能同步数据。
redis分片集群有什么作用?
redis分片集群是redis提供的一种高并发和高可用性的分布式方案,主要解决的是海量数据存储和高并发写的场景。
首先,集群中有多个master,每个master之间通过心跳机制相互检测,类似于哨兵机制,可以进行自动故障转移,保障了服务的高可用性。
其次,每个master还可以配置多个slave,提高了服务的高并发性。
最后,客户端可以访问任意的节点,最后请求可以被转发到相应的节点访问。
redis分配集群中的数据是如何进行存储和读取的?
redis分片集群中提出了哈希槽的概念,一共有16384个哈希槽,数据不是存储到节点上的,而是存储到哈希槽中,为节点分配一定范围的哈希槽。
key通过CRC16校验,然后对16384取余来决定存储到哪个哈希槽中。
读取也是相同的逻辑。
redis是单线程的,为什么还那么快?
其主要原因还是以下几点:
第一,redis是完全基于内存的,避免了磁盘 I/O 瓶颈。
第二,单线程避免了多线程下的上下文切换的竞争。
第三,使用epoll等高效的多路IO复用机制,单线程可以同时处理成千上万的客户端连接。
能解释一下IO多路复用模型吗?
IO多路复用就是利用单线程来监听多个socket,在某个socket可读、可写时通知单线程,从而避免了无效的等待,提升了CPU的性能。现在多数使用的是epoll模式的IO多路复用,在socket就绪通知时,也会将socket写入用户空间,不需要用户进程再循环询问socket是否就绪,从而提升了性能。
redis的网络模型就是使用IO多路复用模型结合事件派发机制来处理多个socket的,在redis6.0以后的版本,redis中使用了多线程来优化了命令请求处理器中的命令转换和命令回复处理器的事件回复处理,进一步提升了redis的性能。