IO 模型篇

1、执行一次 IO 操作需要哪几个阶段?

应用程序发起一次IO包括两个阶段:

  • IO调用:应用程序进程向操作系统内核发起系统调用
  • IO执行:操作系统内核完成IO操作

或者

  • 数据准备阶段:内核等待IO设备准备好数据,并拷贝到内核缓冲区
  • 数据拷贝阶段:操作系统将数据从内核缓冲区拷贝到用户进程缓冲区

2、IO 模型包括哪些?

  1. 阻塞IO模型(BIO)
  2. 非阻塞IO模型(NIO)
  3. IO多路复用模型(select、poll、epoll)
  4. 信号驱动IO模型
  5. 异步IO模型(AIO)

3、阻塞 IO 和 非阻塞 IO 的区别?

根据数据没有到达时候,用户进程是否阻塞,分别称为阻塞IO和非阻塞IO。
阻塞IO在数据准备阶段和数据拷贝阶段都是阻塞的,这是阻塞IO最大的缺点。
非阻塞IO最大的优点在于,每个线程可以处理多个连接。但是会进行频繁的系统调用,消耗资源。

4、同步 IO 和 异步 IO 的区别?

在数据准备阶段或者数据拷贝阶段,如果任一阶段发生阻塞都是同步IO,两阶段都不阻塞才是真正的异步IO。
在目前所有的操作系统中,linux 中的 epoll、mac 的 kqueue 都属于同步 IO,因为其在第二阶段(数据从内核态到用户态)都会发生拷贝阻塞。而只有 windows 中的 IOCP 才真正属于异步 IO,即 AIO。

5、BIO、NIO 和 AIO的适用场景?

IO类型适用场景
BIO连接数目较少,且固定的结构。
NIO连接数较多,且比较短的架构。
AIO连接数较多且比较长。

6、IO 多路复用模型有哪些?

包括select,poll和epoll。
select同时监听多个fd。一旦内核数据准备好了,select就返回可读状态,这时候应用进程发起recvfrom系统调用去读数据。
select的缺点在于:

  • 监听的IO数最多为1024个;
  • select函数采用轮询的方式,只知道有I/O准备好,却不知道是哪个IO;

poll在select上进行改进,最大监听数无限,但其它相同。
epoll采用事件驱动, epoll先通过一个epoll_ctl()来注册一个fd,一旦基于某个fd就绪,操作系统就会采用回调机制,激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件fd的操作。epoll在传递内核与用户空间的消息时使用了**共享内存,**而不是内存拷贝,因此效率比select和poll高。
总结一下:

selectpollepoll
底层数组链表红黑树和双链表
fd获取方式遍历遍历事件回调
复杂度O(N)O(N)O(1)
最大监听数1024无限无限
fd数据拷贝每次调用select,需要将的数据从用户空间拷贝到内核空间每次调用poll,需要将fd数据从用户空间拷贝到内核空间使用内存映射mmap,不需要将fd数据从用户空间拷贝到内核空间

7、Epoll 的工作模式有哪些?

epoll 对文件描述符有两种操作模式– LT(level trigger水平模式)和ET(edge trigger边缘模式)

  • LT是epoll的默认操作模式,当epoll_wait函数检测到有事件发生并将通知应用程序,而应用程序不一定必须立即进行处理,这样epoll_wait函数再次检测到此事件的时候还会通知应用程序,直到事件被处理。
  • ET模式是高速触发模式,只要epoll_wait函数检测到事件发生,通知应用程序立即进行处理,后续的epoll_wait函数将不再检测此事件(不会再发出通知)。因此ET模式在很大程度上降低了epoll事件被重复触发的次数,因此效率比LT模式高。

8、信号驱动模型

信号驱动 IO 不再主动询问数据是否就绪,而是向内核发送一个信号,然后进程可以做别的事。当内核数据准备好,再通过SIGIO通知进程。应用进程收到信号后,便调用recvfrom。
然而信号驱动,数据复制到应用程序缓冲区时还是阻塞的

9、异步 IO

异步IO实现了真正意义上的异步。应用程序发起系统调用后是立即返回的。只有内核将数据拷贝到用户进程缓冲区,才发送信号通知用户进程。只有Windows的IOCP属于真正的异步IO。
缺点是AIO本身适用于大量连接长连接情景,程序比较复杂,难以调试和扩展。

10、Reactor 模型了解?

Reactor(译:反应堆)模式是一种事件处理机制。将事件以及对应的处理方法在Reactor进行注册,当对应的事件发生,便会调用对应的回调函数,实现了事件驱动框架。这在Redis上得到很好的应用,Redis基于I/O多路复用开发Reactor事件处理机制,监听多个套接字的AE_READABLE读,AE_WRITABLE写事件。读事件绑定读操作和具体执行命令的操作函数,写事件绑定命令回复的操作函数。
reactor可以分为单线程reactor模型多线程reactor模型主从reactor模型

  • 单线程 reactor 模型

