Netty指南——Java IO演变

IO基础

Linux IO模型

  1. 阻塞IO模型
    阻塞IO模型是最常用的IO模型,默认所有文件操作都是阻塞的。以套接字接口为例,在进程空间中调用recvfrom,系统调用会直到数据包到达且被赋值到应用进程的缓冲区中或者发生错误时才返回,此期间内会一直等待,进程从调用recvfrom开始到它返回的时间段内都是被阻塞的。
  2. 非阻塞IO模型
    recvfrom从应用层到内核的时候,如果缓冲区内没有数据,就直接返回EWOULDBLOCK错误,一般都对非阻塞IO模型进行轮训检查这个状态,用以判断内核是不是有数据返回。
  3. IO复用模型
    Linux提供select/poll,进程通过将一个或多个fd传递个select或者poll系统调用,阻塞在select操作上,这样select/poll可以侦测多个fd是否处于就绪状态。它们是顺序扫描fd是否就绪,而且支持的fd数量有限,因此使用收到制约。Linux还提供epoll系统调用,epoll基于事件驱动方式代替顺序扫描,当有fd就绪时,立即回调函数rollback,因此性能更高。
  4. 信号驱动IO模型
    首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,调用后立即返回,进程继续工作,因此它是非阻塞的。当数据准备就绪时,就为该进程生成一个SIGIO信号,通过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据。
  5. 异步IO
    告知内核启动某个操作,并让内核在整个操作完成后通知我们(包括将数据从内核复制到用户自己的缓冲区)。它与信号驱动模型的主要区别在于信号驱动IO由内核通知我们何时可以开始一个IO操作,而异步IO由内核通知我们IO操作合适已经完成。

我们只需要了解上述5中IO模型的概念即可,知道对于操作系统而言,底层是支持IO异步通信的。

IO多路复用

当需要同时处理多个请求时,可以利用多线程或者IO多路复用技术。IO多路复用技术是通过把多个IO的阻塞复用到同一个select的阻塞上,从而使系统在单线程情况下也可以同时处理多个请求。与多线程对比,IO多路复用的最大优势就是开销更小,系统不需要创建新的额外进程或者线程,更不需要去维护这些进程和线程,节省了系统资源。
主要应用场景:

  • 需要同时处理多个处于监听状态或者多个连接状态的套接字
  • 需要同时处理多种网络协议的套接字

目前支持IO多路复用的系统调用有select、pselect、poll、epoll。Linux起初使用select做轮训和网络时间通知,但它存在一定缺陷,后来在新内核版本中改用epoll。epoll相比select做了改进。

  1. 支持一个进程打开的socket描述符(fd)不受限制(仅首先与操作系统的最大文件句柄数)
  2. IO效率不会随着fd数量的增加而线性下降
  3. 使用mmap加速内核与用户空间的消息传递
  4. epoll的api更简单

Java IO演变

在1.4前,所有Java Socket通信都采用了同步阻塞模式(BIO),虽然这种模式简化了开发,但是性能和可靠性极差。从1.4开始,Java提供了新的NIO库,开始支持非阻塞IO。NIO库在java.nio包中,提供了异步IO开发的API和类库,主要包括:

  1. 缓冲区ByteBuffer等
  2. 管道Pipe
  3. 异步或同步Channel
  4. 多种字符集的编码、解码能力
  5. 多路复用器selector
  6. 基于Perl实现的正则表达式库
  7. 文件通道FileChannel

虽然新的NIO库支持了异步非阻塞编程,但还是存在一些不足:

  1. 没有统一的文件属性,比如读写权限
  2. API能力弱,比如目录的级联创建和遍历需要自己实现
  3. 底层存储系统的一些高级API无法使用
  4. 所有文件操作还是同步阻塞调用,不支持异步文件读写

JDK 1.7的到来,帮我们进一步优化了NIO库,新的库被称为NIO 2.0,主要提供了3方面升级:

  1. 能够批量获取文件属性
  2. 提供了标准文件系统的SPI,供各服务商自行实现
  3. 提供AIO功能,支持基于文件的异步IO操作和针对网络套接字的异步操作
  4. 支持配置和多播数据报等
白十 wechat
欢迎订阅我的微信公众号!