当前位置: 首页>后端>正文

收集几十位大厂面试者的面试题及见解-Redis模块

一 Redis基本数据类型

1.1 Redis中数据类型有哪些?底层实现是什么?不同的数据类型运用到什么场景?

String、hash、list、set、sorted_set/zset
hash 底层是hash表实现的数据存储。
list 能存储多个数据,按照插入顺序排序,底层使用双向链表实现。
set 和hash存储结构完全相同,仅存储键值,不存储value。且键值不允许重复。存储大量数据,且快速查询。
zset 是字典(dict) + 跳表(skiplist),数据比较少的时候用ziplist编码结构存储。是有序集合,不允许有重复的数据结构,每一个元素关联double类型分数。通过分数对集合中元素从小到大排序。成员唯一的,分数不唯一的。

string 微博上大v的访问量存储。
hash 商品的抢购可以用,店铺的id为key,商品id为filed,数量为value。
list 对有顺序的信息管理
set 用于黑白名单。

1.2 Redis中哪里用了跳表?数据结构是什么?跳表插入和删除的大概过程,跳表相对普通链表有什么优势,是怎么提高访问效率的?

zset底层就使用了跳跃表,是由多层多链表实现。
在进行数据插入时候,先插入底层,然后利用算法(类似抛硬币算法),如果正面插入l2层,然后继续利用算法计算是否还继续向上层插入,当判断不需要插入,元素就插入结束。
删除元素,从上到下找到元素,找到删除,一直到最底层。

跳跃表在查询时候速度很快,因为在跳跃表中每个列表都是有顺序的。从上层往下查找,每一层查询到查询结束,如果比某一个节点大向右边,比一个节点大向左边。然后一层层查询。

1.3 整数集合、压缩列表实现原理?

是集合(set)的底层实现之一,当一个集合(set)只包含整数值元素,并且这个 集合的元素不多时,Redis就会使用整数集合(intset)作为该集合的底层实现。

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表只包含少量列表项时,并且每个列 表项时小整数值或短字符串,那么Redis会使用压缩列表来做该列表的底层实现。 压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数 据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整 数值。

1.4 SDS动态字符串的优点是什么?

  1. len 保存了SDS保存字符串的长度 。
  2. buf[] 数组用来保存字符串的每个元素。
  3. free记录了 buf 数组中未使用的字节数量 。

好处:获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。

1.5 Redis事务回滚?

Redis中是不支持回滚的,可以使用lua脚本执行redis条命令。

1.6 Redis中数据字典如何更新?hash 冲突怎么办,rehash,负载因子?

是一种用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。redis地址底层是hash表实现。

1.7 Redis中的数据类型有哪些 bitmap和grap类型用法

1.8 zset的zrange时间复杂度?

1.9 Redis的一致性哈希算法?

1.10 redis和memcached的区别?

1.11 Redis为什么不用hashmap?

二 Redis应用场景

2.1 Redis除了当做缓存还用来做什么?

可以做队列,也可以做分布式锁

2.3 Redis的热key问题如何解决?

  1. 增加redis集群中的节点,让访问打散到不同的节点。
  2. 利用二级缓存,走jvm内存中。

2.4 Redis的lua脚本,为什么能保证原子性?如果lua脚本在库存扣减完以后执行出错怎么办?

三 Redis持久化

3.1 Redis持久化有哪些?实现原理?有什么区别,使用场景?

两种方式持久化:RDB,记录二进制数据,即快照方式。AOF,记录读写操作过程,即日志持久化。

RDB进行持久化,发送指令,调用fock函数创建一个子线程,进行持久化,生成一个二进制文件。返回信息。
AOF进行持久化,发送指令,调用fock函数创建一个子线程,从写aof文件,响应信息。
即持久化就是创建一个子线程来进行。