如下图,有多个client访问reactor线程,Dispatcher(分派器)将事件转发到不同处理器进行处理 ,其中连接事件转发到acceptor进行处理,读写事件转发到不同的Handler处理函数进行处理。

  • 多线程 reactor 模型

在Reactor多线程模型中。根据事件的不同类型,由Dispatcher将事件转发到不同的角色中处理。连接事件转发到Acceptor单线程处理、读写事件转发到不同的Handler由线程池处理。

  • 主从多 reactor 模型

Reactor多线程模型,由Acceptor接受客户端连接请求后,创建SocketChannel注册到Main-Reactor线程池中某个线程的Select中;具体处理读写事件还是使用线程池处理(Sub-Reactor线程池)。

11、半同步 / 半异步秒杀?

AIO的执行效率能高一点,实时性强,但是他适用于大量并发操作,因为其程序复杂,难以调试和扩展。
对于向服务器这种既要求实时性高,又能同时处理多个客户请求的应用程序。我们就应该同时使用同步和异步两种模型来实现,即半同步/半异步模型。同步线程用于处理用户逻辑,异步线程用于处理I/O事件。异步线程监听到客户请求之后,就将其封装成请求对象,插入到请求队列,请求队列将会通知某个工作在同步线程中的工作线程来读取并请求对象。

12、数据库连接池为什么不采用 IO 多路复用?

IO多路复用不是指多个服务共享一个连接,而仅仅是指多个连接的管理可以在同一进程。
DB 访问一般采用连接池这种现象是生态造成的。历史上的 BIO + 连接池的做法经过多年的发展,已经解决了主要的问题。在 Java 的大环境下,这个方案是非常靠谱的,成熟的。而基于 IO 多路复用的方式尽管在性能上可能有优势,但是其对整个程序的代码结构要求过多,过于复杂。当然,如果有特定的需要,希望使用 IO 多路复用管理 DB 连接,是完全可行的。

13、select、poll、epoll 工作原理

select、poll、epoll 都是 I/O 多路复用的机制。I/O 多路复用可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select、poll、epoll 本质上都是同步 I/O ,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。

select

首先创建事件的描述符集合。对于一个描述符,可以关注其上的读事件、写事件以及异常事件,所以要创建三类事件的描述符集合,分别用来收集读事件描述符、写事件描述符以及异常事件描述符。select 调用时,首先将时间描述符集合 fd_set 从用户空间拷贝到内核空间;注册回调函数并遍历所有 fd ,调用其 poll 方法, poll 方法返回时会返回一个描述读写操作是否就绪的 mask 掩码,根据这个掩码给 fd 赋值,如果遍历完所有 fd 后依旧没有一个可以读写就绪的 mask 掩码,则会使进程睡眠;如果已过超时时间还是未被唤醒,则调用 select 的进程会被唤醒并获得 CPU ,重新遍历 fd 判断是否有就绪的fd;最后将 fd_set从内核空间拷贝回用户空间。

select缺点:

  1. 每次调用 select ,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大
  2. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd ,这个开销在 fd 很多时也很大
  3. select支持的文件描述符数量较小,默认是1024

poll

poll 是 select 的优化版。poll 使用 pollfd 结构而不是 select 的 fd_set 结构。select 需要为读事件、写事件和异常事件分别创建一个描述符集合,轮询时需要分别轮询这三个集合。而 poll 库只需要创建一个集合,在每个描述符对应的结构上分别设置读事件、写事件或者异常事件,最后轮询时可同时检查这三类事件是否发生。

epoll

select 与 poll 中,都创建一个待处理事件列表。然后把这个列表发送给内核,返回的时候再去轮询这个列表,以判断事件是否发生。在描述符比较多的时候,效率极低。epoll 将文件描述符列表的管理交给内核负责,每次注册新的事件时,将 fd 拷贝仅内核,epoll 保证 fd 在整个过程中仅被拷贝一次,避免了反复拷贝重复 fd 的巨大开销。此外,一旦某个事件发生时,内核就把发生事件的描述符列表通知进程,避免对所有描述符列表进行轮询。最后, epoll 没有文件描述符的限制,fd 上限是系统可以打开的最大文件数量,通常远远大于2048 。

select、poll、epoll对比

select,poll实现需要自己不断轮询所有 fd 集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而 epoll 其实也需要调用 epoll_wait 不断轮询就绪链表,期间也可能多次睡眠和唤醒交替。但是它在设备就绪时,调用回调函数,把就绪 fd 放入就绪链表中,并唤醒在 epoll_wait 中进入睡眠的进程。虽然都要睡眠和交替,但是 select 和 poll 在醒着的时候要遍历整个 fd 集合,而 epoll 在醒着的时候只要判断一下就绪链表是否为空就行了,这节省了大量的 CPU 时间。

