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 处理实际的 I/O,分工合作,匹配 CPU 和 IO 速率。

3.4 一个变体

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

小结

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

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

NIO 编程模型的更多相关文章

  1. Nio编程模型总结

    终于,这两天的考试熬过去了, 兴致冲冲的来整理笔记来, 这篇博客是我近几天的NIO印象笔记汇总,记录了对Selector及Selector的重要参数的理解,对Channel的理解,常见的Channel ...

  2. 手动搭建I/O网络通信框架3:NIO编程模型,升级改造聊天室

    第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 第二章:手动搭建I/O网络通信框架2:BIO编程模型实现群聊 在第二章中用BIO编程模型,简单的实现了一 ...

  3. Reactor 典型的 NIO 编程模型

    Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的变体.本文结合 P ...

  4. 谈谈传统BIO网络编程模型的局限性与NIO

    先来看看我们的server端: 创建一个serversocket,进行监听,每来一个客户端,就启动一个新启动为其服务: private void createListenSocket() { //如果 ...

  5. NIO&AIO编程模型

    NIO线程模型 什么是NIO线程模型? 上图是NIO的线程模型,  基于select实现,   这种线程模型的特点:  多条channel通过一个选择器和单挑线程绑定, 并且在这种编程模型中, Cha ...

  6. 手动搭建I/O网络通信框架4:AIO编程模型,聊天室终极改造

    第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 第二章:手动搭建I/O网络通信框架2:BIO编程模型实现群聊 第三章:手动搭建I/O网络通信框架3:NI ...

  7. Java网络编程和NIO详解3:IO模型与Java网络编程模型

    Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...

  8. JDK NIO编程

    我们首先需要澄清一个概念:NIO到底是什么的简称?有人称之为New I/O,因为它相对于之前的I/O类库是新增的,所以被称为New I/O,这是它的官方叫法.但是,由于之前老的I/O类库是阻塞I/O, ...

  9. Linux IO模型和网络编程模型

    术语概念描述: IO有内存IO.网络IO和磁盘IO三种,通常我们说的IO指的是后两者. 阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待. 以文件IO为例,一个IO读过程是文件 ...

随机推荐

  1. redis实现排行榜功能

    目录 加入排行榜 操作排行榜 redis的zset可以很方便地用来实现排行榜功能,下面简单介绍python如何使用redis实现排行榜功能 加入排行榜 获取redis实例 import redis m ...

  2. Springboot的resources下资源访问的问题

    对于路径问题,是让我一直感到痛苦的事情,首先是因为我的眼高手低,感觉路径这么简单根本没必要去看,但是昨天项目组长的冷嘲热讽让我无地自容:“你竟然连linux和window的路径的区别都不知道,呵呵”. ...

  3. 牛客训练41D最小相似度bfs

    最小相似度 题目大意:对于位数相同的两个二进制串,SIM(A,B)为它们的相似度,也就是A^B中0的个数.现在给定一堆串,找出一个T使得max{SIM(S1,T),SIM(S2,T),......,S ...

  4. Java中indexOf的用法

    indexOf有四种用法: 1.indexOf(int ch) 在给定字符串中查找字符(ASCII),找到返回字符数组所对应的下标找不到返回-1 2.indexOf(String str)在给定符串中 ...

  5. 第06课:作用域、JS预解析机制

    从字面上理解----域就是空间.范围.区域,作用就是读.写,所以作用域我们可以简单理解为:在什么样空间或者范围内对数据进行什么样的读或写操作. 看一下代码 alert(a); // 为什么是undef ...

  6. Node.js安装及环境配置

     1.Node.js简介 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境. Node.js ...

  7. Git命令的简单整理大全

    创建目录和查看路径 mkdir 创建目录  cd    进入到目录里面pwd   用于显示当前的目录cat   查看文件内容 Git命令使用说明 初始化一个目录成git的仓库(版本库)包括暂存区和ma ...

  8. RabbitMQ MQTT协议和AMQP协议

    RabbitMQ MQTT协议和AMQP协议 1        序言... 1 1.1     RabbitMq结构... 1 1.2     RabbitMq消息接收... 4 1.3     Ex ...

  9. Java 注解指导手册(下)

    9. 自定义注解   正如我们之前多次提及的,可以定义和实现自定义注解.本章我们即将探讨. 首先,定义一个注解:   public @interface CustomAnnotationClass   ...

  10. html上传图片后,在页面显示上传的图片

    html上传图片后,在页面显示上传的图片 1.html <form class="container" enctype="multipart/form-data&q ...