• NIO 编程模型


    NIO 编程模型

    Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的变体。本文结合 PPT 按照自己的理解整理而来,最终编写了一个简单的 NIO 回显服务。

    Reactor 之所以高效是因为采用了分而治之事件驱动的设计。大部分网络服务像 Web 服务器、分布式对象的通信等大多数具有相同的基本处理流程:

    • 读取请求数据 - read
    • 按协议解析请求 - decode
    • 业务处理 - process
    • 按协议编码内容生成响应 - encode
    • 发送响应 - send

    传统的服务器设计是:一连接一处理线程,也就是我们常说的 BIO 编程。BIO 在读取和发送时是阻塞的,在请求的整个生命周期内,不管有没有数据可读或待发送,都绑定和占用了这个处理线程。

    传统模型中,线程的处理单元是一次完整的请求,为了把线程解放出来,Reactor 对这个处理单元进行了分解。

    1. 分而治之

    Reactor 模式将处理过程分为多个小任务,每个任务执行一个非阻塞的操作,任务通常由一个 IO 事件触发执行。这种机制在 java.nio 中提供了支持:

    • 非阻塞的读和写
    • 调度与发生的 IO 事件关联的任务

    BIO 线程是以 read->decode->process->encode->send 的顺序串行处理,NIO 将其分成了三个执行单元:读取、业务处理和发送,处理过程如下:

    • 读取:如果无数据可读,线程返回线程池;发生读IO事件,申请一个线程处理读取,读取结束后处理业务
    • 业务处理:线程同步处理完业务后,生成响应内容并编码,返回线程池
    • 发送:发生写IO事件,申请一个线程进行发送

    可以看出一个明显的区别就是,一次请求的处理过程是由多个不同的线程完成的,感觉和指令的串行执行并行执行有点类似。

    分而治之的关键在于非阻塞,这样就能充分利用线程,压榨 CPU,提高系统的吞吐能力。

    2. 事件驱动

    通常比其他模型更高效,它使用的资源更少,不用针对每个请求启用一条线程,减少了上下文切换,减少阻塞。但任务调度可能会慢,必须手动将事件和处理动作绑定。

    通常编程比较困难,它必须为服务设计多个逻辑状态,以便跟踪和中断恢复,这也是在非阻塞编程中有大量状态机运用的原因。

    NIO 中总共设计了 4 种事件,每个事件发生都会调度关联的任务,分别是:

    • OP_ACCEPT: 服务端监听到了一个连接,准备接收
    • OP_CONNECT: 客户端与服务器连接建立成功
    • OP_READ: 读事件就绪,通道有数据可读
    • OP_WRITE: 写事件就绪,可以向通道写入数据

    java.nio 对事件驱动也提供了支持:

    • Channels: 连接到文件、Socket 等,支持非阻塞读取
    • Buffers: 类似数组的对象,可由通道直接读取或写入
    • Selectors: 通知哪组通道有 IO 事件
    • SelectionKeys: 维护 IO 事件的状态和绑定信息

    3. Reactor 模式

    Reactor 是一种设计模式,wikipedia 对其定义如下:

    Reactor 是一个或多个输入事件的处理模式,用于处理并发传递给服务处理程序的服务请求。服务处理程序判断传入请求发生的事件,并将它们同步的分派给关联的请求处理程序。

    Reactor 模式按照职责不同,通常可以把线程分为 Reactor 线程、IO 线程和业务线程:

    • Reactor 线程:轮询通知发生IO的通道,并分派合适的 Handler 处理
    • IO 线程:执行实际的读写操作
    • 业务线程:执行应用程序的业务逻辑

    PPT 中介绍了三种版本的实现。

    3.1 单线程版本

    单线程版本

    单线程版本就是用一个线程完成事件的通知、实际的 I/O 操作和业务处理。Reactor 作用就是要迅速的触发 Handler ,显然 Handler 处理的过程会导致 Reactor 变慢,此时可以将 非 IO 操作从 Reactor 线程中分离。

    3.2 多线程版本

    多线程版本

    多线程版本将业务处理和 I/O 操作进行分离,Reactor 线程只关注事件分发和实际的 IO 操作,业务处理如协议的编解码都分配给线程池处理。可能会有这样的情况发生,业务处理很快,大部分的 Reactor 线程都在处理 IO,导致 CPU 闲置,降低了响应速度。

    3.3 主从 Reactor 版本

    主从 Reactor

    主从 Reactor 版本设计了一个 主Reactor 用于处理连接接收事件,多个 从Reactor 处理实际的 I/O,分工合作,匹配 CPU 和 IO 速率。

    3.4 一个变体

    以上的三个版本中,Reactor 线程都参与了 IO 操作,有一种变体是把实际的 IO 操作也转移到线程池线程中,这样从数据的读取到业务的处理都是由一个线程完成,可以避免锁的竞争。

    小结

    知易行难,为了更好的理解,这里对 PPT 中介绍的3个版本实现了一个简单的回显服务,内部有比较详细的代码注释。

    源码地址https://github.com/tonwu/reactor

  • 相关阅读:
    Luogu P1169 [ZJOI2007]棋盘制作 及悬线法浅谈
    P4338 [ZJOI2018]历史 P3703 [SDOI2017]树点涂色 题解
    多省联考2020游记
    min_25筛基础
    斯特林数、贝尔数与伯努利数基础
    后缀数组笔记
    分治FFT学习笔记
    多项式运算表
    基本积分公式表
    THUWC2019游记
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/10952367.html
Copyright © 2020-2023  润新知