IO模型详解
2022-05-15 13:59:02 2 举报
IO模型是指输入/输出模型,是计算机科学中用来描述设备和应用程序之间数据传输方式的概念模型。常见的IO模型有同步阻塞IO、异步非阻塞IO、IO多路复用和信号驱动IO等。其中,同步阻塞IO是指在读取或者写入数据时,进程会一直等待直到操作完成;异步非阻塞IO是指进程发起一个IO操作后,无需等待且可以继续执行其他操作;IO多路复用是指通过一种机制同时监视多个文件描述符,当某个文件描述符就绪时,通知程序进行相应的读写操作;信号驱动IO是指通过信号来通知程序进行IO操作的一种方式。不同的IO模型适用于不同的场景,选择合适的IO模型可以提高程序的性能和效率。
作者其他创作
大纲/内容
业务处理
内存映射MMAP
ThreadPool
套接字发送缓冲区
①连接
Socket
多Reactor线程模式
Handler
应用程序缓冲区
CPU拷贝
select监听
④处理请求
内核
用户进程
磁盘
selector
TCP Socket通信
DMA拷贝
业务逻辑
开始send
业务相关
read()recv()
输入缓冲区
文件读取缓冲区
单线程Reactor模式服务器端的Reactor是一个线程对象,该线程会启动事件循环,并使用Acceptor事件处理器关注ACCEPT事件,这样Reactor会监听客户端向服务器端发起的连接请求事件(ACCEPT事件)。客户端向服务器端发起一个连接请求,Reactor监听到了该ACCEPT事件的发生并将该ACCEPT事件派发给相应的Acceptor处理器来进行处理。建立连接后关注的READ事件,这样一来Reactor就会监听该连接的READ事件了。缺点:若出现IO密集型的操作,因为是单线程模型,所以这个IO操作会让其他请求进入阻塞状态。
send()write()
②建立连接
Client
Worker线程
完成read
数据拷贝
Java程序
读
发起read阻塞
read
mmap内存映射
BIO
阻塞与非阻塞
dispath
Buffer
DirectBuffer
Thread
IO模型
在所有的网络通信和应用程序中,每个TCP的Socket的内核中都有一个发送缓冲区(SO_SNDBUF)和一个接收缓冲区(SO_RECVBUF),可以使用相关套接字选项来更改该缓冲区大小。 当某个应用进程调用write时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区中已有其他数据),假设该套接字是阻塞的,则该应用进程将被投入睡眠。 内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的TCP或应用进程已接收到数据。 Java程序自然也要遵守上述的规则。但在Java中存在着堆、垃圾回收等特性,所以在实际的IO中,在JVM内部的存在着这样一种机制: ① 在IO读写上,如果是使用堆内存,JDK会先创建一个DirectBuffer,再去执行真正的写操作。这是因为,当我们把一个地址通过JNI传递给底层的C库的时候,有一个基本的要求,就是这个地址上的内容不能失效。然而,在GC管理下的对象是会在Java堆中移动的。也就是说,有可能我把一个地址传给底层的write,但是这段内存却因为GC整理内存而失效了。所以必须要把待发送的数据放到一个GC管不着的地方。这就是调用native方法之前,数据—定要在堆外内存的原因。 ② 站在网络通信的角度DirectBuffer并没有节省什么内存拷贝,只是Java网络通信里因为HeapBuffer必须多做一次拷贝,使用DirectBuffer就会少一次内存拷贝。相比没有使用堆内存的Java程序,使用直接内存的Java程序当然更快一点。 ③ 从垃圾回收的角度而言,直接内存不受 GC(新生代的 Minor GC) 影响,只有当执行老年代的 Full GC 时候才会顺便回收直接内存,整理内存的压力也比数据放到HeapBuffer要小。
网络
协议栈
写
Java程序的NIO通信
③注册
Accpetor
Channel
网络设备缓冲区
Main Reactor
注册
单线程Reactor,工作者线程池与单线程Reactor模式不同的是,添加了一个工作者线程池,并将非I/O操作从Reactor线程中移出转交给工作者线程池来执行。这样能够提高Reactor线程的I/O响应,不至于因为一些耗时的业务逻辑而延迟对后面I/O请求的处理。缺点:① 一个NIO线程同时处理成百上千的链路,性能上无法支撑。② 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;
内核态
InputStream数据流
零拷贝
Service
堆外内存
buffer = File.read() Socket.send(buffer)
非阻塞
发起read
用户态
Linux 从2.6.17 支持splice 从磁盘读取到内核buffer后,在内核空间直接与socket buffer建立pipe管道。取代了sendfile()需要调用CPU进行拷贝的问题。 因为当数据从磁盘读取到OS内核缓冲区后,在内核缓冲区直接可将其转成内核空间其他数据buffer,所以不需要拷贝到用户空间。 splice和sendfile的不同:span style=\"font-size: inherit;\
数据是否准备好
Reactor
单线程Reactor模式流程
多Reactor线程模式Reactor线程池中的每一Reactor线程都会有自己的Selector、线程和分发的事件循环逻辑。mainReactor可以只有一个,但subReactor一般会有多个。mainReactor完成接收客户端连接请求的操作,它不负责与客户端的通信,而是将建立好的连接转交给subReactor线程来完成与客户端的通信,这样一来就不会因为read()数据量太大而导致后面的客户端连接请求得不到即时处理的情况。并且多Reactor线程模式在海量的客户端并发请求的情况下,还可以通过实现subReactor线程池来将海量的连接分发给多个subReactor线程,在多核的操作系统中这能大大提升应用的负载和吞吐量。
单线程Reactor,工作者线程池
阻塞
直接内存
堆内存
mmap内存映射实现了对传统数据传送进行了优化。硬盘上文件的位置和应用程序缓冲区进行映射。通过mmap() 的映射关系,直接将文件从硬盘拷贝到用户空间。 该机制减少了调度CPU的次数,但是内核,用户态的切换依然存在。 mmap所消耗成本:3次拷贝,4次上下文切换。
Pipe
linux 2.1支持的sendfile 当调用sendfile()时,DMA将磁盘数据复制到kernel buffer,然后将内核中的kernel buffer直接拷贝到socket buffer;只有记录数据位置和长度的描述符会被加入到socket缓冲区中。 DMA模块将数据直接从内核缓冲区传递给协议引擎,从而消除了遗留的最后一次复制。 sendfile消耗成本:3次拷贝,2次上下文切换。
开始read
send
输出缓冲区
select
splice
应用进程缓存区
Sub Reactor
span style=\"font-size: inherit;\
传统数据传送机制
完成send
sendfile
Reactor模型 (基于简单NIO的设计模式)
NIO
0 条评论
回复 删除
下一页