传统阻塞I/O模式
其中黄色框表示对象,蓝色框表示线程,白色框表示API方法
特点
- 采用阻塞IO模式获取输入数据
- 每个连接都需要独立的线程完成数据的输入,业务处理和处理结果数据返回
潜在问题
- 并发数很大时,需要对应每个连接请求创建一个线程,所以占用资源很大
- 连接创建后,若当前下线程暂时没有数据操作时,该线程会在操作方法处阻塞,造成线程资源浪费
Reactor模式(Dispatch模式)
针对传统阻塞I/O模式的潜在问题,解决方案如下
- 基于I/O复用模型解决创建线程数量多的问题:通过一个阻塞对象管理所有连接
- 基于线程池解决线程资源浪费问题
Reactor模式的设计思想就是I/O复用结合下线程池。
核心组成
- Reator:Reactor在一个单独的thread中运行,负责监听和分发事件,分发给适当的处理程序对IO事件做出反应
- Handler:真正处理执行Reactor分发过来的IO事件
优势
- 响应快:不会由于单个同步而导致阻塞
- 扩展性好:通过方便的增加Reactor实例个数充分利用多核CPU资源
- 复用性好:Reactor本身与具体的事件处理逻辑无关,具有很高的复用性
根据Reactor的数量和处理资源池线程的数量不同,包括单Reactor单线程、单Reactor多线程和主从Reactor多线程
单Reactor单线程
执行流程
- Reactor通过Select监控客户端请求事件,收到事件后通过Dispatch进行分发
- 如果是建立连接请求则由Acceptor处理,并且创建Handler对象处理连接完成后的业务
- 若不是连接请求(比如read)则分发调用对应的Handler来响应(read->业务处理->send流程)
方案分析
- 模式简单,没有多线程竞争,全部由一个线程完成
- 性能问题:仅一个线程,无法发挥多核CPU性能
- 可靠性问题:线程意外终止或者遇到死循环会导致系统不可用
- 适用于客户端数量有限,业务处理非常快速的场景
单Reactor多线程
执行流程
- Reactor通过Select监控客户端请求事件,收到事件后通过Dispatch进行分发
- 如果是建立连接请求则由Acceptor处理,并且创建Handler对象处理连接完成后的业务
- 若不是连接请求(比如read)则Reactor分发调用对应的Handler进行响应(read->分发任务->send流程)
- Handler只负责响应事件,不做具体业务处理,通过read读取数据会分发给work线程池的某个线程处理业务
- work线程池会分配独立线程完成真正的业务并将结果返回给Handler
- Handler收到响应后通过send将结果返回给client
方案分析
- 可以充分利用多核CPU的处理能力
- 多线程数据共享和访问比较复杂,reactor处理所有的事件的监听和响应,在单线程、高并发场景下容易出现性能瓶颈
主从Reactor多线程(Netty基于此模式)
执行流程
- Reactor主线程MainReactor对象通过select监听连接事件,若收到连接请求事件则会通过Acceptor处理
- 当Acceptor处理完成连接事件后,MainReactor将连接分配给SubReactor(一个MainReactor存在多个子Reactor)
- SubReactor将连接加入到连接队列进行监听,并创建Handler进行对应事件处理
- 当存在新事件发生时,subReactor就会调用对应Handler处理
- Handler只负责响应事件,不做具体业务处理,通过read读取数据会分发给work线程池的某个线程处理业务
- work线程池会分配独立线程完成真正的业务并将结果返回给Handler
- Handler收到响应后通过send将结果返回给client
方案分析
- 主线程和子线程数据交互简单职责明确,主线程负责接收连接,子线程完成后续业务处理
- 主线程和子线程交互简单,主线程把连接交给子线程,子线程无需返回数据
- 但编程复杂度比较高
- 应用于Nginx主从Reactor多进程模型、Memcached主从多线程、Netty主从多线程