高并发的IO底层原理(读 netty_zookeeper高并发实战)
一、read,write系统的调用
- 无论是进行read调用还是write调用,数据会在进程缓冲区和内核缓冲区之间进行转换,并不是直接从物理设备把数据读取到内存中,或者进数据从内存直接写入屋里设备。
- read,write系统调用并不负责数据在内核缓冲区和物理设备(如磁盘)之间进行交换,这项底层的操作交由Linux的系统内核(kernel)来完成的
二、内核缓冲区和进程缓冲区
- 由于外部设备的直接读写涉及操作系统的中断,发生操作系统中断时,需要保存之前的进程数据和进程状态等信息,而结束中断的时候需要恢复保存的信息,为了减少这种底层的系统的时间损耗,性能损耗,就出现了内存缓存区
- 上层应用使用read系统调用时候,仅仅把数据从内核缓存区复制到进程缓冲区,调用write系统时候反之,底层操作会对内核缓冲区进行监控,等缓冲到达一定数量的时候再进行IO设备的中断处理,集中执行物理设备的实际IO操作,这种机制提升了系统的性能,至于什么时候中断,由系统的内核来完成,用户无需关心。典型的系统调用流程如下图所示:第一个阶段:等待数据缓冲到达网卡,它被复制到内核中的某个缓冲区,这个过程有系统内核完成,用户程序无感知;第二阶段:将数据从内核缓冲区复制到应用进程缓冲区
三、四种主要的IO模型
-
同步阻塞IO(Blocking IO)
具体流程如下图:Java应用程序从开始从IO系统调用开始直到系统调用返回,在这段时间里,Java进程都是阻塞的
-
同步非阻塞IO(Non-Blocking IO)
具体流程图如下:在内核区没有数据的情况下,系统调用立即返回,返回一个调用失败的信息,当内核区内有数据时,是阻塞的直到数据从内核缓冲区读取到用户进程缓冲区,复制完成后, 系统返回调用成功,应用程序开始处理用户空间的缓存数据
-
多路复用IO(IO Multiplexing)
具体流程图如下:引入了一种新的系统调用,查询IO的就绪状态,在Linux系统中对应的系统调用为select/epoll系统调用,一旦某个描述符就绪(一般是内核缓冲区可读或者可写),内核能够将就绪状态返回给应用程序,随后应用系统根据就绪状态进行相应的IO系统调用.
(1):选择器注册。在这种模式中,首先要将read操作的目标socket网络连接,提前注册到select/epoll选择器中,Java对应的选择器类为selector类,然后才可以开启整个IO多路复用模型的查询
(2):就绪状态的查询。通过选择器的查询方法,查询注册过的所有socket连接的状态。通过系统的查询调用,内核会返回一个就绪的socket列表,当任何一个注册过的socket连接中的数据准备好了,内核缓冲区中有数据了,内核就将该socket加入到就绪的列表中(当用户线程调用了select方法,那么整个线程就会阻塞)
(3):用户获得就绪的列表后,根据其中的socket连接,发起read系统调用,用户线程阻塞,内核开始复制数据,将数据从内核区复制到用户进程缓冲区
(4):复制完成后,内核返回执行结果,用户线程才会解除阻塞状态,用户线程取得了数据,继续执行
-
异步IO(Asynchronous IO)
流程图如下,
(一)、当用户系统发起了系统调用,立即可以开始去做其他事情。
(二)、内核开始IO的第一阶段:准备数据,等到数据准备好了过后内核就会将数据从内核缓冲区复制到用户缓冲区
(三)、内核会返回被用户线程一个信号,或者回调用户线程的回调接口,告诉用户线程read操作完成了
(四)、用户线程读取用户缓冲区的数据,完成后续的业务操作