RDB(快照),可以手动调用持久化,也可以配置自动化持久化。根据一个时间段内变化的频率来进行持久化。持久化速率快。
AOF(快照),持久化方式三种,每次、每秒、系统控制。持久化相对慢点,但是有效防止数据丢失。
如果数据十分敏感,采用AOF持久化,如果想要快速数据恢复,采用RDB。

3.2 AOF日志量太大时redis如何处理?

会执行AOF的重写,无效指令直接忽略,有效指令但是已经过期了不写入AOF中,对同条数据操作指令合并一条。

四 Redis过期数据删除策略和Redis淘汰策略

4.1 Redis中过期数据删除是什么?

过期数据是一块独立内存,filed是value数据内存地址,value是过期时间。进行过期处理时候,对时间进行检测,找出过期的对应的地址,然后进行操作。

删除策略有:定时删除(过期删除)、惰性删除(用到时删除)、定期删除(随机选若干个,对过期数据删除。如果过期数据比例大于阙值循环操作)。

4.2 Redis内存淘汰策略是什么?

在redis执行命令时候都会检查内存是否充足,如果不充足将要删除一些数据叫内存淘汰。淘汰策略为:
检查易失数据、检查全库数据、放弃数据驱逐。

五 Redis主从架构、分布式集群架构、哨兵机制

5.1 Redis实现主从复制原理、过程?

分为三个阶段:建立连接、数据同步、命令传播。
建立连接
建立slave到master连接,使master能识别slave,并保存slave端口。

数据同步
复制master全部数据到slave,请求同步数据,创建RDB同步数据,在slave恢复RDB数据,即全量复制。master发送在RDB同步过程的数据到slave,同步数据完成,复制缓冲区数据,部分复制。

命令传播
master接受到数据变更指令,发送给slave。slave执行接受指令。

命令传播过程中的部分复制
slave向master发送心跳,携带runid和复制偏移量。master对主节点的runid和携带过来runid进行比对,如果不对进行全量复制。如果匹对成功比较偏移量,如果偏移量不一样,进行部分复制。

5.2 Redis分布式集群实现原理?

  1. 引入了槽的概念,一个redis集群包含 16384 个哈希槽。
  2. 集群时,会将16384个哈希槽分别分配给每个Master节点,每个Master节点占16384个哈希槽中的一部分。
  3. 执行GET/SET/DEL时,都会根据key进行操作,Redis通过CRC16算法对key进行计算得到该key所属Redis节点。
  4. 根据key去指定Redis节点操作数据。

5.3 使用哨兵机制,解决redis集群故障转移。

哨兵是实现redis高可用的,监控集群的主从变化,进行故障转移。

哨兵节点监控
三个定时任务:如下
第一个定时任务,每隔十秒钟,每个sentinel节点向redis节点发送信息。向主节点发送info,获取最新的salve信息。当有新的节点能立刻感知到。完成故障转移,通过info命令完成新的拓扑结构。

第二个定时任务,每隔两秒,每个sentinel节点向sentinel:hello频道发送当前节点信息和对主节点的判断。所有节点都订阅这个频道,了解其他节点信息和其他节点对主节点的判断。发现新的sentinel节点,将新的sentinel节点信息保存并建立连接。sentinel交换主节点判断的状态,做为后面客观下线和主节点选举的依据。

第三个定时任务,每隔1秒,每个sentinel会向其他sentinel节点和redis节点发送ping指令,判断当前节点是否可达,检查每隔个节点的健康状态。
故障转移过程
当主服务器故障不能工作时候(哨兵对当前主节点做了主观下线),sentinel会自动做一次故障转移,从主节点的从节点选择做为主节点(哨兵选举新leader),然后从节点和原来主节点从新复制新节点数据。客户端连接老的主节点时候,集群向客户端返回新的主节点。

5.4 Redis集群中的主观下线和客观下线是什么?

主观下线:一个哨兵判断当前主节点宕机。
客观下线:多个哨兵判断一个主节点宕机了,并且多个哨兵交流后对主节点做出宕机判断。

