0x00 两种体系结构
在处理web请求时,通常有两种体系结构,分别为:Thread-based architecture
(基于线程)、Event-driven architecture
(事件驱动)。
基于线程的体系结构 (Thread-based architecture)
最最原始的网络编程思路就是服务器用一个while循环,不断监听端口是否有新的套接字连接,如果有,那么就调用一个处理函数处理。
这种方法的最大问题是无法并发,效率太低,如果当前的请求没有处理完,那么后面的请求只能被阻塞,服务器的吞吐量太低。
之后,想到了使用多线程,也就是很经典的connection per thread,每一个连接用一个线程处理。对于每一个请求都分发给一个线程,每个线程中都独自处理上面的流程。tomcat服务器的早期版本确实是这样实现的。
这种基于线程的体系结构通常会使用多线程来处理客户端的请求,每当接收到一个请求,便开启一个独立的线程来处理。
这种方式虽然是直观的,但是仅适用于并发访问量不大的场景,因为线程需要占用一定的内存资源,且操作系统在线程之间的切换也需要一定的开销,当线程数过多时显然会降低web服务器的性能。
并且,当线程在处理I/O操作,在等待输入的这段时间线程处于空闲的状态,同样也会造成cpu资源的浪费。
一个典型的设计如下:
事件驱动体系结构 (Event-driven architecture)
事件驱动架构 (Event-driven architecture
) 是一种软件体系范例,可促进时间的产生, 检测, 使用和响应。对于事件驱动系统而言,事件的捕获、通信、处理和持久保留是解决方案的核心结构。这和传统的请求驱动模型有很大不同。
这种方式会定义一系列的事件处理器来响应事件的发生,并且将服务端接受连接与对事件的处理分离。
许多现代应用都采用了事件驱动设计。因为事件驱动本身是一种编程方法,而不是一种编程语言, 因此事件驱动应用可以用任何一种编程语言来创建。事件驱动架构可以最大程度减少耦合度,因此是现代化分布式应用架构的理想之选。
事件驱动架构采用松散耦合方式,因为事件发起者并不知道哪个事件使用者在监听事件,而且事件也不知道其所产生的后续结果。
什么是事件?
事件, 是指系统硬件或软件的状态出现任何状态的重大改变。比如,tcp中socket的new incoming connection、ready for read、ready for write。
事件的来源可以是内部或外部输入。事件可以来自用户 (例如点击鼠标或按键)、外部源 (例如传感器输出) 或系统 (例如加载程序)。
事件与事件通知不同,后者是指系统发送的消息或通知,用于告知系统的其他部分有相应的事件发生。从形式上看,产生,发布,传播,检测或使用的是 (通常是异步的) 消息,称为事件通知,而不是事件本身,它是触发消息发出的状态更改。事件不会传播,它们只会发生。但是,术语“事件”通常被用作代名词来表示通知消息本身,这可能会引起一些混乱。这是由于通常在消息驱动的体系结构之上设计事件驱动的体系结构,在这种通信模式下,这种通信模式要求输入之一是纯文本消息 (消息),以区分应如何处理每种通信。
0x01 Reactor (反应器)模式
Reactor
设计模式是Event-driven architecture
的一种实现方式,处理多个客户端并发请求服务端的场景。
每种服务在服务端可能由多个方法组成, Reactor会解耦并发请求的服务并分发给对应的事件处理器来处理。
目前,许多流行的开源框架都用到了Reactor模式,如:大多数IO相关组件如Netty、Redis在使用的IO模式、linux上的libevent等,包括java的nio。
总体图示如下:
Reactor主要由以下几个角色构成:文件描述符 handle
、同步事件分离器 Synchronous Event Demultiplexer
、初始分发器 Initiation Dispatcher
、事件处理器 Event Handler
、Concrete Event Handler
。
1. 文件描述符 (Handle)
Handle
在linux中一般称为文件描述符
(File Descriptor),而在window称为句柄
,两者的含义一样。Handle是事件的发源地。比如一个网络socket、磁盘文件等。而发生在handle上的事件可以有connection、ready for read、ready for write等。
2. 同步事件分离器 (Synchronous Event Demultiplexer)
本质上是系统调用。比如linux中的select、poll、epoll等。比如,select方法会一直阻塞直到handle上有事件发生时才会返回。
3. 初始分发器 (Initiation Dispatcher)
初始分发器,也是reactor角色,提供了注册、删除与转发event handler的方法。当Synchronous Event Demultiplexer检测到handle上有事件发生时,便会通知initiation dispatcher调用特定的event handler的回调方法。
4. 事件处理器 (Event Handler)
事件处理器,其会定义一些回调方法或者称为钩子函数,当handle上有事件发生时,回调方法便会执行,一种事件处理机制。
5. 具体的事件处理器 (Concrete Event Handler)
具体的事件处理器,实现了Event Handler。在回调方法中会实现具体的业务逻辑。
0x02 Reactor模式的处理流程
-
- 当应用向初始分发器(
Initiation Dispatcher
)注册具体的事件处理器(Concrete Event Handler
)时,应用会标识出该事件处理器希望Initiation Dispatcher
在某种类型的事件发生时向其通知,事件与handle
关联。
- 当应用向初始分发器(
-
2. 初始分发器(
Initiation Dispatcher
)要求注册在其上面的具体的事件处理器(Concrete Event Handler
)传递内部关联的handle
,该handle会向操作系统标识。 -
- 当所有的具体的事件处理器(
Concrete Event Handler
)都注册到初始分发器(Initiation Dispatcher
)上后,应用会调用handle_events
方法来启动初始分发器(Initiation Dispatcher
)的事件循环,这时初始分发器(Initiation Dispatcher
)会将每个具体的事件处理器(Concrete Event Handler
)关联的handle合并,并使用同步事件分离器(Synchronous Event Demultiplexer
)来等待这些handle上事件的发生。
- 当所有的具体的事件处理器(
-
- 当与某个事件源对应的handle变为ready时,同步事件分离器(
Synchronous Event Demultiplexer
)便会通知初始分发器(Initiation Dispatcher
)。比如tcp的socket变为ready for reading。
- 当与某个事件源对应的handle变为ready时,同步事件分离器(
-
5. 初始分发器(
Initiation Dispatcher
)会触发事件处理器的回调方法。当事件发生时, 初始分发器(Initiation Dispatcher
)会将一个“key”(表示一个激活的handle)定位和分发给特定的事件处理器 (Event Handler
)的回调方法。 -
6. 初始分发器(
Initiation Dispatcher
)调用特定的具体的事件处理器(Concrete Event Handler
)的回调方法来响应其关联的handle上发生的事件。
下图是一个来自网络的Reactor事件处理流程:
参考资料: