1.Java1.4以前的早期版本的 I/O的问题:前端
a,没有数据缓冲区,I/O性能存在问题java
b,没有C或者C++中的channel概念,只有输入流和输出流正则表达式
c,同步阻塞式 I/O 通讯(BIO),一般会致使通讯线程被长时间阻塞编程
d,支持的字符集有限,硬件可移植性很差。后端
2.Java1.4新增了java.nio包,提供了不少进行异步 I/O 开发的API和类库,主要的类和接口以下:数组
a,进行异步I/O操做的缓冲区ByteBuffer等缓存
b,进行异步I/O操做的管道Pipe服务器
c,进行各类I/O操做(异步或者同步)的Channel,包括ServerSocketChannel 和SocketChannel;网络
d,多种字符集的编码能力和解码能力多线程
e,基于流行的Perl实现的正则表达式类库
f,文件通道FileChannel
可是它仍然有不完善的地方,特别是对文件系统的处理能力仍显不足,主要问题以下:
a,没有统一的文件属性
b,API能力比较弱,例如目录的级联建立和递归遍历,每每须要本身实现
c,底层存储系统的一些高级API没法使用
d,全部的文件操做都是同步阻塞调用,不支持异步文件的读写操做
2011年JDK1.7发布,将原来的NIO类库进行了升级,被称为NIO2.0,提供了下面三个改进
a,提供可以批量获取文件属性的API,这些API具备平台无关性,不与特性的文件系统相耦合,另外还提供了标准文件系统的SPI,供各个服务提供商扩展实现
b,提供AIO功能,支持基于文件的异步I/O操做和针对网络套接字的异步操做
c,完成JSR-51定义的通道功能,包括对配置和多播数据报的支持等。
BIO只要的问题在于每当有一个新的客户端接入时,服务端必须建立一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端链接,在高性能的服务器应用领域,每每须要面对成千上万个客户端的链接,这种模型显然没法知足高性能,高并发接入的场景。
伪异步BIO编程:
为了解决同步阻塞I/O面临的一个链路须要一个线程初拉力的问题,后来有人对它的线程模型进行了优化-----后端经过一个线程池来处理多个客户端的请求接入,造成客户端个数M,线程池最大线程数N的比例关系,其中M能够远远大于N,经过线程池能够灵活的调配线程资源,设置线程的最大值,防止因为海量并发接入致使线程资源耗尽。
InputStream 的read(byte b[]) 方法注释中有这么一段话:
This Method blocks until input data is available ,end of file is detected,or an exception is thrown。
即当对Socket的输入流进行读取操做的过后,它会一直阻塞下去,直到发生以下三种事件:
1.有数据可读
2.可用数据已经读取完毕
3.发生空指针或者I/O异常
这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通讯线程将会被长时间阻塞,若是对方要60s才可以将数据发送完成,读取一方的I/O线程也将会同步阻塞60s,在此期间,其余接入消息只能在消息队列中排队。
outputStream的 write(byte b[]) 写操做也是同步阻塞的,阻塞的时间取决于对方I/O线程的处理速度和网络I/O的传输速度。本质上来讲咱们没法保证生产环境的网络情况和对端的应用程序能足够快,若是咱们的应用程序依赖对方的处理速度,它的可靠性就会很是差。面对恶劣的网络环境和参差不齐的第三方系统,问题就会如火山同样喷发。
伪异步I/O其实是对以前I/O模型的一个简单优化,它没法从根本上解决同步I/O致使的通讯线程阻塞问题,下面是通讯对方返回应答时间过长会引发的级联故障:
1.服务端处理缓慢,返回应答消息耗费60s,平均只须要10ms。
2.采用伪异步I/O的线程正在读取故障服务节点的响应,因为读取输入流是阻塞的,它将会同步阻塞60s
3.假如全部的可用线程都被故障服务器阻塞,那后续的I/O消息都将在队列中排队
4.因为线程池采用阻塞队列排队,当队列积满以后,后续入队列的操做都将被阻塞
5.因为前端只有一个Accept线程接收客户端的接入,它被阻塞在线程池的同步阻塞队列以后,新的客户端请求都将被拒绝,客户端会发生大量的链接超时。
6.因为几乎全部的链接都超时,调用者会认为系统已经奔溃,没法接收新的请求消息。
NIO编程:
NIO非阻塞I/O (Non-block I/O)
1.缓冲区Buffer
Buffer是一个对象,它包含一些要写入或者读出的数据。在NIO库中,全部数据都是用缓冲区来处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任什么时候候访问NIO中的数据,都是经过缓冲区进行操做。
缓冲区实质上是一个数组。一般它是字节数组(ByteBuffer)是最经常使用的缓冲区,一个ByteBuffer提供了一组功能用于操做Byte数组,每一种Java基本类型(除了Boolean)都对应一种缓冲区,ByteBuffer 字节缓冲区,CharBuffer 字符缓冲区,ShortBuffer 短整型缓冲区,IntBuffer 整型缓冲区,LongBuffer 长整型缓冲区,FloatBuffer 浮点型缓冲区,DoubleBuffer 双精度浮点型缓冲区。
2.通道Channel
Channel是一个通道,网络数据经过Channel读取和写入。通道与流的不一样之处在于通道是双向的,流只在一个方向上移动(一个流必须是InputStream或者OutputStream的子类)而通道能够用于读、写或者两者同时进行。
由于Channel是全双工的,因此它能够比流更好地映射底层操做系统的API。特别是在Unix网络模型中,底层操做系统的通道都是全双工的,同时支持读写操做。
实际上Channel分为两类:用于网络读写的SelectableChannel和用于文件操做的FileChannel。ServerSocketChannel和SocketChannel都是SelectableChannel的子类。
3.多路复用器Selector
它是Java NIO编程的基础。熟练的掌握Selector对于NIO编程相当重要。多路复用器提供选择已经就绪的任务的能力。简单来说,Selector会不断地轮询注册在其上的Channel,若是某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,而后经过SelectionKey能够获取就绪Channel的集合,进行后续的I/O操做。
一个多路复用器能够同事轮询多个Channel,因为JDK使用了epoll来代替传统的select实现,因此它并无最大链接句柄1024/2048的限制。这也就意味着只须要一个线程负责Selector的轮询,就能够接入成千上万的客户端,这确实是个很是巨大的进步。
NIO服务端序列图:
NIO客户端序列图:
AIO:
TODO
不选择Java原生NIO编程的缘由:
一、NIO的类库和API繁杂,使用麻烦,你须要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
二、须要具有其余的额外技能作铺垫,例如熟悉Java多线程编程。这是由于NIO编程涉及到Reactor模式,你必须对多线程和网络编程很是熟悉,才能编写出高质量的NIO程序。
三、可靠性能力补齐,工做量和难度都很是大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流等问题,NIO编程的特色是功能开发想对容易,可是可靠性和能力补齐的工做量和难度都很是大。
四、JDK NIO的BUG,例如臭名昭著的epoll bug,它会致使Selecor空轮询,最终致使CPU 100%。一直没获得根本解决。
为何选择Netty:
一、API使用简单,开发门槛低
二、功能强大,预置了多种编解码功能个,支持多种主流协议
三、定制能力强,能够经过ChannelHandler对通讯框架进行灵活的扩展
四、性能高,经过与其余业界主流的NIO框架对比,Netty的综合性能最优
五、成熟、稳定,Netty修复了已经发现的全部JDK NIO BUG,业务开发人员不须要再为NIO的BUG而烦恼
六、社区活跃,版本迭代周期短,发现的BUG能够及时被修复,同时,更多的新功能会加入。
七、经历了大规模的商业应用考验,质量获得验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经获得了成功商用,证实它是已经彻底可以知足不一样行业的商业应用了。