BIO (阻塞IO)
服务器程序一个线程负责一个连接,进行请求的处理和响应。当客户端比较多的时候,服务端的线程可能就不够了,这时候对应的请求就没法及时处理了。
伪异步IO(BIO增加线程池)
服务端会维护一个线程池来处理请求端的请求,减少了线程创建销毁开销。当客户端的请求比较多的时候,线程池的线程也是不够的,此时也会出现请求处理阻塞的情况。
NIO
同步非阻塞IO方式,服务端通过缓存区、通道、多路复用等技术,允许客户端的请求成千上万。读写都首先通过缓冲区来承接,然后通过通道(双向,区别于需要两个InputStream和OutputStream)来传输信息。通过多路复用器轮询通道(select/poll,epoll),看看是否存在读或者写时间,然后再进行处理,大大提升了IO处理的性能和稳定性.由于操作复杂,Netty横空出世。
AIO
AIO又称为NIO2.0,在JDK7才开始支持(但Linux平台下没有真正的异步IO实现,如windows下的IOCP技术,只能用epoll模拟异步IO)。真正的异步IO,简化了NIO的通信模型。它是连接注册读写事件和回调函数,读写方法异步,同时它是主动通知程序。
AIO异步通信提供了两种方式获取操作结果:第一种方式是通过java.util.concurrent的Future类来表示异步操作的结果;第二种方式是在执行异步操作的时候传入一个java.nio.channels.CompletionHandler接口的实现类作为操作完成回调。
AIO的异步套接字回调,是真正的异步非阻塞IO, .对应于Unix网络编程中的事件驱动IO,不需要通过多路复用器对被注册的通道进行轮询操作即可实现异步读写,从而简化NIO的编程模型。
对比
| IO通信方式 | 客户端:处理线程 | IO类型 | 可靠性 | 调试难度 | 吞吐量 |
|---|---|---|---|---|---|
| BIO | M:M | 同步阻塞 | 差 | 简单 | 低 |
| 伪异步IO(线程池) | M:N (N<M) | 同步阻塞 | 比较差 | 简单 | 中 |
| NIO | M:1 | 同步非阻塞 | 比较高 | 复杂 | 高 |
| AIO(异步NIO) | M:0 | 异步非阻塞 | 比较高 | 比较复杂 | 高 |
阻塞式的I/O模型需要每个链接开一个线程(或进程),当线程数达到10k级别时,内存占用和上下文切换(context switch)导致系统的开销很大。
为什么Netty使用NIO而不是AIO?
- Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化
- Netty整体架构是reactor模型, 而AIO是proactor模型, 混合在一起会非常混乱,把AIO也改造成reactor模型看起来是把epoll绕个弯(Linux用epoll实现AIO)又绕回来
- AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存.所以对连接数量非常大但流量小的情况, AIO内存浪费很多
- Linux上AIO不够成熟,处理回调结果速度跟不到处理需求,比如外卖员太少,顾客太多,供不应求,造成处理速度有瓶颈.
- Netty作者原话:
Not faster than NIO (epoll) on unix systems (which is true).
There is no daragram suppport.
Unnecessary threading model (too much abstraction without usage).