面试官:说说高并发IO原理及模型,结果没结果了

面试官:说一下高并发IO底层原理?

面试者:呃……嗯……这个那个……我们都是用XX框架……

结果:卒

我们来理解一下高并发原理,展现真正的实力。

一、IO读写基础原理

IO读写分为read和write两块,在不同的操作系统中,IO读写的底层调用可能有些区别,但基本功能是一样的。read调用,并不是直接从物理磁盘读取,同样,write调用也不是直接把数据写进物理磁盘,在他们中间还有一层,就是缓冲区,而缓冲区又包括:内核(kernel)缓冲区和用户进程缓冲区。

具体可以看下图

面试官:说说高并发IO原理及模型,结果没结果了

在Java中,完整的Socket请求和响应如下:

  • 客户端调用:系统通过网卡读取客户端的请求,将数据读取到内核缓冲区;

  • 程序请求数据:Java通过read命令,将数据从内核缓冲区送入Java的用户缓冲区;

  • Java服务端数据处理:在Java用户空间中处理客户请求数据;

  • Java服务端返回数据:数据处理完,通过write命令,将构建好的响应数据从用户缓冲区写入内核缓冲区;

  • 发送数据到客户端:内核通过网络IO,将内核缓冲区中的数据通过网卡,适时将数据发送给目标客户端。

二、四种常见的IO模型

1、同步阻塞IO(Blocking IO)

我们先了解一下概念:

同步与异步:同步指的是用户空间是主动发起IO请求的一方,内核空间是被动接受的一方;异步则是将以上过程反过来;

阻塞与非阻塞:阻塞指的用户空间发起请求后,需要等待内核空间彻底完成后,才将数据返回到用户空间;非阻塞指的是用户空间不需要等待内核空间操作完成,可以立即返回用户用户空间的过程 。

传统的IO模型都是同步阻塞IO,在Java中,默认创建的socket都是同步阻塞的。Java应用从发起IO调用开始,直到系统调用返回,Java进程都是阻塞的,直到有数据返回成功后,应用程序才能开始处理用户缓冲区的数据,具体流程如下:

面试官:说说高并发IO原理及模型,结果没结果了

由上图可以看出,每一步必须等待上一步的完成才能进行。

同步阻塞IO优点:开发简单,容易入门;在阻塞等待期间,用户线程挂起,在挂起期间不会占用CPU资源。

同步阻塞IO缺点:一个线程维护一个IO,不适合大并发,在并发量大的时候需要创建大量的线程来维护网络连接,内存、线程开销灰常大。

2、同步非阻塞IO(Non-blocking IO)

参考以上概念,当处于非阻塞IO时,用户空间与内核空间可以更快的交互,只要拿到内核返回的状态值,就可以继续干自己的事情,而不用做无谓的等待。

在Java中,将socket的属性设置为NONBLOCK即为非阻塞模式。在这个模式中,会有以下两种情况:

在内核缓冲区没有数据时,系统调用会马上返回失败状态给调用方;

在内核缓冲区有数据时,此时是会阻塞的,直到数据从内核缓冲区复制到用户缓冲区,复制完成后,系统调用返回成功,继而用户的应用进程开始处理用户空间的数据,具体流程如下:

面试官:说说高并发IO原理及模型,结果没结果了

由上图可以看出,在内核还没有准备好数据的时候,用户线程发起IO请求,会立即返回;为了最终读取到数据,用户线程需要不断发起IO调用;历经多次探险后,终于有数据到达内核时,此时,用户线程会进入阻塞状态,当数据成功复制到用户缓冲区,用户线程成功读取到数据后,才会解除阻塞状态,重新运行起来。

同步非阻塞IO优点:每次发起IO调用,在内核等待数据的过程中可以立即返回,用户线程不会阻塞,实时性较好。

同步非阻塞IO缺点:多个线程不断轮询内核是否有数据,占用大量CPU时间,效率不高。一般Web服务器不会采用此模式。

3、IO多路复用(IO Multiplexing)

如何解决同步非阻塞IO轮询造成效率低下的问题,这时IO多路复用出现了。

经典的Reactor反应器设计模式,也可称为异步阻塞IO,是众多高性能高并发服务器的基础,比如Nginx、Netty、Redis等。Java中NIO(New IO)里的Selector选择器也是基于此模式。

在Linux系统中,对应的系统调用为select/epoll系统调用,当内核缓冲区可读或者可写时,内核将就绪的状态主动通知相应的用户程序,应用程序收到就绪状态,进行相应的IO调用,具体流程如下:

面试官:说说高并发IO原理及模型,结果没结果了

由上图可以看出,该模式下,read操作流程如下:

  • 注册选择器,将read操作的目标socket提前注册到select/epol选择器中(类比Java中NIO的selector);

  • 轮询,通过选择器的查询方法,查询注册过的所有socket连接的就绪状态,内核个就绪的socket列表(当内核监控到注册过的socket有数据准备好了,就会将该socket加入到就绪列表)。注意,当用户调用select查询方法时,此时整个线程会处于阻塞状态;

  • 用户线程获取到就绪列表后,根据其中的socket连接,发起read请求,用户线程阻塞,内核开始复制数据,将数据从内核缓冲区复制到用户缓冲区;

  • 复制完成后,内核返回结果,用户线程才会解除阻塞,待用户线程读取成功数据后,线程继续。

IO多路复用优点:

系统不必创建维护大量线程,只使用一个线程,一个选择器即可同时处理成千上万个连接,大大减少了系统开销。

IO多路复用缺点:

本质上,select/epoll系统调用是阻塞式的,属于同步IO,需要在读写事件就绪后,由系统调用进行阻塞的读写。

4、异步IO(Asynchronous IO)

异步IO,指的是用户空间与内核空间的调用方式返过来。内核空间是主动调用者,用户空间变成被动接收者。用户空间的线程向内核空间注册各种IO事件的回调函数,由内核主动触发调用。

AIO的基本流程是:用户线程通过系统调用,向内核注册某个IO操作。在整个内核的数据处理中, 包括数据从网卡到内核缓冲区,内核缓冲区到用户缓冲区,用户程序都不需要阻塞。

具体流程如下:

面试官:说说高并发IO原理及模型,结果没结果了

由上图可以看出,该模式下,在内核等待数据和复制数据的两个阶段,用户线程都不会阻塞。read操作流程如下:

  • 用户发起read请求,内核立刻返回成功状态,用户不会阻塞,可以马上去做其它事情;

  • 内核开始准备数据,待数据准备好了,内核会主动将数据从内核缓冲区复制到用户缓冲区;

  • 此时,内核会给用户线程发送一个信号,或者回调用户线程注册的回调接口,通知用户read操作完成了;

  • 用户线程收到通知后,读取用户缓冲区的数据,完成后续业务。

异步IO优点:

真正实现了异步非阻塞,吞吐量在这几种模式中是最高的。

异步IO缺点:

应用程序只需要进行事件的注册与接收,其余工作都交给了操作系统内核,所以需要内核提供支持。在Linux系统中,异步IO在其2.6才引入,目前也还不是灰常完善,其底层实现仍使用epoll,与IO多路复用相同,因此在性能上没有明显占优。

好了,以上说明了目前常见的四种IO模型,目前市场上高并发主要还是采用IO多路复用模型,我们经常用的Netty也是使用IO多路复用。

文源网络,仅供学习之用,如有侵权请联系删除。

我将面试题和答案都整理成了PDF文档,还有一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,但不仅限于此。

关注公众号【java圈子】获取资料,还有优质文章每日送达。

猜你喜欢

转载自blog.csdn.net/qianlia/article/details/106398540