当前位置:网站首页>【RPC】I/O模型——BIO、NIO、AIO及NIO的Rector模式

【RPC】I/O模型——BIO、NIO、AIO及NIO的Rector模式

2022-06-25 09:39:00 牧心.

1. I/O模型

1.1 用户进程与系统进程

在了解I/O操作的过程之前,先介绍一下什么是用户进程和系统进程:

  • 用户进程:通过执行用户进程或内核之外的程序而产生的进程,该类进程可以在用户的控制下运行或者关闭。
  • 系统进程:可以执行内存资源分配和进程切换等管理操作,系统进程一般无法使用用户权限进行控制。

用户进程所在的区域叫做用户空间,系统进程所在的区域叫做内核空间。

用户进程不可以直接访问系统数据或者资源,他没有办法从内核态空间直接获取资源,需要将数据从内核态空间复制到用户空间,再获取用户空间中的数据。处于用户态的程序只能访问用户空间,只有处于内核态的程序才可以访问用户空间和内核空间。

如果用户进程需要访问系统资源或者使用系统数据,则必须调用操作系统提供的接口。这个调用接口的过程就是用户态进程主动要求从用户态切换到内核态的一种方式,也就是平常所说的系统调用。

1.2 一次I/O操作经历了什么?

以一次数据的读取为例,无论磁盘I/O还是网络I/O,第一步都是用户进程等待内核准备数据,内核准备数据的阶段会将数据复制到内核空间,用户进程也从用户态转变为内核态。磁盘I/O和网络I/O唯一不同的是磁盘I/O中内核从磁读取磁盘数据,而在网络I/O中内核从网卡中读取流数据。数据准备完毕收,第二步就是从内核空间将数据复制到目标用户空间供用户进程读取。

1.3 I/O模型是什么?

了解了一次I/O操作的过程后,那么平常说的I/O模型又是什么呢?

I/O模型可以理解为机器上进行数据读/写调度的策略模型。

I/O模型可以从两个维度去理解:

  • 1)阻塞与非阻塞。用户进程发起I/O操作后,根据是否需要等待I/O操作完成才能继续运行,可以分为阻塞与非阻塞两种。

  • 2)同步与异步。在用户进程发起I/O操作后,会由用户态转变为内核态,内核进程在完成复制数据的操作后执行I/O操作,操作执行完成后,需要从内核态转变为用户态,并且需要执行复制数据的操作。

    • 如果是同步I/O操作,则在将数据复制到用户空间的过程中,用户线程会阻塞。
    • 如果是异步I/O操作,则内核线程直接执行复制数据到用户空间,完成复制数据的操作后才通知用户进程I/O操作完成,不会造成用户线程直接执行复制数据的时候阻塞。也就是内核进程负责将数据复制到用户进程,用户进程可以直接执行后续操作。

    同步和异步针对的对象是内核进程。

Linux有5种I/O模型,分别是:

  • BIO(Blocking I/O):用户进程发起I/O操作后,内核进入数据准备阶段。数据还未全部准备好时,内核一直处于数据准备节点,而用户进程也被阻塞。
  • NIO(Non-blocking I/O):用户进程发起I/O操作后,内核进入数据准备阶段。数据还未全部准备好时,内核一直处于数据准备阶段,而用户进程暂时先去做其他事情,但是用户进程会每隔一段时间询问数据是否准备好了。一旦准备好了,用户进程将会继续后续的操作。
  • I/O Multiplexing:该模型主要是单个进程就可以同时处理多个网络连接的I/O模型,本质上用户进程还是阻塞的,但是select不会被阻塞,select/poll/epoll的方法不断地轮询所负责的而所有Socket,当某个Socket有数据到达时,就通知对应的用户进程。
  • Singal Driven I/O:当用户进程执行I/O操作(读或写)时,内核马上返回,进程继续运行,在内核进程完成I/O操作(或出错)后,通过信号通知进程。
  • AIO(Asychronous I/O):内核线程直接执行复制数据的过程,完成数据复制后才通知用户进程I/O操作完成,不会造成用户线程在复制数据的时候阻塞。

前四种是同步的,最后一种是异步的。

2. Java对I/O模型的封装

Java对I/O模型的封装可以分为BIO、NIO和AIO三种。

