您当前的位置: 首页 > 

cuiyaonan2000

暂无认证

  • 1浏览

    0关注

    248博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

InPut And OutPut

cuiyaonan2000 发布时间:2022-03-04 16:12:08 ,浏览量:1

序言

我们知道Spring在2017年下半年迎来了Webflux,Webflux的出现填补了Spring在响应式编程上的空白,Webflux的响应式编程不仅仅是编程风格的改变,而且对于一系列的著名框架,都提供了响应式访问的开发包,比如Netty、Redis等等。

传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的,在Servlet3.1之后才有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor线程模型的相关API实现的。

如上异步非阻塞就是 AIO,它的2个其它的兄弟是BIO和NIO.

AIO、BIO、NIO是从技术上来解决I/O瓶颈的手段,现在随着时代的发展,内存和CPU已经不再是限制程序的瓶颈了,I/O瓶颈的解决更需要我们关注,因此I/O相关知识的了解,能帮助你做出更好的决策cuiyaonan2000@163.com

参考资料:

  • https://www.cnblogs.com/blackjoyful/p/11534985.html

I/O

在计算机系统中I/O就是输入(Input)和输出(Output)的意思,

针对不同的操作对象,可以划分为:

  1. 磁盘I/O模型
  2. 网络I/O模型
  3. 内存映射I/O
  4. Direct I/O
  5. 数据库I/O等

只要具有输入输出类型的交互系统都可以认为是I/O系统,也可以说I/O是整个操作系统数据交换与人机交互的通道,这个概念与选用的开发语言没有关系,是一个通用的概念。

如下的分析的很到位:

在如今的系统中I/O却拥有很重要的位置,现在系统都有可能处理大量文件,大量数据库操作,而这些操作都依赖于系统的I/O性能,也就造成了现在系统的瓶颈往往都是由于I/O性能造成的。

因此,为了解决磁盘I/O性能慢的问题,系统架构中添加了缓存来提高响应速度;或者有些高端服务器从硬件级入手,使用了固态硬盘(SSD)来替换传统机械硬盘;(I/O瓶颈的硬件提升策略cuiyaonan2000@163.com)

在大数据方面,Spark越来越多的承担了实时性计算任务,而传统的Hadoop体系则大多应用在了离线计算与大量数据存储的场景,这也是由于磁盘I/O性能远不如内存I/O性能而造成的格局(Spark更多的使用了内存,而MapReduece更多的使用了磁盘)。

因此,一个系统的优化空间,往往都在低效率的I/O环节上,很少看到一个系统CPU、内存的性能是其整个系统的瓶颈。也正因为如此,Java在I/O上也一直在做持续的优化,从JDK 1.4开始便引入了NIO模型,大大的提高了以往BIO模型下的操作效率。

进程中的IO调用步骤大致可以分为以下四步(这里一定要注意 系统对于进程 一个是输入,一个是输出的关系.他们组成了I/O一个闭环cuiyaonan2000@163.com):

  1. 进程向操作系统请求数据 ;
  2. 操作系统把外部数据加载到内核的缓冲区中;
  3. 操作系统把内核的缓冲区拷贝到进程的缓冲区 ;
  4. 进程获得数据完成自己的功能 ;

BIO(Blocking I/O)

同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。

这是最基本与简单的I/O操作方式,其根本特性是做完一件事再去做另一件事,一件事一定要等前一件事做完,这很符合程序员传统的顺序来开发思想,因此BIO模型程序开发起来较为简单,易于把握。但是BIO如果需要同时做很多事情(例如同时读很多文件,处理很多tcp请求等),就需要系统创建很多线程来完成对应的工作,因为BIO模型下一个线程同时只能做一个工作,如果线程在执行过程中依赖于需要等待的资源,那么该线程会长期处于阻塞状态,我们知道在整个操作系统中,线程是系统执行的基本单位,在BIO模型下的线程 阻塞就会导致系统线程的切换(等待线程要占CPU,则当前CPU要从处理的线程中先退出来,在去处理等待线程cuiyaonan2000@163.com),从而对整个系统性能造成一定的影响(CPU切换线程对系统的影响比计较大)。当然如果我们只需要创建少量可控的线程,那么采用BIO模型也是很好的选择,但如果在需要考虑高并发的web或者tcp服务器中采用BIO模型就无法应对了,如果系统开辟成千上万的线程,那么CPU的执行时机都会浪费在线程的切换中,使得线程的执行效率大大降低。此外,关于线程这里说一句题外话,在系统开发中线程的生命周期一定要准确控制,在需要一定规模并发的情形下,尽量使用线程池来确保线程创建数目在一个合理的范围之内,切莫编写线程数量创建上限的代码。

