解读《Scalable IO in Java》阅读思考与笔记

《Scalable IO in Java》阅读笔记

    • 网络处理流程
    • 一个线程对应一个网络连接
    • 高性能的奥义:分而治之 事件驱动
    • Reactor模式
      • 单线程
      • 单reactor多worker线程模式
      • 多reactor多worker线程模式
    • 用数据说明同样都是多线程为啥reactor更好

《Scalable IO in Java》 出自于java大神Doug Lea,这是一篇关于高性能IO服务的经典文章,文章中梳理了网络服务架构的演化,提出了Reactor反应器模式,这种设计模式是netty等高性能网络框架的基础。

网络处理流程

服务器对于一次网络请求的处理大概流程如下
流程

一个线程对应一个网络连接

在早期的网络编程中,服务端程序会为每一个网络连接开启一个线程,线程与连接之间一一对应。这种方式简单清晰也便于理解与实现,但是对于系统资源消耗较大,当客户端数量增多时,服务器难以承受。
一个线程一个连接

高性能的奥义:分而治之 事件驱动

在上述模型中***每一个线程都要完成读数据、解码、业务逻辑处理、编码、回复这一系列操作***,对于不同客户端的连接,除了业务逻辑处理是差异化的之外,其余四部分都是相同的。那么按照我们以往的编程习惯是不是应该把公共部分进行抽取整合呢?采用分治的思想,把这一完整的过程分为一个个细小的任务,然后单独处理。

上述模型的另外一个缺陷就是阻塞式IO,只有当一个连接完全处理完成之后,这个线程才能处理其他的连接,如果在某个环节有个耗时操作,那么这个线程会一直处于空闲但又不能工作的状态,是对于系统资源的浪费。采用事件驱动的方式可以避免因为阻塞而带来的等待。

基于事件驱动的架构设计通常比其他架构模型更加有效,因为可以节省一定的性能资源,事件驱动模式下通常不需要为每一个客户端建立一个线程,这意味这更少的线程开销,更少的上下文切换和更少的锁互斥,但任务的调度可能会慢一些,而且通常实现的复杂度也会增加,相关功能必须分解成简单的非阻塞操作,类似与GUI的事件驱动机制,当然也不可能把所有阻塞都消除掉,特别是GC, page faults(内存缺页中断)等。由于是基于事件驱动的,所以需要跟踪服务的相关状态(因为你需要知道什么时候事件会发生);

Reactor模式

Reactor也可以称作反应器模式,它有以下几个特点:

  1. 它把网络处理流程进行拆分,分离了网络连接的建立与业务逻辑的处理。
  2. Reactor模式中会通过分配适当的handler(处理程序)来响应IO事件,类似与AWT 事件处理线程;
  3. 每个handler执行非阻塞的操作,类似于AWT ActionListeners 事件监听
  4. 通过将handler绑定到事件进行管理,类似与AWT addActionListener 添加事件监听;

单线程

单线程
基于此种模式一个线程也可以处理来自多个客户端的网络连接,可以利用Java NIO来对此模式进行实现,其处理流程如下:
处理流程
在这种模式下一个线程可同时接受多个客户端的连接,当某个连接就绪时就可以对其进行处理,因为是单线程所以对于业务逻辑的处理是串行,所以这种方式带来的性能提升并没有那么高,可以为每一个业务逻辑新开一个线程进行处理,那就是下面的设计方式了。

单reactor多worker线程模式

多线程
在多处理器场景下,为实现服务的高性能我们可以有目的的采用多线程模式:
增加Worker线程,专门用于处理非IO操作,因为通过上面的程序我们可以看到,反应器线程需要迅速触发处理流程,而如果处理过程也就是process()方法产生阻塞会拖慢反应器线程的性能,所以我们需要把一些非IO操作交给Woker线程来做;

多reactor多worker线程模式

多reactor多worker线程模式
拆分并增加反应器Reactor线程,一方面在压力较大时可以饱和处理IO操作,提高处理能力;另一方面维持多个Reactor线程也可以做负载均衡使用;线程的数量可以根据程序本身是CPU密集型还是IO密集型操作来进行合理的分配;

用数据说明同样都是多线程为啥reactor更好

假设网络连接的建立需要1秒,业务逻辑处理0.5秒,一个线程所占资源值为1%,
现在有50个客户端连接,用传统的方式一个线程对应一个连接,需要建立50个线程,占用50%的系统资源,每个线程用时1.5秒,一共75秒。资源与时间的乘积为35。

现在有50个客户端连接,用单reactor多worker线程方式。一个线程处理50个连接,资源与时间的乘积为1% * 1 * 50=0.5;50个线程进行业务逻辑处理50% * 0.5 * 50=12.5.一共为13。

核心就在于reactor中只有一个线程进行了阻塞,即把网络IO这个耗时操纵放在了一个线程里单独处理,多线程用于处理业务逻辑,这样就避免了网络IO把整个程序拖慢。