一、redis 为什么快?¶
-
业务执行单线程,避免cpu不必要的上下文切换
-
存内存操作
-
I/O 多路复用+事件模型
-
高效的数据结构+合理的数据编码
视频补充:单线程模型为什么还能很快
-
单线程模型避免了多线程场景下频繁的线程切换、锁竞争和同步开销,这也是 Redis 在命令执行路径上保持简洁和高性能的重要原因。
-
Redis 所说的“单线程”,主要是指命令执行这条主路径是串行的;并不代表整个 Redis 进程只有一个线程。
-
从较新版本开始,Redis 已经引入后台线程或 I/O 线程来分担部分非命令执行工作,但核心命令处理依然是单线程完成。
二、I/O 模型介绍¶
视频补充:理解 I/O 模型前的两个基础概念
-
I/O 是 Input/Output 的缩写,通常指应用程序与外部世界之间的数据交换,在网络编程里常常就是数据从内核到用户态、再从用户态回到网络设备的过程。
-
一次 I/O 通常可以拆成两个阶段:
- 等待数据准备好
-
把数据从内核空间拷贝到用户空间
-
从这个角度看,常见的网络 I/O 模型可以归纳为五种:阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O、异步 I/O。
2.1 阻塞式 I/O 模型(Blocking I/O )¶
程序在进行 I/O 操作时会阻塞等待操作完成,直到读写完成后才能继续执行。
应用程序通过调用recvfrom接收数据,由于内核还未准备好数据,应用程序就会阻塞,直到内核准备好数 据,recvfrom完成数据复制工作,应用程序才能结束阻塞状态。

2.2 非阻塞式 I/O 模型(Non-blocking I/O)¶
程序在进行 I/O 操作时不会阻塞等待操作完成,而是立即返回,通过轮询方式不断查询 I/O 操作是否完成。
应用进程通过recvfrom调用不停地去和内核交互,直到内核准备好数据。如果没有准备好,内核会返回error,应用进程在得到error后,过一段时间再发送recvfrom请求。在两次发送请求的时间段,进程可以先做别的事情。

2.3 I/O 多路复用模型 (I/O Multiplexing )¶
程序使用 select/poll/epoll 等系统调用同时监听多个文件描述符,当其中任意一个文件描述符可以进行 I/O 操作时,程序才会阻塞等待该操作完成
IO多路转接是多了一个select函数,多个进程的IO可以注册到同一个select上,当用户进程调用该select,select会监听所有注册好的IO,如果所有被监听的IO需要的数据都没有准备好时,select调用进程会阻塞。当任意一个IO所需的数据准备好之后,select调用就会返回,然后进程再通过recvfrom来进行数据拷贝。
这里的IO复用模型,并没有向内核注册信号处理函数,所以它并不是非阻塞的。进程在发出select后,要等到select监听的所有IO操作中至少有一个需要的数据准备好,才会有返回,并且也需要再次发送请求去进行文件的拷贝。

2.4 信号驱动 I/O 模型 (Signal-driven I/O)¶
程序通过调用 sigaction 函数来安装一个信号处理函数,然后对文件描述符设置一个标记使其处于信号驱动 式 I/O 模式。当 I/O 操作完成时,会向进程发送一个信号,由信号处理函数来处理 I/O 完成事件。

2.5 异步 I/O 模型 (Asynchronous I/O )¶
程序发起 I/O 操作后可以立即返回,由内核来负责等待 I/O 操作完成,并通知进程完成事件,进程在接收 到通知后再来处理完成事件

三、reactor 模式¶
什么是 reactor 模式?
-
一种事件驱动处理模式
-
处理一个或多个输入并发请求
-
服务器通过 handler 对请求进行多路分发,分发给处理器
Reactor 模式一共有三种模式
3.1 单reactor单线程 模型¶
-
整个过程只有一个线程处理
-
redis 6.0 之前都是使用这个模型

3.2 单 reactor 多线程处理模型¶
-
一个线程接(reactor)只负责分发
-
处理由多线程处理
-
redis 6.0 使用的是这个模型的(稍微有点变化)

3.3 多线程 reactor 模型¶
-
由 reactor 由线程池担任
-
任务处理由另一个线程池承担

四、存储结构¶
redis默认有16个redisDb,通过select num 选择数据库,从0开始是第一个库。

4.1 渐进式 rehash¶
redis初始化以后,hash桶的大小是固定的,随着插入的元素增多,redis的hash桶会不够用,如果不对 hash进行扩容,就会一直在对应的hash位的链表上一直添加元素,就会导致hash查询由O(1)退化到O(n)。
redis就为了平衡性能和成本,会进行rehash。rehash有两块,一块是扩容,一块缩容。
- 扩容
- 防止key过多,hash冲突、链化导致的查询性能下降
- 5倍容量后才会触发扩容
-
往上取当前hash值最近的2的幂次方(比如原始大小2,插入到10个以后,直接扩容到16)
-
缩容
- 大量key过期后,hash过大,会有大量的空闲空间
- 降到1/10以下的使用量才会触发
如果是常规的rehash,铁定会阻塞redis,会影响redis的性能,那redis是怎么做的呢?redis使用一种渐 进式rehash进行扩容。

五、一次 get 命令的执行流程¶
5.1 redis 运行模型¶
想了解一个命令的执行,我们先看下redis 是怎么工作的。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
//2,每次循环前执行beforesleep
eventLoop->beforesleep(eventLoop);
//3,事件处理
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}
5.2 get 命令执行流程¶
我们通过一次get请求来看下redis是如何执行的。

六、redis 的数据清理机制¶
清理方式
-
惰性删除
-
定期删除
-
内存淘汰机制
6.1 惰性删除¶

6.2 定期清理¶

6.3 内存淘汰机制¶
Redis 的几种淘汰策略:
-
noeviction 无过期策略,内存满了就直接异常
-
volatile-lru 对有过期时间的 key 进行 lru 淘汰(越长时间没有被访问,越容易被淘汰)
-
allkeys-lru 对全局的 key 按 LRU 进行淘汰(越长时间没有被访问,越容易被淘汰)
-
volatile-lfu 对有过期时间的 key 进行 lfu 淘汰(经常不被访问的,越容易被淘汰)
-
allkeys-lfu 对全局的 key 进行 lfu 淘汰(经常不被访问的,越容易被淘汰)
-
volatile-random 对有过期时间的 key 进行随机淘汰
-
allkeys-random 对有所有的 key 进行随机淘汰
-
volatile-ttl 按时间进行过期淘汰