5.5 redis缓存与数据库不一致解决方式?

使用Canal监听数据库指定表的增量变化,在Java程序中消费Canal监听到的增量变化,并在Java程序中实现对Redis和Nginx缓存更新。

5.6 Canal实现原理是什么?

  1. Canal伪装成slave,向master发送drump协议。
  2. master接受命令之后,向slave(Canal)发送binary log。
  3. Canal开始解析binary log。

六 Redis的分布式锁

6.1 分布式锁是什么?

同一个时间多个调用方加锁竞争,只有一个调用方加锁成功。

6.2 分布式锁实现原理?

  1. Redis是单线程,所有让多个调用方请求排队。
  2. 利用客户端三个API
    setNx(),向redis存key-value,只有key不存在则设置成功,否则返回0,体现同一个key互斥性。
    expire(),设置key的过期时间,避免死锁。
    delete(),删除key,释放锁。

6.3 如何保证释放锁是自己加的锁?

在加锁的时候,放入唯一标识(一般为线程id)。在解锁时候判断传入的值和加锁时候唯一标识是否一样。如果是一样可以解锁,不一样不能解锁。
因为释放锁会有很多人同事操作,但是我们在释放锁时候,先查询比对,是同把锁进行释放。但是这是两步操作,redis中我们无法保证原子性,因此采用lua脚本完成查询删除。

6.4 在执行业务时候,锁过期。如何解决?

因为业务执行时间和很多因素有关,无法判断业务执行时间。如果业务还没执行时候,锁过期了。会有其他线程获取锁,执行业务,无法保证原子性。解决方案,在添加锁时,添加一个守护线程,开启一个定时任务每隔一段时间对于未释放锁增加过期时间。任务执行完,释放锁,关闭守护线程。解决锁续期。

6.5 Redisson实现分布式锁的API

getLock(lockKey)获得锁对象。
lock(10, TimeUnit.SECONDS),设置锁失效时间。
tryLock(),使用看门狗策略。对于未设置过期时间的锁,起一个线程在锁快要过期,对锁不停添加过期时间(默认30秒)。保证任务结束之前锁有效。
unlock(),释放锁。

6.6 Redisson在获取锁、定义过期时间、释放锁原理分析。

获得锁(tryLock):
检查key是否被占用,没有设置key和唯一标识初始值为1,并设置过期时间。
被占用,检查下value是否也匹配,如果匹配表示当前线程持有锁,重用次数+1,设置过期时间。
返回key失效时间。

释放锁
判断key和线程id标识是否匹配,如果步匹配,锁已经占用,直接返回。
如果当前线程持有锁,value-1,进入重入操作。大于0,设置过期时间。小于0直接删除key,并发布锁释放消息,通知其他线程申请锁。

看门狗实现原理
根据过期时间、时间单位、线程id获取锁,如果设置了锁的过期时间,直接创建锁。如果没有设置锁的过期时间,把永不过期设置为默认30秒过期,并且创建异步任务,如果没有获取到锁,则什么都不做。如果获取到了锁,则创建一个线程对当前线程id的锁进行延时。

6.7 在集群中,已经获取锁但是在数据同步前,主节点挂掉。salve节点变成主节点。其他线程也能获取锁,破坏原子性。

Redisson是分布式锁的第三方类库,对各种锁都有实现,我们只需要调用api就行。解决了锁续期问题和单机问题。

Redisson中的红锁解决了上述问题,如下
获取所有节点redis中的锁(获取锁时间远小于锁释放时间,避免客户端等一个已经关闭客户端),在获取之前先看看获取锁时间是否大于失效时间(获取当前锁是否已过期),没有过期,检查是否有半数以上的redis中获取锁。是获取锁成功。不是,获取锁失败。然后redis节点释放获取失败锁。获取失败后,可以在一定时间尝试获取锁,设置尝试次数。

