大家好,又见面了,我是你们的朋友全栈君。
1.Java1.4之前的早期版本的 I/O的问题:
a,没有数据缓冲区,I/O性能存在问题
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编程的原因:
1、NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
2、需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。
3、可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流等问题,NIO编程的特点是功能开发想对容易,但是可靠性和能力补齐的工作量和难度都非常大。
4、JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selecor空轮询,最终导致CPU 100%。一直没得到根本解决。
为什么选择Netty:
1、API使用简单,开发门槛低
2、功能强大,预置了多种编解码功能个,支持多种主流协议
3、定制能力强,可以通过ChannelHandler对通信框架进行灵活的扩展
4、性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
5、成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼
6、社区活跃,版本迭代周期短,发现的BUG可以及时被修复,同时,更多的新功能会加入。
7、经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它是已经完全能够满足不同行业的商业应用了。
转载于:https://www.cnblogs.com/yangyongjie/p/10418193.html
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/106986.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...