线程模型
线程模型
1.传统服务设计模型
特点:
- 通过阻塞I/O来获取数据
- 每个连接都需要独立的线程来完成业务输入、数据处理、数据返回
存在的问题:
- 1.当并发数很大时,需要创建大量线程,占用了很多系统资源。
- 2.连接创建后,如果连接中没有数据可读,线程会被阻塞,操作线程资源浪费。
2. NIO分发模型
分发模型具有以下几个机制:
- 将一个完整处理过程分解为一个个细小的任务。
- 每个任务执行相关的动作且不产生阻塞。
- 在任务执行状态被触发时才会去执行,例如,只在有数据时才会触发读操作。
3.事件驱动模型
事件驱动模型简单来说,就是当有事件来临时,任务才会被触发执行。
事件驱动的编程难度相对较高,它必须为服务设计多个逻辑状态,以便跟踪和中断恢复。这些逻辑状态也代表着相应的事件。
在Java NIO中,有以下逻辑状态:
- OP_ACCEPT:接收连接事件,表示服务器监听到了客户连接。
- OP_CONNECT:连接就绪事件,表示客户与服务器的连接已经建立
- OP_READ:读就绪事件,表示通道中已有可读的数据。
- OP_WRITE:写就绪事件,表示已经可以向通道写数据了。一般写就绪事件发生时机:当我们需要主动的向通道中写数据时,一般来说有两种方式:1.直接从Selector中拿到通道,然后直接写;2.将需要写的数据附加在SelectionKey上,然后调用key.interestOps(SelectionKey.OP_WRITE),来将该key标记,当下次select时,该key会触发写操作。
Java NIO 示例
// 在需要写时拿到key,附加需要写的byteBuffer,然后设置感兴趣的操作
key.attach(msg);
key.interestOps(SelectionKey.OP_WRITE);
//...
// 下次select时,会判断到该key可写,执行写操作
if (key.isWritable()) {
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
byteBuffer.flip();
((SocketChannel)key.channel()).write(byteBuffer);
}
4.Reactor模型
有多种叫法,分别是反应器模式、分发者模式(Dispatcher)、通知者模式(Notifier)。
针对传统阻塞I/O模型的缺点,提出了两个解决方案:
-
基于IO复用模型:多个连接公用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接,当某个连接有新的数据可以处理时,操作系统通知应用程序从阻塞状态返回,开始进行业务处理。
-
基于线程池复用线程资源:不必为每个连接都创建线程,将连接完成后的业务处理任务交给线程来进行处理,一个线程可处理多个连接的业务。
优点:
- 响应快
- 可以最大程度的避免复杂的多线程及同步问题,并且避免了线程/进程切换开销
- 扩展性好,可以通过增加Reactor实例个数来充分利用CPU资源
- 可复用性高,Reactor模型与具体事件处理逻辑无关,具有很高的复用性
4.1 单Reactor单线程模型
所有的处理流程仅在一个线程中完成,Reactor、Acceptor、Handler仅做了代码上的划分。
处理流程如下:
- 客户端发送请求,服务端Reactor监听到请求后,调用dispatch处理
- 如果是连接事件,则调用Acceptor处理
- 如果是读写事件,则调用Handler处理
使用案例:Redis
4.2 单Reactor多线程模型
该模型下监听、读写事件仍然由Reactor线程来处理,将耗时的业务处理流程交给了线程池来处理。
处理流程如下:
- 客户端发送请求,服务端Reactor线程监听到事件,调用dispatch处理
- 如果是连接事件,则交给Acceptor来处理
- 如果是读事件,则调用Handler读取到数据,然后交给Worker线程来进行业务逻辑处理,然后将操作结果返回给Reactor
- 如果是写事件,则Reactor调用Handler执行写操作
4.3 主从Reactor多线程模型
模型处理流程如下:
- 客户端发起连接请求,主Reactor通过Acceptor监听到连接请求。
- Acceptor从多个副Reactor中挑选一个出来,然后将新连接交给该副Reactor。
- 将该连接注册到副Reactor单独维护的Selector中。
- 当连接可读时,副Reactor通过Handler来执行读操作,然后将处理数据的过程放到Worker来执行。
- Workder线程处理完成后,Worker将返回操作结果给副Reactor,副Reactor再将操作结果返回给用户。
其中主Reactor是单线程的,副Reactor由多个线程组成,Workder线程池由多个线程组成。
优点:
- 主Reactor和副Reactor分工明确交互简单,主Reactor只需要接受新连接,副Reactor负责监听连接读写事件
缺点:
- 编程复杂度较高
使用案例:Netty、Nginx 等