NIO 看破也说破(四)—— Java的NIO

Stella981
• 阅读 838

Java的NIO有selector,系统内核也提供了多种非阻塞IO模型,Java社区也出现了像netty这种优秀的 NIO 框架。Java的NIO 与内核的阻塞模型到底什么关系,为什么Java有NIO的API还出现了netty这种框架,网上说的 reactor 到底是什么?本文通过分析代码,带你一步步搞清楚Java的NIO和系统函数之间的关系,以及Java NIO 是如何一步步衍生出来netty框架。

NIO概念

前几节我们提到了 Nonblocking IO 的概念,在Java中有Java NIO 的系列包,网上的大多数资料把Java的NIO等同于 Nonblocking IO ,这是错误的。Java 中的 NIO 指的是从1.4版本后,提供的一套可以替代标准的Java IO 的 new API。有三部分组成:

  • Buffer 缓冲区

  • Channel 通道

  • Selector 选择器

API的具体使用不在本文赘述。

代码模板

NIO大致分为这几步骤:

  1. 获取channel

  2. 设置非阻塞

  3. 创建多路复用器selector

  4. channel和selector做关联

  5. 根据selector返回的channel状态处理逻辑

    // 开启一个channel

单线程示例

参考代码模板,我们用 NIO 实现一个Echo Server。server代码如下:

public static void main(String[] args) throws IOException {

 写一个client ,模拟 50 个线程同时请求 server 端,在 readHandler 中模拟了随机sleep。client代码:

public static void main(String[] args) throws IOException {

NIO 看破也说破(四)—— Java的NIO

Selector 实现原理

用strace启动,[root@f00e68119764 tmp]# strace -ff -o out /usr/lib/jvm/java-1.8.0/bin/java NIOServerSingle 分析执行的过程,在日志中可以看到如下片段:

 20083 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 4

可以看出,在Java的 NIO 中(java1.8)底层是调用的系统 epoll ,关于 epoll请出门右转,这里不再啰嗦。

从源码中也可以看出:

public static Selector open() throws IOException {

openSelector是抽象方法具体实现类,在Linux上代码如下:

public class EPollSelectorProvider

跟踪代码可以看到最后调用 native 方法,说明:Java NIO 是利用系统内核提供的能力。

多线程处理

我们把单线程示例中,readHandler 随机 sleep,稍稍做些修改。模拟 server 端执行某一次请求时,处理过慢,如图示:

第十五个请求过来时,随机sleep:

 // 模拟server端处理耗时

结果第十五个线程之后,所有 client 的执行都有一个短暂的等待

NIO 看破也说破(四)—— Java的NIO

很容易解释,因为在单线程处理中,channel创建、IO读写均为一个 Thread ,面对50个 client,IO时间需要排队处理。因此我们Redis系列中也提到了在Redis中,尽量避免某一个key的操作会很耗时的情况 出门右转

我们对代码做一些改造,client 端代码不动,server 端代码稍作调整。增加一个线程来处理读写时间,代码片段如下:

if (selectionKey.isAcceptable()) {

这样相当于server端有两个线程,一个是主线程启动的 selector 来监听 channel 的 OP_ACCEPT 状态,另一个线程是处理 channel 的读写。程序也可以继续执行,稍稍快了一些。

NIO 看破也说破(四)—— Java的NIO

Reactor模式

接触 NIO 就一定听过reactor 这个名词,reactor 经常被混入 NIO 中,让很多人混淆概念。Reactor 到底是什么,维基百科的解释:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.

核心点四个:

  1. reactor design pattern (reactor是一种设计模式,不是专属于某个语言或框架的)

  2. event handling pattern (事件处理模式)

  3. delivered concurrently to a service handler by one or more inputs(一次处理一个或多个输入)

  4. demultiplexes the incoming requests and dispatches them(多路分解,分发)

我们对单线程示例做些修改:

 public static void main(String[] args) throws IOException {

initServer的实现:

private static Selector initServer() throws IOException {

dispatcher 的实现:

private static void dispatcher(SelectionKey selectionKey) {

acceptHandler的实现:

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

readHandler的实现:

SocketChannel channel = (SocketChannel) selectionKey.channel();

单线程Reactor

改造后的代码,具备了以下特点:

  1. 基于事件驱动( NIO 的 selector,底层对事件驱动的epoll实现 jdk1.8)

  2. 统一分派中心(dispatcher方法)

  3. 不同的事件处理(accept 和 read write 拆分)

已经基本上实现了 Reactor 的单线程模式,我们把示例代码再做一些改造:

public class ReactorDemo {

我们实现了最基本的单Reactor的单线程模型,程序启动后 selector 负责获取、分离可用的 socket 交给dispatcher处理,dispatcher 交给不同的 handler 处理。其中 Acceptor 只负责 socket 链接,IO 的处理交给 ProcessHandler。

我把网上流传的Reactor的图,按自己的理解重新画了一份

NIO 看破也说破(四)—— Java的NIO

多线程Reactor

上面的示例代码中,从 socket 建立到 IO 完成,只有一个线程在处理。NIO 单线程示例中我们尝试加入线程池来加速 IO 任务的处理,reactor 模式中该如何实现呢?

简单理解,参考 NIO 多线程加入线程池处理所有的processHandler ,可以利用 CPU 多核心加快业务处理,代码不再赘述。

NIO 看破也说破(四)—— Java的NIO

多Reactor模式

参考ReactorDemo ,我们的 acceptor 处理 socket 链接时和 handler 处理 IO 都是用的同一个 selector 。如果我们在多线程基础上有两个 selector ,一个只负责处理 socket 链接一个处理网路 IO 各司其职将会更高大的提升系统吞吐量,该怎么实现呢?

public class ReactorDemo {

示例中创建了 selector 和 ioSelector ,其中 selector 只处理 socket 的建立,在 Acceptor.process 方法中把 socket 注册给 ioSelector。在 ProcessHander.process 方法中 ioSelector 只负责处理 IO 事件。这样,我们把 selector 进行了拆分。参考多线程实现,同理我们可以创建 N 个线程,处理 ioSelector 对应的 IO 事件。

NIO 看破也说破(四)—— Java的NIO

总结

至此,我们了解了 Reactor 的三种模型结果,分别是单 Reactor 单线程、单 Reactor 多线程、多 Reactor 多线程。所有代码不够严谨,只为了表示可以使用多个线程或者多个 selector 之间的关系。总结重点:

  1. reactor 是一种设计模式

  2. 基于事件驱动来处理

  3. 利用多路分发的策略,让不同业务处理各司其职

  4. 有单线程,单Reactor 多线程 和 多 Reactor 多线程,三种实现方式

参考:

http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

关注我

如果您在微信阅读,请您点击头像关注我 ,如果您在 PC 上阅读请扫码关注我,欢迎与我交流随时指出错误

NIO 看破也说破(四)—— Java的NIO

本文分享自微信公众号 - 小眼睛聊技术(it-ffs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Netty之缓冲区ByteBuf解读(一)
!(https://oscimg.oschina.net/oscnet/up6de4d71f462d9846befe00ec6505125a928.JPEG)\Netty在数据传输过程中,会使用缓冲区设计来提高传输效率。虽然,Java在NIO编程中已提供ByteBuffer类进行使用,但是在使用过程中,其编码方式相对来说不太友好,也
Wesley13 Wesley13
3年前
NIO
1、简介1.1Java中的IO介绍1.BIO:BlockingIO,同步式阻塞式IO,即传统的IO,是java中最早期的流2.NIO:NonBlockingIO,又称NewIO,同步式非阻塞IO,是JDK1.4提供的流3.AIO:AsynchronousIO,异步是非阻塞IO,可以认为是NIO的二代版
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable