您的位置:首页 > 编程语言 > Java开发

IO模型简介

2019-11-20 18:15 423 查看

(一)IO的过程

      在linux 操作系统系统中几乎所有IO操作都是以“文件”的形式管理的(一切皆文件),对“文件”的读写一般都要经过内核态和用户态的切换,对于一次IO访问(以read为例),会经历两个阶段:

  1. 阶段一:调用操作系统的read方法,并开始阻塞等待,等待数据准备好(数据被拷贝到内核缓冲区);

  2. 阶段二:将数据从内核缓冲区拷贝到应用进程,应用进程进行处理。

 

(二)IO模型的演进(BIO、NIO、AIO)

      Java 中的 BIO、NIO和 AIO 本质上是 Java 语言对操作系统层面的各种 IO 模型的封装。

      BIO是JDK1.4之前的传统IO模型,属于阻塞模式,服务器采用每个连接由独立线程维护的方式,即服务器收到客户端的连接请求时,会启动一个新的线程进行处理。在高并发场景下,机器资源很快会被耗尽,即使通过线程池来优化,也无法改变阻塞IO的根本问题,即每个线程在IO执行的上述两个阶段都被阻塞,系统的吞吐量也自然很难提升。

      NIO,可以支持非阻塞IO。NIO采用了多路复用的IO模式,多路复用的模式在Linux底层可以基于select、poll或epoll实现(本质都是一些系统函数),这种机制下可以用单个线程监视多个fd(文件描述符),当其中一个fd读写就绪,会通知用户线程进行IO操作。阻塞IO模式下阻塞的是每一个用户线程,与之不同的是,多路复用只需要阻塞一个用户线程即可,这个用户线程通常我们叫它Selector,其实底层调用的是内核的select,只要任何一个IO操作就绪,就可以唤醒select,然后交由用户线程处理。用户线程读取数据这个过程仍然是阻塞的,多路复用技术只是在第一个阶段可以变为非阻塞调用,但在第二个阶段拷贝数据到用户空间,其实还是阻塞的,多路复用技术的最大特点是使用一个线程就可以处理很多的socket连接,尽管性能上不一定提升,但并发能力和吞吐量却大大增强了。

       AIO,也被称为NIO2.0,在JDK1.7版本中发布,提供了AIO的功能,支持文件的异步IO操作。从NIO中可以看到,对于IO的两个阶段的阻塞,只是对于第一个阶段有所改善,对于第二个阶段在NIO里面仍然是阻塞的。而理想的异步非阻塞IO要做的就是,将IO操作的两个阶段都全部交给内核系统完成,用户线程只需要告诉内核,我要读取一块数据,请你帮我读取,读取完了放在我给你的地址里面,然后告诉我一声就可以了。从操作系统层面来看,AIO算是真正的实现了异步非阻塞,操作系统层面的异步需要系统原生提供支持,目前windows基于IOCP(nput/Output Completion Port)技术实现,在Linux上,目前有很多开源的异步IO库,例如libevent、libev、libuv,都不是基于操作系统的异步IO实现的,底层均是基于epoll实现的。

 

(三)UNIX IO模型解析

1)阻塞IO

       阻塞IO(blocking IO)模式下,用户进程在发起系统调用直到数据到达且被拷贝到用户空间的两个阶段都处于阻塞状态。

2)非阻塞IO

       非阻塞IO(noblocking IO)模式下,用户进程持续非阻塞地询问内核数据准备好了没有,如果数据还没准备好,用户进程不会被block住,内核会立即返回error给用户进程,直到数据准备就绪后,应用进程需同步等待数据从内核空间拷贝到用户空间。

3)IO多路复用

      IO多路复用(IO multiplexing)模式也称作事件驱动IO。用户进程会阻塞在select(或poll、epoll)系统调用上,内核会不断的轮询所负责的所有socket(或其他文件描述符),当某个socket有数据到达了,就通知用户进程,select调用就返回,这时候用户进程再同步等待数据从内核空间拷贝到用户空间。

      IO多路复用和阻塞IO看起来貌似没有什么区别(两个阶段都是阻塞的),事实上还更差一些,因为这里需要使用两个系统调用(select和recvfrom),而阻塞IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个连接。(所以,如果处理的连接数不是很高的话,使用select/poll/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

4)信号驱动IO

      信号驱动IO(signal-driven IO),使用信号机制,让内核在描述符就绪时发送SIGIO信号通知用户进程。整个过程是先通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,用户进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号,我们随后可以在信号处理函数中调用recvfrom读取内核空间准备好的数据。特点:第一阶段(等待数据报到达期间)进程不被阻塞。

5)异步IO

      异步IO(asynchronous IO)的工作机制是:告知内核开启某个操作,并让内核在整个操作完成后通知用户进程,两个阶段都不会被阻塞。

      与信号驱动IO模式的区别:信号驱动IO由内核通知我们何时启动一个IO操作,异步IO由内核告诉我们IO操作何时完成。

6)异步IO和同步IO

       前面说的5种IO模型的前四种,阻塞式I/O、非阻塞式I/O、I/O复用、信号驱动式I/O 在操作系统层面都是同步IO,它们都会阻塞在数据从内核空间复制到用户空间的缓冲区;异步IO模型在两个阶段都不会阻塞调用进程,在操作系统层面实现真正的异步IO。

 

(四)技术选型

       传统的服务器,IO模型是采用为每个请求创建一个子线程来处理,这种模式在并发量小的情况下可以正常支撑业务,但是在高并发场景下,机器资源很快就会耗尽。现今常见的高吞吐高并发系统往往是基于事件驱动的IO多路复用模式设计,这种模式将所有的请求交给一个单独的线程管理,此线程被称之为事件循环线程,当事件等待的系统资源就绪时会及时进行处理,而不是为每个连接生成一个OS线程。这种事件驱动的异步模型大幅度提升了服务器的吞吐能力,在相同配置的服务器能接受更多的并发请求。事件驱动模型的应用十分广泛,Redis就是一个典型的单线程基于事件驱动的内存数据库,Node.js、Nginx、Netty等也都是基于这种方式来实现高吞吐性能。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IOCP epoll libev libuv JDK