Redis面试题 - 基于Redis7.0
1、 Redis的内存结构是什么样子的?
- RedisServer 每个Redis服务都包含一个RedisServer对象,保存了进程ID、DB实例(一个服务可以包含多个Redis数据库,用于隔离,一般用的就是第一个),事件、网络相关信息
- RedisDb 默认有16个数据库,保存的是字典Dict对象
- Dict字典,保存了数据的散列表对象,用于快速查找,用key value保存常用信息,内部其实包含了两个散列表 ht_table[0]和ht_table[1]
- ht_table,真正的散列表实现,底层是一个数组,用hash进行查询。平时使用ht_table[0],如果处于rehash操作,就使用ht_table[1]配合完成,是一种cow copy on write的思想,这样就不需要加锁了
- DictEntry 保存了redis的数据对象,由于Redis支持多种数据结构,所以value可以指向不同的数据类型对象。Redis为了统一,使用RedisObject进行封装。
2、 Redis有哪些数据类型,分别用了什么数据结构来实现?
- String 字符串,底层使用
SDS(Simple Dynamic String)实现。 - List 列表,底层使用linkedlist(3.2之前),ziplist,quicklist(3.2-7.0),listpack(7.0+)实现
- Sets,无序集合,底层使用intset或者散列表实现
- Hash 散列表,包含key value,底层使用散列表实现
- Sorted Set 排序集合,底层使用skiplist 或者 ziplist(7.0-) | listpack(7.0+)实现
- Geo 地理位置查询,使用GeoHash进行编码,SortedSet存储
- Bitmap 位图,可以使用在签到等场景中,底层使用SDS实现
- Hyperloglog,用来统计不完全精确的数据,比如DAU日活,不同用户访问UV,搜索数量,IP数量等。底层是基于概率学来完成的,生成一个16384个桶,hash之后取前几位取模入桶,计算最后往前的0的数量,多个桶取平均值。
- BloomFilter,布隆过滤器,底层基于散列表完成。4.0之后Redis提供插件支持布隆过滤器,当然也可以使用Redisson来完成。
接下来简单介绍几个核心数据结构
linkedlist
就是链表,但是占用内存大、内存不是连续的所以遍历起来效率不高。所以3.2之前,如果链表中元素占用字节数小于64,并且元素个数少于512,就会使用链表
ziplist
压缩列表
相比较链表,ziplist内存是连续的,同时节省了内存,少了prev和next指针。但是如果插入entry时如果长度不足,会引发扩容。
另外,他需要在每个元素数据中存放到上一个数据的总长度,这样才能反向遍历。所以容易出现数据更新之后连锁更新的问题
quicklist
结合了ziplist和linkedlist特点
外层用了一个
linkedlist,每一个节点指向一个ziplist用来存放多个数据
但是ziplist扩容的问题,依然没有解决
listpack
listpack在每个元素中存放了这个数据的长度,用于遍历,就不会出现连锁更新的问题了。
3、 Redis高性能的原因是什么?
- 基于内存实现
- IO多路复用模型,由于单线程容易因为被网络IO阻塞,所以redis采用了I/O多路复用模型,采用epoll(linux)+自研的ae事件框架,高性能处理网络事件
- 单线程模型,不需要加锁,也没有上下文切换的开销
- 高效的数据结构,上文已经介绍了
- 全局散列表,快速定位到key的位置
4、 IO多路复用模型到底是什么?
IO多路复用是一种同步IO模型,用于高效地处理多个输入/输出(I/O)操作,使单个进程或线程能够同时监视多个文件描述符(如套接字、文件等),以确定哪个描述符就绪,可以进行读写操作而不会阻塞。
一、基本概念
在传统的阻塞式 I/O 模型中,当一个进程调用 read 或 write 等 I/O 系统调用时,如果相应的 I/O 设备没有准备好数据或缓冲区已满无法写入,进程会被阻塞,直到 I/O 操作完成。这种方式在处理单个连接时可能比较简单直接,但在处理多个连接时效率低下,因为每个连接都需要一个单独的进程或线程来处理,并且在等待 I/O 操作时会浪费大量的 CPU 时间。
IO 多路复用的核心思想是使用一个或几个系统调用(如 select、poll、epoll 等)来同时监视多个文件描述符,当其中任何一个文件描述符就绪时,系统调用就会返回,告知程序哪个文件描述符可以进行读写操作。这样,程序就可以避免在每个连接上都阻塞等待 I/O 操作,从而提高了系统的并发性和效率。
二、常见的 IO 多路复用实现方式
-
select:select函数是最早出现的 IO 多路复用实现方式之一。- 它通过一个文件描述符集合来监视多个文件描述符的状态,当有文件描述符就绪时,
select函数返回。 select函数的主要缺点是可监视的文件描述符数量有限(通常为 1024),并且每次调用select时都需要将所有要监视的文件描述符从用户空间复制到内核空间,效率较低。
-
poll:poll函数与select类似,也是通过一个文件描述符数组来监视多个文件描述符的状态。- 与
select相比,poll没有最大文件描述符数量的限制。 - 但是,
poll也存在每次调用都需要将所有要监视的文件描述符从用户空间复制到内核空间的问题。 - 示例代码:
-
epoll:epoll是 Linux 特有的高性能 IO 多路复用实现方式。epoll通过在内核中维护一个事件表,避免了每次调用都将文件描述符从用户空间复制到内核空间的开销。epoll支持边缘触发(Edge Triggered)和水平触发(Level Triggered)两种模式,可以根据不同的应用场景进行选择。- 示例代码:
三、应用场景
- 网络服务器:在网络服务器中,通常需要同时处理多个客户端的连接请求。使用 IO 多路复用可以使服务器在单个进程或线程中高效地处理多个连接,避免为每个连接创建一个单独的进程或线程,从而减少系统资源的消耗。
- 文件服务器:文件服务器需要同时处理多个文件的读写请求。IO 多路复用可以使文件服务器在单个进程或线程中高效地处理多个文件的读写操作,提高文件服务器的性能。
- 数据库服务器:数据库服务器需要同时处理多个客户端的查询请求。IO 多路复用可以使数据库服务器在单个进程或线程中高效地处理多个客户端的查询请求,提高数据库服务器的性能。
四、优势
- 提高系统的并发性:通过单个进程或线程同时监视多个文件描述符,减少了进程或线程的创建和切换开销,提高了系统的并发性。
- 减少系统资源的消耗:相比于为每个连接创建一个单独的进程或线程,IO 多路复用可以在单个进程或线程中处理多个连接,减少了系统资源的消耗。
- 提高系统的响应速度:当有文件描述符就绪时,IO 多路复用可以立即通知程序进行处理,提高了系统的响应速度。
总之,IO 多路复用是一种高效的 I/O 处理方式,适用于需要同时处理多个 I/O 操作的场景。它可以提高系统的并发性、减少系统资源的消耗,并提高系统的响应速度。