文章目录
1、服务器基本框架
这张图既可以用来描述一台服务器的基本框架,也可以用来描述一个服务器集群的基本框架,下面这张表说明了在两种情况下各个模块的功能
模块 | 单个服务器 | 服务器集群 |
---|---|---|
I/O处理单元 | 处理客户连接,读写网络数据 | 作为接入服务器,实现负载均衡 |
逻辑模块 | 业务进程或线程 | 逻辑服务器 |
网络存储单元 | 本地数据库,文件或缓存 | 数据库服务器 |
请求队列 | 各单元之间的通信方式 | 各服务器之间的永久TCP连接 |
2、两种高效的事件处理模式
2.1、Reactor模式(由同步I/O模型实现)
该模式下,主线程只负责监听文件描述符上是否有事件发生,所有的工作都在工作线程上完成。
使用同步I/O模型(epoll_wait
)实现的Reactor模式工作流程为:
- 主线程往
epoll
内核事件表中注册socket
上的读就绪事件。 - 主线程调用
epoll_wait
等待socket
上有数据可读。 - 当
socket
上有数据可读时,epoll_wait
通知主线程,主线程将socket
可读事件放入请求队列。 - 睡眠在请求队列上的某个工作线程被唤醒,它从
socket
读取数据,并处理客户请求,然后往epoll
内核事件表中注册该socket
上的写就绪事件。 - 主线程调用
epoll_wait
等待socket
可写。 - 当
socket
可写时,epoll_wait
通知主线程,主线程将socket
可写事件放入请求队列。 - 睡眠在请求队列上的某个工作线程被唤醒,它往
socket
上写入服务器处理客户请求的结果。
工作线程从请求队列中取出事件后,如果是读事件,执行读数据和处理请求的操作,如果是写事件,执行写数据的操作。所以,在Reactor模式中,没有必要区分读工作线程以及写工作线程。
2.2、Proactor模式(由异步I/O模型实现)
在该模式下,Proactor模式将所有的I/O操作交给主线程和内核来处理,工作线程仅仅负责业务逻辑。
使用异步I/O模型(以aio_read
和aio_write
为例)实现的Proactor模式的工作流程如下:
- 主线程调用
aio_read
函数向内核注册socket
上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时要如何通知应用程序。 - 主线程继续处理其他逻辑
- 当
socket
上的数据被读入用户缓冲区之后,内核将向应用程序发送一个信号,通知应用程序数据已经可用 - 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后,调用
aio_write
函数向内核注册socket
上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序 - 主程序继续处理其他逻辑。
- 当用户缓冲区的数据被写入
socket
之后,内核将向应用程序发送一个信号,以通知应用程序数据以及发送完毕。 - 应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭
socket
由于在这种模式下,连接socket
上的读写事件是通过aio_read/aio_write
向内核注册的,因此内核用过信号来向应用程序报告连接socket
上的读写事件。所以,这种模式下的主线程epoll_wait
调用仅仅用来检测监听socket
上的连接请求事件,而不能检测socket
上的读写事件。
2.3、模拟Proactor模式(由同步I/O模型模拟实现)
使用同步I/O模型(以epoll_wait
为例)模拟出的Proactor模式的工作流程如下:
- 主线程往
epoll
内核事件表中注册socket
上的读就绪事件。 - 主线程调用
epoll_wait
等待socket
上有数据可读 - 当socket上有数据可读时,
epoll_wait
通知主线程。主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。 - 睡眠在请求队列上的某个工作线程被唤醒,他获取请求对象并处理客户请求,然后往
epoll
内核时间表中注册socket
上的写就绪事件。 - 主线程调用
epoll_wait
等待socket
可写 - 当
socket
可写时,epoll_wait
通知主线程,主线程往socket
上写入服务器处理客户端请求的结果。
3、两种高效的并发模式
并发模式是指I/O处理单元和多个逻辑单元之间协调完成任务的方法。这里主要介绍两种并发编程模式:半同步/半异步模式和领导者/追随者模式。
3.1、半同步/半异步模式
这里的同步异步与I/O模型中的同步异步不同。
- I/O模型中,同步和异步区分的是内核向应用程序通知的是哪种I/O事件(是就绪事件还是完成事件),以及由谁来完成I/O读写(是应用程序还是内核)。
- 并发模型中,同步指的是程序完全按照代码序列顺序执行,异步指的是程序的执行需要由系统事件来驱动。
按照同步方式运行的线程称为同步线程,逻辑简单但运行效率低,实时性差。按照异步方式运行的线程称为异步线程,效率高实时性强,但是程序复杂难以调试,是很多嵌入式程序采用的模型。所以,对于服务器这样的既要求实时性有要求可以同时处理多个客户请求的应用程序,我们就应该同时使用同步线程以及异步线程来实现。
结合考虑两种事件处理模式以及几种I/O模型,则半同步/半异步模式存在许多变体,其中包括半同步/半反应堆模式。这种模式采用的事件处理模式是Reactor模式。
这种模式的缺点在于:
- 主线程和工作线程共享请求队列。主线程往请求队列中添加任务,或者工作线程从请求队列中取出任务,都需要对请求队列加锁保护,从而浪费CPU时间。
- 每个工作线程在同一时间只能处理一个客户请求。如果客户数量较多,而工作进程较少,那么请求队列中就会堆积任务对象,客户端的响应速度就会降低。如果通过添加工作进程来解决这一问题的话,则会导致工作进程切换导致的时间增加。
除此之外,可以通过将请求队列换成工作线程与主线程之间连接的管道,这样会使得这种半同步/半异步方式更加高效。
3.2、领导者/追随者模式
领导者/追随者模式是多个工作线程轮流获得事件源集合、轮流监听、分发并处理事件的一种模式。在任意时间点,程序都仅有一名领导者,他负责监听I/O事件,而其他线程都是追随者,他们休眠在进程池中等待成为新的领导者。此时,如果领导者检测到了I/O事件,那么它就会现在进程池中推选出新的领导者,然后去处理I/O事件。此时,新的领导者等到I/O事件,旧的领导者处理I/O事件,二者就实现了并发。
由于领导者线程自己监听了I/O事件并处理客户请求,因而领导者/追随者模式不需要再线程之间传递任何额外的数据,也无需向半同步/半反应堆模式那样在线程之间同步对请求队列的访问。但领导者/追随者的一个明显的缺点是仅支持一个事件源集合,因而无法让每个工作线程独立的管理多个连接。
《Linux高性能服务器编程》学习笔记