【编程进阶知识】高并发场景下Bio与Nio的比较及原理示意图
摘要: 本文介绍了在Linux系统上使用Tomcat部署Java应用程序时,BIO(阻塞I/O)和NIO(非阻塞I/O)在网络编程中的实现和性能差异。BIO采用传统的线程模型,每个连接请求都会创建一个新线程进行处理,导致在高并发场景下存在严重的性能瓶颈,如阻塞等待和线程创建开销大等问题。而NIO则通过事件驱动机制,利用事件注册、事件轮询器和事件通知,实现了更高效的连接管理和数据传输,避免了阻塞和多级数据复制,显著提升了系统的并发处理能力。
假设是用部署在Linux操作系统上的Tomcat配置的java应用程序。
关键字:Java, Linux, Tomcat, BIO, NIO, 高并发, 阻塞I/O, 非阻塞I/O, 事件驱动, 并发性能
1.Bio的技术代码逻辑实现
Bio属于传统网络编程处理连接以及数据传输的方式,技术代码逻辑实现大致如下:
1)一个BioServerTest(服务端启动服务的main主方法入口所在类,监听是否有客户端接入请求,有的话创建线程开始业务处理)
ServerSocket.bind(ip+port);//绑定ip+port,以便监听客户端向此ip+port发起的连接请求
while(server is Run){
Socket socket= ServerSocker.accept();
new Thread(new BioServerTask(socket)).start();
}
2)一个BioServerTask extends Thread(收到客户端请求后进行具体业务处理的一个线程类)
InputStream.read (data)//读取客户端发送的数据
3)一个BioClient(客户端,向服务器监听的IP+Port发送请求)
Socket(ip+port)//向指定ip+port发起连接请求
OutputStream.writer(data//往指定ip+port发送数据
2.高并发场景下Bio存在的弊端
高并发场景下Bio存在的弊端就会被暴露出来:
1)首先在ServerSocket.accpet()时就会发生阻塞,如果没有客户端连接请求,服务器会一直被阻塞在这里什么也做不了(不优雅高效);
2)其次,当接收到一个客户端请求后,服务端会创建一个业务处理线程进行具体业务处理,而new Thread这个操作比较耗时(需要jvm分配一段内存空间)。假设并发量为50000qps(query per seconds 每秒请求数),创建一个线程耗时为0.0001s,将数量级同步缩小一千倍,并发量50qps,耗时0.1s,相当于在这被消耗的0.1s的时间窗口内,请求只能等待,暂时得不到处理。单位再换算以下,1s有50个请求=0.1s有5个请求,在这被线程创建所消耗的0.1s内有5个左右的请求同时到达,服务器只能处理其中一个,其余4个请求丢失了。
Tomcat默认配置下能支持100-150qps并发量,当并发量在150-300qps时会出现200ms延迟,当并发量超过300qps时会出现500ms延迟并伴随部分连接丢失。
3)在进行具体业务处理时,先要把客户端发送的数据接收过来,接收数据的过程中会出现阻塞和二级复制,高并发场景下效率也不高,可能会影响用户体验:
(1)阻塞出现在InputStream.read()这个方法,此方法要求数据完整性,要一次性将数据传输过来,在传输过程中程序会卡住;
(2)二级复制的产生过程:与服务器建立连接后,客户端向服务器传输数据,数据首先经过基于HTTP协议的网络层复制到Linux操作系统(将数据复制到Linux Kernel的缓冲区,一次只接收一部分数据,像个小杯子,而不是一次性接收全部数据),JVM再把数据从内核复制到JVM中(JVM不断从内核缓冲区(小杯子)里取出数据最后组成完整数据),应用程序从JVM中读取数据。过程比较复杂,数据传输效率也不高。示意图如下:
3.Nio中的连接和数据传输都基于事件处理。
1)服务端不必一直去监听是否有客户端发起连接请求接着再去进行具体业务逻辑处理,而是通过事件注册、事件轮询器和事件通知的这一整套事件处理机制,将服务器从原先一直处于的监听状态中给解放出来。
2)每次收到一个客户端连接请求后也不必再去创建新的线程去进行业务处理,对每个连接请求处理在一个连接池中统一管理,当进行业务处理所需的条件/状态事件准备好时(比如数据可读),事件处理机制会通知处于连接池中的某个连接请求进行后续处理。
3)数据传输时服务器也不会因为一次读取不到完整数据而产生阻塞(当数据传输完毕可读取的时候,会被当作一个事件通知服务器,这时服务器再去读取数据)。
4)客户端与应用程序进行数据传输时,可通过Nio提供的一个叫buffer的东西经由Channel(管道,双向可读可写,可以异步读写,通道中的数据总是要先读到一个buffer或总要先从一个buffer写入)与Linux Kernel缓冲区进行直接的读写实时数据操作(这个buffer应该具有将读取到的一块块数据依据某些信息组合成完整数据且能保证数据正确性的一些功能),这样就避免了两级复制。
5)Nio基于事件机制对客户连接请求及数据传输的逻辑处理示意图如下:
# 4.总结
本文深入探讨了在Linux系统上使用Tomcat部署Java应用程序时,BIO(阻塞I/O)和NIO(非阻塞I/O)在网络编程中的实现和性能差异。BIO在高并发场景下由于其基于每个连接创建新线程的模型,导致性能瓶颈和资源消耗问题。相比之下,NIO采用事件驱动机制,通过事件注册、事件轮询和事件通知,实现了更高效的连接管理和数据传输,避免了阻塞和多级数据复制,从而显著提升了系统的并发处理能力。
在BIO模型中,服务器在接收客户端连接请求时可能会阻塞,且每次接收请求都需要创建新线程,这在高并发情况下会导致延迟和资源浪费。此外,数据传输过程中的阻塞和二级复制也会影响效率。而NIO则通过使用缓冲区(Buffer)和通道(Channel)直接与操作系统的缓冲区进行数据读写,避免了这些性能问题。
通过代码示例和原理示意图,详细解释了BIO和NIO的技术实现,以及它们在处理高并发请求时的不同表现。NIO的事件驱动模型不仅提高了性能,还减少了资源消耗,使其成为处理高并发网络应用的更佳选择。