要开启AOF持久化方案
防止所有节点挂掉,锁信息丢失,导致多线程同时执行。设置持久化时间为1秒。在一秒重启仍然会有数据丢失。

七 Redis网络模型

7.1 Redis是单线程为啥这么快?

  1. 因为redis大部分操作都是内存中。
  2. 因为redis操作数据是单线程,避免多线程的切换和竞争。
  3. redis底层通信时采用多路复用技术。大量并发下,提高系统吞吐量。

7.2 描述下多路复用(select apoll)。

支持应用程序将一个或者多个fd交给内核,内核检测fd上的状态变化(连接读写等)。因为应用程序对磁盘读、网络数据读写都是通过内核获取访问权限的。

epoll网络通信模型

  1. 应用程序调用epoll_create方法返回fd假设为5(红黑树根节点).
    创建epoll对象,创建监听树,创建队列(双向链表)。
    执行socket函数,返回fd为6.
    将fd为6绑定6379端口,并进行监听fd为6(监听连接对象的fd)。
    让epoll_ctl变为异步。
  2. 调用epoll_ctl函数
    将监听对象fd:6放到监听树上。当有客户端连接6379端口,fd:6监听到连接事件,将事件信息放到消息队列中。
  3. epoll_wait函数(轮询调用)
    应用程序从队列中取出事件信息,如果是连接事件,则调用accept方法(异步)返回fd:8,并将8放到以5为根节点的监听树上,并设置监听事件。如果发生对事件,将读事件放到消息队列中。应用程序调用系统函数read方法,进行消息读取。
  4. 下次轮询调用 epoll_wait
    如果fd:6发生多个连接事件,fd:8发生读事件。应用程序从消息队列取出全部事件信息,依次处理。
    即没有fd模型限制,不会随着fd增加导致IO效率降低。不需要将fd每次拷贝到内核,一次拷贝,多次复用。

select通信模型(IO多路复用)
应用进程自己维护fd set集合,然后调用select将fd集合传递给内核,内核检测就绪的fd,返回给应用进程,应用进程进行事件信息处理。应用程序循环调用select系统函数。

7.3 Redis6.0是怎么实现多线程?

划分了主线程和多个IO线程,Io线程负责网络连接,命令执行还是主线程来执行。多线程是默认关闭的。如果要使用,修改配置打开多线程,配置线程数,建议线程数小于内核数。

八 缓存穿透、缓存击穿、雪崩

8.1 缓存穿透是什么现象?怎样解决?

用户查询数据,先查询缓冲没有查询到数据,然后再查询数据库也没有查询到数据。用户反复刷新查询。

第一种解决方案:查询缓冲没有查询到数据,然后查询数据库也没查询到数据。将null作为value存储到缓存中。当下次查询直接将null返回。
第二种解决方案:在查询数据时候,先查询布隆过滤器查询。如果查询到再去缓存种查询,如果没有查询到直接返回。
布隆过滤器介绍:大规模数据下不需要精准过滤场景。布隆过滤器含有一个数组两个hash函数,一个数经过两个hash函数计算后产生两个数,放在数组中。当有一个新的数m1,经过两个hash函数计算出两个数有一个在集合中证明m1在布隆过滤器中。

8.2 缓存击穿是什么现象?怎样解决?

缓存过期,但是此时有大量的请求访问key。导致大量请求查询数据库。

方案一:设置一个定时器,主动更新缓存。
方案二:多级缓存,在redis存储数据设置永不过期,用户查询数据时候,先去nginx去查询数据,如果有直接返回。没有查询redis数据,并将redis中数据放到nginx中,并设置过期时间。
方案三:Nginx缓存队列术,多个客户端请求一个缓存中不存在的文件。只允许第一个请求发送到服务端,其他请求在缓存中取到取到信息。

8.3 雪崩是什么现象?怎样解决?

大量的缓存失效,导致大量请求查询数据库。

解决方案:多级缓存、缓存预热。


https://www.xamrdz.com/backend/33j1945736.html

相关文章: