Java的NIO

6 篇文章 0 订阅

Unix的五种IO模型

阻塞IO:应用进程从发起IO系统调用,至内核空间中的数据就绪,这个期间处于阻塞状态。

非阻塞IO:应用进程发起IO系统调用后立刻返回。应用进程可以不断(轮询)发起IO系统调用,直至数据就绪,再将数据从内核空间拷贝到用户空间进行数据处理。(在拷贝数据的过程,进程仍然属于阻塞状态)。优点是进程发起I/O操作时,不会因为数据还没就绪而阻塞。缺点是增大了响应延迟,因为每过一段时间才会发起系统调用检查数据是否就绪,而任务可能在两次轮询之间的时间完成,这会导致整体数据吞吐量的降低;尤其是在本地I/O,内核读取数据很快,这种模式下多了至少一次系统调用,而系统调用是比较消耗CPU的操作。

IO多路复用:应用进程借助select或poll或epoll,通过发起这种系统调用可以让内核监听注册的多个事件(传入file descriptor和感兴趣的事件readable、writable等),(发起后应用进程被该系统调用block)当其中有就绪的数据后,该系统调用会返回结果,再由应用进程发起真正的IO系统调用(read、recvfrom等),来完成数据读取。优点是优化了非阻塞IO大量发起系统调用的问题,且一次可以监控大量的连接/fd。但处理的连接数不是很高的话,使用select/epoll的不一定比使用multi-threading + blocking IO性能更好,可能延迟还更大,因为select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

在IO 多路复用模型中,对于每一个socket,一般都设置成为non-blocking,虽然多路复用产生的效果,完全可以由用户态去遍历文件描述符并调用其非阻塞的 read 函数实现。但是多路复用快的原因在于,操作系统提供了这样的系统调用,使得原来的 while 循环里多次系统调用,变成了一次系统调用 + 内核层遍历这些文件描述符。

  • select(): 等待内核返回后,需要轮询所有fd找出就绪的fd,随着fd数量增加,性能逐渐下降
  • poll(): 和select()差不多,只有linux支持poll。
  • epoll(): 不需要轮询,直接返回就绪的fd,用以替代select()和poll()

信号驱动IO:应用进程向内核注册一个信号处理程序,发起系统调用后立即返回,不会阻塞。当内核中数据就绪后,会发送信号给应用进程,应用进程在信号处理程序中发起真正的 IO 系统调用完成数据读取。

异步IO:应用进程程发起aio_read系统调用,无论内核数据是否就绪,都会直接返回给用户进程,然后用户进程不会阻塞,而是可以去做别的事情。等数据就绪后,内核直接复制数据到用户空间给到进程,然后内核向进程发送在aio_read中指定的信号。异步IO在IO的两个阶段,进程都是非阻塞的。它就像用户进程将整个IO操作交给了内核来完成,然后内核完成后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动地去拷贝数据。

POSIX对同步IO和异步IO定义是:同步I/O操作将会造成请求进程阻塞,直到I/O操作完成;异步I/O操作不会造成进程阻塞。 因此根据此定义,前面4种I/O模型都是同步I/O,因为它们在第二阶段的I/O操作(recvfrom)都会造成进程阻塞。只有最后一个I/O模型匹配异步I/O的定义。

Java IO 与 Unix IO 不严谨的对应关系:

BIO - 阻塞式IO | NIO - IO多路复用 | AIO - 异步IO

IO设计模式

TPR (Thread Per Request): 服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。此模式下可以采用线程池的方式,避免为每一个客户端都要创建线程带来的资源浪费,使得线程可以重用。但线程池比较适合大量的短连接应用,如果连接大多是长连接,可能会导致在一段时间内,线程池中的线程都被占用,那么再有用户请求连接时,由于没有可用的空闲线程来处理,客户端连接就会失败。

Reactor (反应器): 将所有要处理的I/O事件注册到一个中心I/O多路复用器上,当I/O事件到来或准备就绪(可读写),多路复用器返回并将事先注册的相应I/O事件分发到对应的处理器中。Reactor是用来处理并发的同步IO的一种常见模式,是基于事件驱动的设计模式。(感知的是就绪可读写事件)Reactor的常见实现方案有三种:

阻塞IO中,每个连接需要一个线程(Handler)完成read,业务处理和send;针对阻塞IO缺点,Reactor采用IO多路复用模型使得多个连接共用一个阻塞对象(ServiceHandler/Reactor),采用线程池复用线程资源(EventHandler)进行连接完成后的业务处理。

  • 单Reactor单线程:一个线程(包含Reactor、Acceptor和Handler对象)多路复用搞定所有IO操作。通过Reactor的select监听多路连接请求,dispatch进行分发;通过Acceptor的accept处理连接请求;通过Handler进行连接完成后的后续业务处理。
  • 单Reactor多线程:Handler只响应事件,即执行读写操作,不执行业务操作。而添加了一个Worker线程池,处理业务的非I/O操作从主线程转移到Worker线程池中执行(处理完毕返回给Handler)。这样能提高Reactor主线程的I/O响应,不会因为一些耗时的业务逻辑而延迟对后面I/O请求的处理。缺点是Reactor主线程仍在单线程中运行,高并发场景下很容易称为性能瓶颈。
  • 主从Reactor多线程:Reactor在多线程下运行,分为Reactor主线程和一或多个Reactor子线程,Reactor主线程中的MainReactor对象通过select监听连接事件,通过Acceptor对象处理连接事件,然后将连接分配给SubReactor;Reactor子线程中的SubReactor对象通过read读取数据,交给Worker线程池通过decode、compute、encode进行业务处理返回给SubReactor后,再通过send发送数据给客户端。[nginx/netty使用此方案]

Proactor (主动器): 和异步I/O相关,也是基于事件驱动的设计模式。依赖操作系统对异步的支持,不需要把数据从内核复制到用户空间,这步由操作系统完成。(感知的是已完成的读写事件)

NIO三大组件

Selector:监听一个或多个Channel是否处于可读/可写,当Channel中有感兴趣的事件发生时,...,从而实现单线程管理多个Channels;SelectableChannel需要注册到Selector并指定监听感兴趣的事件(可读/可写/连接/接收);选择器查询的并非Channel的某种操作,而是Channel某种操作的就绪状态;“选择键”的概念类似于“事件“。

Buffer:本身是一块内存,底层实现是数组;数据的读写都通过Buffer实现(同一块Buffer区域既可以读也可以写)。NIO中定义了IntBuffer、CharBuffer、DoubleBuffer、ByteBuffer(通常使用ByteBuffer,其他的实现也是对它的包装)等实现,其中MappedByteBuffer 用于实现内存映射文件。

Channel:是数据来源或数据写入的目的地,是内核区域与IO设备进行通信的桥梁;Channel本身并不直接读取或写入数据,而是通过Buffer进行;Channel的四个主要实现是:

  • FileChannle:用于文件数据的读写
  • DatagramChannel:用于UDP数据的读写
  • ServerSocketChannel:用于TCP数据的读写
  • SocketChannel:用于TCP数据的读写

Netty组件

 NIO 开发客户端和服务端的开发和维护成本较高,因此我们更多采用Netty框架这个高性能、异步事件驱动的NIO框架。Netty主要包含下面几个的组件:

  • Bootstrap:【引导类】用于引导Netty的启动并把其他各部分连接起来;客户端的Bootstrap连接到远程主机和端口,服务器端的ServerBootstrap绑定到一个本地的端口。
  • Channel:【网络通信组件】用于网络IO操作,代表一个Socket的连接。
  • EventLoopGroup:一个Group包含多个EventLoop,可以理解为线程池。
    • Boss Group:[主从Reactor多线程中的MainReactor] 轮询处理accept事件,与clien建立连接,生成NIOSocketChannel...
    • Worker Group:轮询处理IO(read/write)事件,在对应NIOSocketChannel处理
  • EventLoop:处理具体Channel上的事件,一个EventLoop对应一个Selector,一个EventLoop可以处理多个Channel,可以理解为线程。
  • ChannelPipeline:用于处理IO事件或者拦截IO操作,ChannelPipeline是一个handler的集合,每个Channel绑定一个Pipeline。
  • Handler:对消息或连接的具体处理,分为Inbound和Outbound类型代表消息接收的处理和消息发送的处理。
  • ChannelFuture:【异步IO操作的结果】处理异步操作,等待完成或注册监听。Netty中的IO都是异步的,所有的IO调用都会立即返回一个ChannelFuture实例提供IO操作的结果或状态信息。

Netty可以作为 RPC 框架的网络通信工具 ,也可以用来实现HTTP 服务器、即时通讯系统、消息推送系统等。

Reference

带你彻底理解Linux五种I/O模型

python的twisted

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值