Redis面试问答

232 阅读9分钟

摘要

Redis是以键值对形式存储数据的内存数据库,因为其将数据存储在内存中,所以读写速度特别快,常用于作为缓存。该文章介绍了Redis常见数据类型、redis优势、线程模型、持久化、集群。持续完善与更新中。

1、五种常见数据类型结构及使用场景

redis是存储key-value对数据的数据库,它的key一般是String类型,但value有很多种,基本类型包括:

  • 字符串(String):本质是字符串数组,最大不能超过512M。

  • 链表(List):底层是一个链表,可以在链表头部、尾部删除或者插入数据,可以实现队列、栈的功能。

  • 哈希表(Hash):存储键值对集合的数据,常用于存储json格式数据。底层和ZSET类型,只是去掉了跳表。

  • 集合(Set):存储是数据是无序的集合,提供了两个几个之间的并较差集的计算。常用于存储好友列表,共同好友的计算。底层是一个Dict(哈希表),如果存储的元素是整数,那么将采用intSet编码,如果是字符,使用set编码。如果一开始是整数,未来插入了字符串,那么将会变为set编码。

    集合存储的是一个元素,使用Dict键值对,所以在这里是将要保存的值作为key,值为null。

  • 排序集合(SortedSet):存储值时需要指定每个值的分数,按分数排名有序的存储数据,默认升序。常用于排名榜业务。底层数据是一个Dict+跳表,其中Dict用于确保键唯一和快速查询,跳表用于排序,跳表结构如图: image.png

    显然:排序集合需要两种结构,很占存储空间,所以当元素数量小于128并且元素字节数小于64,将会使用ZipList(压缩链表)存储数据。

2、底层数据结构

指常见数据类型的底层数据存储结构。例如key的类型,链表中的数据,集合中的数据等底层都是简单动态字符串进行存储的。

2.1、简单动态字符串(SDS)

SDS和C语言字符串区别:

  • 它不以\0作为字符串结束的标识位,而是以长度来看,所有它能够存储\0这样特殊字符,是二进制安全的;

注意SDS为了和C语言兼容,所以字符串末尾存储了\0 的结束标识符

  • 获取字符串长度的时间复杂度为O(1);

  • 能够修改;

  • 动态的扩容。向容量满了的SDS中添加字符串,将会触发扩容,扩容机制是: * 新字符串长度<=1M,则新空间为扩展后的两倍+1。 * 超过1M,新空间长度为扩展后的长度+1M+1。

它的数据结构如下

      struct sdshdr {
          uint8_t len; // buf数组中已使用字节的数量
          uint8_t alloc; // buf数组中申请的总内存大小
          unsigned char flags; // 不同的头类型,用来标识存储大小。uint8最大值是256,也就是说该buf最能存储256个字节的数据。Redis定义了多个尺度的SDS。
          char buf[]; // 字符数组,存储数据
      }

2.2、跳表

本质是一个链表,但是

  • 其中的元素是按照分数升序排列的。
  • 每个节点包含多个用来增加查询效率的不同跨度的指针。

2.3、zipList

压缩链表,底层实际是一个数组,每个"结点"的结构体是由:

  • previous_entry_length:前一个结点从长度。
  • encoding:当前节点的数据类型及长度。
  • contents:当前结点的数据。

压缩链表可以通过encoding从前向后遍历,也可以通过previous_entry_length从后往前遍历。从而实现两端遍历的操作。

3、底层通信协议

3.1、RESP协议

Redis是CS架构的软件,即客户端-服务端,RESP协议是客户端和服务端通信的协议。它通过首个字节的字符来区分不同的数据类型,例如:

  • 单行字符串:首字节是+,后面跟上单行字符串,以CRLF("\r\n")结尾。
  • 错误:首字节是-,以CRLF("\r\n")结尾。
  • 数字:首字节是:,以CRLF("\r\n")结尾。
  • 多行字符串:首字节是$,后面紧跟字符串的长度,以\r\n结尾,再跟着需要读取的字符串,将会根据传入的字符串长度去读取后面的字符串。例如$5\r\nhello。

    多行字符串允许字符串内部存储\r\n这样的特殊字符串,所以是二进制安全的。

  • 数组:首字节是*,后面跟上数组元素个数,再跟上每个数组的元素,元素类型不受限制。

4、有MySQL不就够用了吗?为什么要用Redis这种新的数据库?

Mysql数据库是存在磁盘上的,读取速度慢,而Redis是内存数据库,访问内存比访问磁盘的速度快得多。直接访问内存能承受的请求远远大于直接访问数据库,所以Redis常常用于缓存,用来分担数据库的压力。

5、使用Redis的好处有哪些?

  • 数据以键值对的形式存储在内存中,访问速度快。
  • 数据类型丰富,能够存储字符串,列表、hash表、集合、有序集合等多种数据结构。
  • 支持事务,操作是原子性的
  • 特性丰富,可用于缓存,消息队列,过期删除。

6、缓存中常说的热点数据和冷数据是什么?

  • 热点数据:指访问次数较多的数据;
  • 冷数据:指访问频率低的数据;

注意:只有缓存热数据才有价值,对于大部分冷数据, 可能还未再次访问就已经过期了,所以,缓存冷数据是没有意义的。

