一、引言
在处理web请求时,通常有两种体系结构,分别为:thread-based architecture(基于线程)、event-driven architecture(事件驱动)。我们来看看他们具体是怎么实现的。
二、传统的BIO通信
1.单线程阻塞模型
-
客户端向服务器端发出请求后,客户端会一直等待(不会再做其他事情),直到服务器端返回结果或者网络出现问题。
-
服务器端同样的,当在处理某个客户端A发来的请求时,另一个客户端B发来的请求会等待,直到服务器端的这个处理线程完成上一个处理。
缺点:服务端一次只能同时处理一个客户端连接。因此这种模型是无法用于高并发服务器的。
2.多线程阻塞模型
既然服务端要处理多个请求,那直接增加线程数不就好了?
要知道多线程是有以下局限性的:
- 虽然请求的处理交给了一个独立线程进行,但是操作系统通知accept()的方式还是单个的。也就是,服务器接收到数据报文后的“业务处理过程”可以多线程,但是数据报文的接受还是需要一个一个的来。(服务端对每个新连接都单独起一个线程去处理,主线程继续阻塞在accept上等待新连接。)
- 系统最大线程数是有限的。如果创建过多的线程,可能会导致效率低下。
- 线程的频繁切换也极度浪费系统资源。
这两种模型存在一些共同的问题:
- 主线程每次调用 accept 函数获取客户端连接是阻塞的。必须要等待能获取到新连接之后,accept函数才会返回,否则主线程一直阻塞在accept上。
- 子线程分配到新的客户端连接后,read和write操作也是阻塞的,导致白白地浪费线程资源。
- 不利于同时多个socketfd的处理。
三、Reactor模型
Reactor线程模型就是多路I/O复用结合线程池的思想。
即多个连接共用一个阻塞对象,应用程序无需阻塞等待所有连接,当某个连接有新的数据可以处理时,操作系统通知应用程序线程从阻塞状态返回,并将数据分发给对应的线程处理。
1.单Reactor单线程模式 (Reactor和handler都在同一个线程中)
-
select是IO复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求。
-
Reactor对象通过Select监控客户端请求事件,收到事件后通过Dispatch进行分发。
-
如果是建立连接请求事件,则由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的后续业务。
-
如果不是建立连接事件,则Reactor会分发调用连接对应的Handler来处理业务。
-
Handler会完成Read->业务处理->Send的完整业务流程。
缺点:
- 服务器端用一个线程通过多路复用搞定所有的IO操作,包括连接、读、写等,但是如果客户端连接数较多,将无法支撑,比如处理一个客户端的业务时,别的客户端的业务请求只能阻塞等待。
- 单线程无法发挥多核CPU的性能。
2.单Reactor多线程
-
Reactor对象通过select监控客户端请求事件,收到事件后,通过dispatcher进行分发
-
如果是建立连接的请求,则由Acceptor通过accept处理连接请求,然后创建一个Handler对象处理完成连接后的各种事件
-
如果不是连接请求,则由Reactor分发调用连接对应的handler进行处理
-
handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务
-
worker线程池会分发独立的线程完成真正的业务,并将结果返回给handler
-
handler收到响应的结果后,再通过send将结果返回给client
缺点:
- 一个reactor处理所有事件的监听和响应,在单线程运行,高并发场景下容易出现性能瓶颈
3.主从Reactor多线程
-
Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
-
当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
-
SubReactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理
-
当有新事件发生时,SubReactor就会调用对应的handler进行处理
-
handler通过Read读取数据,分发给后面的worker线程处理
-
worker线程池会分配独立的worker线程进行业务处理,并返回结果
-
handler收到响应的结果后,再通过send将结果返回给client
-
MainReactor主线程可以关联多个SubReactor子线程
优点:
- 主线程与子线程的数据交互简单职责明确,主线程只需要接收新连接,子线程完成后续的业务处理
- 可以通过扩展多个Reactor子线程的方式来减小单个子线程的压力,提高并发处理能力
Netty和Kafka都采用了这种主从Reactor多线程的模型。