2.1 BIO

在这里插入图片描述

一个客户端的连接请求被接收器接收后,在服务端会创建一个线程与该客户端进行通信,这个线程一般会执行读取、解码、计算、编码、发送这几个步骤。

这种模式非常耗费系统资源,首先在服务器端需要一个接收器一直处于阻塞状态等待新的客户端请求,其次每当有新的客户端请求连接时,都需要创建新的线程来处理请求,每个线程在处理完请求后都需要销毁。线程的创建和销毁会占用系统资源。

于是在该架构上又增加了线程池的设计,如下图:
在这里插入图片描述

当服务端收到客户端的连接请求后,作为一个任务丢给线程池,让线程池来分配线程执行与客户端通信的逻辑。

虽然加入线程池的设计可以提升系统性能,但是当请求量增大、线程数过高时,线程池频繁切换线程带来的成本问题也会暴露出来;并且在服务端需要一个线程一直阻塞等待客户端的连接请求,在客户端的线程也会因为服务端还没有把请求结果返回而一直阻塞等待。

2.2 NIO

Java NIO和Linux中的非阻塞I/O有一些区别,他更像是Linux中的非阻塞多路复用I/O,它可以帮助Java并发构建同步非阻塞的多路复用I/O应用程序,同时提供更接近底层高性能的数据操作方式,并且它还支持select/poll模型,在JDK1.5之后又增加了对epoll的支持。
在这里插入图片描述

介绍一下三个重要的概念:

  • Channel(通道):Java提供了四种Channel,分别是FileChannel(用于文件操作)、SocketChannel(用于客户端TCP操作)、ServerSocketChannel(用于服务端TCP操作)、DatagramChannel(用于UDP操作)。

    • Channel是一个双向的数据读、写通道。 Channel可以实现读和写同时操作,并且同时支持阻塞和非阻塞模式。
    • 一个客户端连接对应一个Channel,当有新的连接请求时,会向Selector中注册一个Channel,并且会将Channel与对应的事件处理器进行绑定。
    • 事件处理器包含该通道里面的各类事件对应的处理逻辑,事件处理器一共有4中,分别是读数据事件处理器、写数据事件处理器、连接事件处理器、接受事件处理器。
  • Buffer(缓冲区):可以将Buffer看作一个存储数据的容器,Channel可以对Buffer进行读/写操作,并且所有数据的读/写都会经过缓冲区。在NIO中,有两种不同的缓冲区:

    • 直接缓冲区:可以直接操作JVM的堆外内存,即在系统内核缓存中分配的缓冲区,通过allocateDirect()方法可以分配直接缓冲区。
    • 非直接缓冲区:只能操作JVM的堆中内存,通过allocate()方法可以分配非直接缓冲区。

    Buffer有许多实现,比如ByteBuffer、CharBUffer、LongBuffer等,分别对应一种数据类型的实现。

  • Selector(选择器):Selector通过不断轮询注册在其上的Channel来选择分发已处理就绪的事件,这里的事件有四种,分别是连接事件、接收连接事件、读事件和写事件。

    • Selector可以同时轮询和监控多个Channel,当Selector发现某个Channel的数据状态发生变化时,会通过SelectorKey出发相关事件,并由对此事件感兴趣的事件处理器来执行相关逻辑,数据则从Buffer中获取,执行完成后再写回Buffer,以供Channel读取。

了解了以上概念之后,整个NIO的处理过程也就比较简单明了了。从上面的处理结构图可以看到,当一个客户端连接到来时,服务端会为这个新的连接创建一个Channel,并将这个Channel注册到Selector上,Selector会监控所有注册在自己身上的Channel,一旦某个Channel的数据状态发生改变,比如数据读取完毕,则会触发相关的事件。Channel的数据读取和写入都只与Buffer进行交互。


下文待续~

2.3 NIO的Rector模式

2.3.1 单Rector单线程模型

2.3.2 单Rector多线程模型

2.3.3 主从Rector多线程模型

2.4 AIO

参考资料:《深入理解RPC框架原理与实现》 华钟明著.

原网站

版权声明
本文为[牧心.]所创,转载请带上原文链接,感谢
https://muxin.blog.csdn.net/article/details/125420817