select、poll、epoll 应用场景

  1. select:timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。select 可移植性更好,几乎被所有主流平台所支持。
  2. poll:poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select
  3. epoll:只运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接;需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势;需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。

14、BIO、NIO、AIO工作原理

BIO

BIO 全称 Block-IO 是一种同步阻塞的通信模式。BIO 是一种比较传统的通信方式,模式简单,使用方便。但并发处理能力低,通信耗时,依赖网速。服务器通过一个 Acceptor 线程负责监听客户端请求和为每个客户端创建一个新的线程进行链路处理。典型的请求一应答模式。若客户端数量增多,频繁地创建和销毁线程会给服务器打开很大的压力。后改良为用线程池的方式代替新增线程,被称为伪异步IO。BIO 模型中通过 Socket 和 ServerSocket 完成套接字通道的实现。BIO 具有阻塞性,同步性。

NIO

NIO 全称 New IO,也叫 Non-Block IO 是一种非阻塞同步的通信模式。NIO 相对于 BIO 来说一大进步。客户端和服务器之间通过 Channel 通信。NIO 可以在 Channel 进行读写操作。这些 Channel 都会被注册在 Selector 多路复用器上。Selector 通过一个线程不停的轮询这些 Channel 。找出已经准备就绪的 Channel 执行 IO 操作。NIO 通过一个线程轮询,实现千万个客户端的请求,这就是非阻塞 NIO 的特点。NIO 的特性如下:

  1. 缓冲区Buffer:它是 NIO 与 BIO 的一个重要区别。BIO 是将数据直接写入或读取到 Stream 对象中。而 NIO 的数据操作都是在缓冲区中进行的。缓冲区实际上是一个数组。 Buffer 最常见的类型是 ByteBuffer ,另外还有 CharBuffer , ShortBuffer , IntBuffer , LongBuffer , FloatBuffer , DoubleBuffer
  2. 通道Channel:和流不同,通道是双向的。NIO 可以通过 Channel 进行数据的读,写和同时读写操作(全双工?)。通道分为两大类:一类是网络读写,一类是用于文件操作
  3. 多路复用器Selector:NIO 编程的基础。多路复用器提供选择已经就绪的任务的能力。就是 Selector 会不断地轮询注册在其上的通道(Channel),如果某个通道处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以取得就绪的 Channel 集合,从而进行后续的 IO 操作。服务器端只要提供一个线程负责 Selector 的轮询,就可以接入成千上万个客户端,这就是JDK NIO库的巨大进步
  4. NIO 模型中通过 SocketChannel 和 ServerSocketChannel 完成套接字通道的实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销。

AIO

AIO 也叫 NIO2.0 是一种非阻塞异步的通信模式。在 NIO 的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。AIO 并没有采用 NIO 的多路复用器,而是使用异步通道的概念。其 read, write 方法的返回类型都是 Future 对象。而 Future 模型是异步的。AIO 模型中通过 AsynchronousSocketChannel 和AsynchronousServerSocketChannel 完成套接字通道的实现。AIO 具有非阻塞性和异步性。
从编程模式上来看 AIO 相对于 NIO 的区别在于,NIO 需要使用者线程不停的轮询 IO 对象,来确定是否有数据准备好可以读了。而 AIO 则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。当然 AIO 的异步特性并不是 Java 实现的伪异步,而是使用了系统底层 API 的支持。在 Unix 系统下,采用了epoll IO 模型,而 windows 便是使用了 IOCP 模型。

15、BIO VS NIO

  1. BIO是面向流(字节流和字符流)操作的,而NIO是面向缓冲区操作的
  2. BIO是阻塞性IO,而NIO是非阻塞性IO
  3. BIO不支持选择器,而NIO支持选择器
  4. 同步阻塞IO : 用户进程发起一个 IO 操作以后,必须等待 IO 操作的真正完成后,才能继续运行
  5. 同步非阻塞IO: 用户进程发起一个 IO 操作以后可做其它事情,但用户进程需要经常询问 IO 操作是否完成,这样造成不必要的 CPU 资源浪费
  6. 异步非阻塞IO: 用户进程发起一个 IO 操作然后立即返回,等 IO 操作真正的完成以后,应用程序会得到 IO 操作完成的通知

16、AIO VS NIO

  1. NIO 需要使用者线程不停地轮训 IO 对象,来确定是否有数据准备好并可读
  2. AIO 在数据准备好后通知使用者,避免了使用者的不断轮训
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值