7、Redis 为什么是单线程的而不采用多线程方案

Redis是放在内存中的数据库,其访问速度本身就很快,限制其性能的并不是CPU性能,更可能是网络带宽和机器的内存,所以不需要采用多线程。

8、单线程的Redis为什么这么快?

  • Redis的操作是基于内存的。
  • 使用单线程,避免了多线程的上下文切换。
  • 使用非阻塞IO多路复用机制。

9、IO多路复用

利于单个线程去监听多个FD,在某个任务的文件描述符(FD)准备就绪,可读、写时得到通知,从而避免无效等待,充分利用CPU资源。Linux系统监听的方式有:

  • select、poll、epoll

注意:select和poll只会通知用户进程有FD准备就绪,但不会通知具体是哪个FD,需要进程去遍历确认。而epoll则会直接通知用户进程就绪的FD是哪个。

10、了解Redis的线程模型吗?可以大致说说吗?

它包括套接字、IO多路复用程序、文件事件分派器、事件处理器组成。它采用IO多路复用程序去监听多个套接字,当套接字产生事件后,将事件放入队列中,由事件处理分派器从队列中取出事件交给对应处理器处理。文件分派器分派任务是单线程的,所以Redis是单线程的。事件处理器包括连接应答处理器、命令回复处理器、命令应答处理器。

image.png

11、Redis设置过期时间的两种方案是什么?

  • 定时删除:每隔一段时间,随机抽取一些设置了过期事件的key,检查是否已经过期,将过期的删除。
  • 惰性删除:当用到某个key时,再检查其是否过期,过期则删除。

12、定期和惰性一定能保证删除数据吗?如果不能,Redis会有什么应对措施?

不能,Redis有一个内存淘汰机制去删除数据,主要有4种方案:

  • LRU:淘汰最近最少使用的数据;
  • LFU:淘汰使用频率最低的数据;
  • TTL:淘汰将要过期的数据;
  • Random:随机淘汰数据;

13、Redis持久化机制

通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。持久化机制有两种:

  • 快照持久化(RDB):通过创建某一个时刻的快照去保存和持久化数据。
  • AOF持久化:每执行一次更改语句,都会将该命令写入AOF文件。

14、AOF重写

AOF持久化是将Redis执行的写入命令存入一个AOF文件中,恢复Redis时,加载文件重新执行命令即可。但久而久之,AOF文件会越来越大,非常占用内存,处理分析起来也需要较长时间,考虑到AOF中的很多写入命令是数据的中间状态,而不是最终状态,据此便有了AOF重写。

  • AOF重写是为了降低AOF文件的大小及指令的数量。做法是去除掉AOF文件中的中间状态命令,将多条指令合并,只保留最终状态。Redis在重写时,会fork一个子进程去完成重写操作,为了防止子进程重写AOF文件期间,输入到redis的指令丢失,Redis会将该期间的写入指令暂存在重写缓冲区,最后将其添加到重写后的AOF文件中。

15、双写一致性问题及解决方案

指数据库数据被更新后,还未将更新同步至缓存,就有新的请求来访问了还未更新的旧数据,导致数据不一致的情况。解决方案:

  • 先更新缓存、再更新数据库:一般不考虑,因为如果缓存更新成功、数据库更新失败,将导致缓存和数据库完全不一致,且很难察觉,因为缓存中一直有数据。

  • 先更新数据库,再更新缓存:一般不考虑。因为更新数据库后,更新缓存可能会失败。

  • 先删除缓存,再更新数据库: 因为在删除缓存和DB更新完之间,可能有新的请求访问了数据库并携带旧的结果存入了redis,从而导致数据不一致。可以使用延迟双删的办法解决,即更新完数据库后,延迟几秒再删除一次缓存。

    弊端:还是会小概率出现数据不一致的情况。因为在这个时间差内,缓存中脏数据 可能会被其它进程读取。

  • 先更新数据库再删除缓存:在更新数据库并删除缓存后,网络中可能存在读取了旧数据正准备用旧数据更新缓存的线程,此时对缓存进行了更新,而导致数据不一致(极短的时间不一致)。可以使用延迟双删解决该问题。

16、Redis集群详情

16.1 Redis主从架构及哨兵

主从架构是用来保证Redis的高可用,采用一个主节点、多个从节点进行架构。数据修改操作都是在主节点进行,从节点负责同步主节点数据,并提供读数据的服务,当主节点挂掉后,选择一个从节点作为新的主节点,怎么选?则是由哨兵控制的哨兵通常是由三个及三个以上的服务器组成,它会监控主节点与从节点的状态,当主节点挂掉后,由哨兵集群中推选出来的某个哨兵去选择从节点作为新的主节点。

16.2 Redis集群

主从架构保证了高可用,但处理请求只有一个Redis,性能不足。Redis集群保证了高并发。它采用了多个Redis主从集群处理请求,并使用一致性hash算法去选择服务器处理请求。一致性hash的算法是将真实主从集群数量映射至16384个虚拟节点槽位,每次数据请求时,计算数据hash值对16384取模,得到其需要放入的虚拟槽位,根据虚拟槽位操作对应的主从集群。

参考资料

Redis01-20 | 阿秀的学习笔记 (interviewguide.cn)