|
|
@ -119,6 +119,8 @@ org.springframework.boot.env.YamlPropertySourceLoader
|
|
|
|
|
|
|
|
|
|
|
|
## 线程模型
|
|
|
|
## 线程模型
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
Redis内部使用文件事件处理器`File Event Handler`,这个文件事件处理器是单线程的所以Redis才叫做单线程的模型。它采用`I/O`多路复用机制同时监听多个`Socket`,将产生事件的`Socket`压入到内存队列中,事件分派器根据`Socket`上的事件类型来选择对应的事件处理器来进行处理。文件事件处理器包含5个部分:
|
|
|
|
Redis内部使用文件事件处理器`File Event Handler`,这个文件事件处理器是单线程的所以Redis才叫做单线程的模型。它采用`I/O`多路复用机制同时监听多个`Socket`,将产生事件的`Socket`压入到内存队列中,事件分派器根据`Socket`上的事件类型来选择对应的事件处理器来进行处理。文件事件处理器包含5个部分:
|
|
|
|
|
|
|
|
|
|
|
|
- **多个Socket**
|
|
|
|
- **多个Socket**
|
|
|
@ -127,6 +129,12 @@ Redis内部使用文件事件处理器`File Event Handler`,这个文件事件
|
|
|
|
- **文件事件分派器**
|
|
|
|
- **文件事件分派器**
|
|
|
|
- **事件处理器**(连接应答处理器、命令请求处理器、命令回复处理器)
|
|
|
|
- **事件处理器**(连接应答处理器、命令请求处理器、命令回复处理器)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 通信流程
|
|
|
|
|
|
|
|
|
|
|
|
客户端与redis的一次通信过程:
|
|
|
|
客户端与redis的一次通信过程:
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|

|
|
|
@ -149,7 +157,68 @@ Redis内部使用文件事件处理器`File Event Handler`,这个文件事件
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 效率高
|
|
|
|
### 文件事件处理器
|
|
|
|
|
|
|
|
- **基于 Reactor 模式开发了自己的网络事件处理器(文件事件处理器,file event handler)**
|
|
|
|
|
|
|
|
- 文件事件处理器 **使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字**,并根据套接字目前执行的任务来为套接字关联不同的事件处理器
|
|
|
|
|
|
|
|
- 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件
|
|
|
|
|
|
|
|
- 文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### I/O多路复用
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
I/O多路复用的I/O是指网络I/O,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。意思说一个或一组线程处理多个TCP连接。最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程。
|
|
|
|
|
|
|
|
I/O多路复用使用两个系统调用(select/poll/epoll和recvfrom),blocking I/O只调用了recvfrom;select/poll/epoll 核心是可以同时处理多个connection,而不是更快,所以连接数不高的话,性能不一定比多线程+阻塞I/O好,多路复用模型中,每一个socket,设置为non-blocking,阻塞是被select这个函数block,而不是被socket阻塞的。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**select机制**
|
|
|
|
|
|
|
|
**基本原理**
|
|
|
|
|
|
|
|
客户端操作服务器时就会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)、和exceptfds(异常)。select会阻塞住监视3类文件描述符,等有数据、可读、可写、出异常 或超时、就会返回;返回后通过遍历fdset整个数组来找到就绪的描述符fd,然后进行对应的I/O操作。
|
|
|
|
|
|
|
|
**优点**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 几乎在所有的平台上支持,跨平台支持性好
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**缺点**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 由于是采用轮询方式全盘扫描,会随着文件描述符FD数量增多而性能下降
|
|
|
|
|
|
|
|
- 每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间)
|
|
|
|
|
|
|
|
- 默认单个进程打开的FD有限制是1024个,可修改宏定义,但是效率仍然慢。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**poll机制**
|
|
|
|
|
|
|
|
基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制(使用链表的方式存储fd)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**epoll机制**
|
|
|
|
|
|
|
|
**基本原理**
|
|
|
|
|
|
|
|
没有fd个数限制,用户态拷贝到内核态只需要一次,使用时间通知机制来触发。通过epoll_ctl注册fd,一旦fd就绪就会通过callback回调机制来激活对应fd,进行相关的io操作。epoll之所以高性能是得益于它的三个函数:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `epoll_create()`:系统启动时,在Linux内核里面申请一个B+树结构文件系统,返回epoll对象,也是一个fd
|
|
|
|
|
|
|
|
- `epoll_ctl()`:每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数
|
|
|
|
|
|
|
|
- `epoll_wait()`:轮训所有的callback集合,并完成对应的IO操作
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**优点**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
|
|
|
|
|
|
|
|
- 效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
|
|
|
|
|
|
|
|
- 内核和用户空间mmap同一块内存实现(mmap是一种内存映射文件方法,即将一个文件或其它对象映射到进程的地址空间)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
例子:100万个连接,里面有1万个连接是活跃,我们可以对比 select、poll、epoll 的性能表现:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `select`:不修改宏定义默认是1024,则需要100w/1024=977个进程才可以支持 100万连接,会使得CPU性能特别的差
|
|
|
|
|
|
|
|
- `poll`: 没有最大文件描述符限制,100万个链接则需要100w个fd,遍历都响应不过来了,还有空间的拷贝消耗大量资源
|
|
|
|
|
|
|
|
- `epoll`: 请求进来时就创建fd并绑定一个callback,主需要遍历1w个活跃连接的callback即可,即高效又不用内存拷贝
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 执行效率高
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Redis是单线程模型为什么效率还这么高?**
|
|
|
|
|
|
|
|
|
|
|
|
- `纯内存操作`:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础
|
|
|
|
- `纯内存操作`:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础
|
|
|
|
- `非阻塞的I/O多路复用机制`:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间
|
|
|
|
- `非阻塞的I/O多路复用机制`:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间
|
|
|
|