高并发IO的底层原理
2022-06-20 18:35:52 8 举报
Netty学习必备的基础知识,也是JavaIO的底层原理,包括IO操作原理和三种IO模型,个人学习总结,不对的地方还请指正。
作者其他创作
大纲/内容
1.IO读写的基础原理
1.1内核缓冲区和进程缓冲区
缓冲区的目的,是为了减少频繁的与外部物理设备的直接交互,计算机的外部物理设备与内存与CPU相比,差别是非常大的,外部设备的直接读写,涉及到操作系统的中断,操作系统中断时,需要保存之前的进程数据和状态等信息,中断完成后,需要恢复之前的进程数据等信息。为了减少底层操作系统的频繁中断所带来的性能损耗、时间损耗,于是出现了内核缓冲区。
有了内核缓冲区,操作系统会对内存缓冲区进行监控,等待缓冲区到达一定数量的时候,再进行IO设备的中断处理,集中执行物理设备的IO操作,通过这种机制来提升系统的性能。
上层应用程序使用read系统调用时,仅仅把数据从内核缓冲区复制到用户空间的进程缓冲区;上层应用使用write系统调用时,仅仅把数据从用户空间的进程缓冲区复制到内核缓冲区。
内核缓冲区与进程缓冲区在数量上也不同,在Linux系统中,操作系统只有一个内核缓冲区。而每个用户程序则有自己独立的缓冲区,叫做用户缓冲区或者进程缓冲区。Linux系统中的用户程序的IO读写程序,在大多数情况下,并没有进行实际的IO操作,而是在用户缓冲区和内核缓冲区之间直接进行数据交换。
1.2详解典型的系统调用流程
用户程序所使用的系统调用read&write,并不是使数据在内核缓冲区和物理设备之间的交换。read调用把数据从内核缓冲区复制到进程缓冲区,write调用把数据从应用程序的进程缓冲区复制到内核缓冲区
以read系统调用为例,完整输入流程的两个阶段
应用程序等待数据准备好
从内核缓冲区复制到进程缓冲区
1.2.1如果是read一个socket,那么以上两个阶段的具体处理流程如下:
第一个阶段:应用程序等待数据通过网络中到达网卡,当所等待的分组到达时,数据被操作系统复制到内核缓冲区中。这个操作由操作系统来完成,用户程序无感知。
第二个阶段:内核将数据从内核缓冲区复制到用户程序的进程缓冲区。
1.2.2Java客户端和服务器端之间完成一次socket请求和响应的完整流程:
客户端发送请求:Java客户端发起write系统调用,将数据复制到内核缓冲区,Linux将内核缓冲区中的请求数据通过客户端的网卡发送出去。在服务端,这份请求数据会从接受网卡中读取到服务器端机器的内核缓冲区。
服务端获取请求:Java服务端发起read系统调用,从Linux内核缓冲区读取数据,再送入Java进程缓冲区。
服务端业务处理:Java服务端在自己的用户空间中,完成客户端的请求所对应的业务处理。
服务器端返回数据:Java服务器端发起write系统调用,将数据复制到内核缓冲区,Linux将内核缓冲区中的返回数据发送出去。
发送给客户端:服务端Linux系统将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议,会将数据发送给客户端。
1.3四种主要的IO模型
1.3.1同步阻塞模型
首先解释一下阻塞与非阻塞,阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间执行用户程序的操作指令,阻塞一词指的是用户程序的执行状态是阻塞的。可以说传统的IO操作都是阻塞的,并且在Java中,默认创建的socket都是阻塞的。
其次,解释一下同步与异步,简单来说,可以把同步和异步看成是两种发起IO请求的方式。同步IO指的是用户程序是作为主动发起IO请求的一方,系统内核是被动接受方。异步IO则反过来,系统内核是主动发起IO请求的一方,用户空间则是被动接收方。
所谓同步阻塞IO,是指用户程序作为主动发起IO请求的一方,需要等到内核IO操作彻底完成后,才可以返回执行用户空间的操作指令。IO操作过程中,发起IO请求的用户进程处于阻塞状态。
同步阻塞模型的处理流程
用户程序主动发起IO请求,进入阻塞状态;
操作系统收到请求,开始准备数据到内核缓冲区,这个时候内核就要等待;
内核一直等待数据到达后,内数据从内核缓冲区中复制到用户空间的进程缓冲区,然后内核返回结果;
直到内核返回后,用户程序才会解除阻塞状态,重新运行起来。
阻塞IO的特点:在内核进行IO执行的两个阶段,发起IO请求的用户进程被阻塞了(内核等待数据到达和复制数据到进程缓冲区)。
阻塞IO的优点:应用的程序开发非常简单:在阻塞等待数据期间,用户线程挂起,几乎不会浪费CPU资源。
阻塞IO的缺点:一般情况下,会为每个连接配备一个独立的线程,一个线程维护一个连接的IO操作。在并发量小的情况下,这样做没有什么问题。但是,在高并发场景下,需要大量的线程维护大量的网络连接,内存、线程的切换消耗会非常巨大。在高并发场景下,阻塞IO性能是很低的,基本上是不可用的。
1.3.2同步非阻塞模型
所谓同步非阻塞模型,指的是用户程序主动发起,不需要等待内核的IO操作彻底完成,就能立即返回到用户空间的IO操作,IO操作过程中,发起IO请求的用户进程处于非阻塞状态。
同步非阻塞IO的处理流程:
(1)在内核缓冲区中没有数据的时候,系统调用会立即返回,返回一个调用失败的信息。
(2)在内核缓冲区中有数据的时候,在数据的复制过程是阻塞的,直到数据从内核缓冲区完整的复制到进程缓冲区中。复制完成后,系统调用返回成功,用户进程解除阻塞状态。
发起一个非阻塞socket的read系统调用,流程如下:
(1)在内核缓冲区数据没有准备好的阶段,用户发起IO请求时,立即返回,所以,为了读取到最终的数据,用户进程要不断的发起系统调用。
(2)当内核缓冲区中的数据到达后,用户进程进入阻塞状态,内核将数据从内核缓冲区复制到进程缓冲区中。
(3)复制完成后,系统调用返回成功,用户空间解除阻塞状态,重新运行起来。
同步非阻塞IO的特点:应用程序的线程需要不断地进行IO系统调用,轮询数据是否已经准备好,如果没有准备好,就一直轮询,直到数据到达。
同步非阻塞IO的优点:每次发起的IO系统调用,在内核等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。
同步非阻塞IO的缺点:用户需要不断的发起IO系统调用来轮询数据是否准备好,这将占用大量的CPU时间,效率低下。
1.3.3IO多路复用模型
为了提高性能,操作系统引入了一类新的系统调用,专门用于查询IO文件描述符的就绪状态。在Linux系统中,新的系统调用为select/epoll系统调用。通过该系统调用,一个用户进程可以监视多个文件描述符,一旦某个文件描述符就绪,内核能够将该文件描述符的就绪状态返回给用户进程,用户空间可以根据文件描述符的就绪状态,进行响应的IO系统调用。
发起一个多路复用IO的read系统调用,流程如下:
(1)选择器注册。在这种模式中,首先,将需要read操作的目标文件描述符,提前注册到Linux的select/epoll选择器中,在Java中所对应的选择器是Selector类。然后,才可以开启整个IO多路复用模型的轮询流程。
(2)就绪状态的轮询。通过选择器的查询方法,查询所有提前注册过的目标文件列表。当任何一个注册过的socket中的数据准备好或者就绪了,就是内核缓冲区有数据了,内核就将该socket加入到就绪的列表中,并且返回就绪事件。
(3)用户线程获得了就绪状态的列表后,根据其中的socket连接,发起read系统调用,用户线程阻塞。内核开始复制数据,将数据从内核缓冲区复制到进程缓冲区。
IO多路复用模型的特点:IO多路复用模型的IO涉及两种系统调用:一种是IO操作的系统调用,另一种是select/epoll就绪状态系统调用。IO多路复用建立在操作系统的基础设施之上,即操作系统的内核必须能够提供多路复用的系统调用select/epoll。
IO多路复用模型的优点:一个选择器查询线程,可以同时处理成百上千的socket连接,所以,用户程序不必建立大量的线程,也不必维护这些线程,从而大大减少了系统的开销。这是一个线程维护一个连接的阻塞IO模式相比,使用多路复用IO模型的最大优势。
IO多路复用模型的缺点:本质上,select/epoll系统是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是说这个读写过程是阻塞的,如何彻底地解除线程的阻塞,就必须使用异步IO模型。
收藏
收藏
0 条评论
下一页