NIO(New I/O)

同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。

关于NIO,国内有很多技术博客将英文翻译成No-Blocking I/O,非阻塞I/O模型 ,当然这样就与BIO形成了鲜明的特性对比。NIO本身是基于事件驱动的思想来实现的,其目的就是解决BIO的大并发问题(这里的事件驱动是比较重要的一个概念cuiyaonan2000@163.com),在BIO模型中,如果需要并发处理多个I/O请求,那就需要多线程来支持,NIO使用了多路复用器机制,以socket使用来说,多路复用器通过不断轮询各个连接的状态,只有在socket有流可读或者可写时,应用程序才需要去处理它,在线程的使用上,就不需要一个连接就必须使用一个处理线程了,而是只是有效请求时(确实需要进行I/O处理时),才会使用一个线程去处理,这样就避免了BIO模型下大量线程处于阻塞等待状态的情景。

相对于BIO的流,NIO抽象出了新的通道(Channel)作为输入输出的通道,并且提供了缓存(Buffer)的支持,在进行读操作时,需要使用Buffer分配空间,然后将数据从Channel中读入Buffer中,对于Channel的写操作,也需要现将数据写入Buffer,然后将Buffer写入Channel中。

实例:

package cui.yao.nan.highlevetest.forkjoin;

import reactor.core.Exceptions;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Author: cuiyaonan2000@163.com
 * @Description: todo
 * @Date: Created at 2022-3-4  15:27
 */
public class Test3 {


    public static void main(String[] args) throws Exception {
        //开启写入流
        FileInputStream fileInputStream = new FileInputStream("");
        //开入写出流
        FileOutputStream fileOutputStream = new FileOutputStream("");

        //写入流的Channel
        FileChannel readChannel = fileInputStream.getChannel();
        //写出流的Channel
        FileChannel writeChannel = fileOutputStream.getChannel();

        //缓存信息
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while(true){
            //清空缓存
            buffer.clear();
            //从Channel中读取数据到缓存中
            if (readChannel.read(buffer) == -1 ){
                break;
            }
            //将缓存区游标置于0
            buffer.flip();
            //将缓存中数据写入输出channel中
            writeChannel.write(buffer);
        }
        readChannel.close();
        writeChannel.close();
    }
}

通过比较New IO的使用方式我们可以发现,新的IO操作不再面向 Stream来进行操作了(JDK1.4引入NIO的优化cuiyaonan2000@163.com),改为了通道Channel,并且使用了更加灵活的缓存区类Buffer,Buffer只是缓存区定义接口, 根据需要,我们可以选择对应类型的缓存区实现类。在java NIO编程中,我们需要理解以下3个对象Channel、Buffer和Selector。---------这里有个关键的点:新的IO不在面向Stream即流进行操作了cuiyaonan2000@163.com

Channel

首先说一下Channel,国内大多翻译成“通道”。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream。而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作

NIO中的Channel的主要实现有(这里挺重要的cuiyaonan2000@163.com):

  • FileChannel(对应文件IO)
  • DatagramChannel(对应UDP)
  • SocketChannel (对应TCP的Client)
  • ServerSocketChannel(对应TCp的Server)

Buffer

NIO中的关键Buffer实现有(MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等这里先不具体陈述其用法细节):

  • ByteBuffer(对应byte)
  • CharBuffer(对应char)
  • DoubleBuffer(对应double)
  •  FloatBuffer(对应float)
  • IntBuffer(对应int)
  • LongBuffer(对应long)
  • ShortBuffer(对应short)

Selector(相当于监视者)

Selector 是NIO相对于BIO实现多路复用的基础,Selector 运行单线程处理多个 Channel(一个Selector同时处理多个Channel,就是上面举例中,轮询监察开水壶是否开了的的线程cuiyaonan2000@163.com),如果你的应用打开了多个通道,但每个连接的流量都很低,使用 Selector 就会很方便。例如在一个聊天服务器中。要使用 Selector , 得向 Selector 注册 Channel,然后调用它的 select() 方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收等。

AIO( Asynchronous I/O)

异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。

从编程模式上来看AIO相对于NIO的区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。当然AIO的异步特性并不是Java实现的伪异步,而是使用了系统底层API的支持,在Unix系统下,采用了epoll IO模型,而windows便是使用了IOCP模型。关于Java AIO,本篇只做一个抛砖引玉的介绍,如果你在实际工作中用到了,那么可以参考Netty在高并发下使用AIO的相关技术。

关注
打赏
1638267374
查看更多评论
立即登录/注册

微信扫码登录

0.1708s