美文网首页
BIO&NIO&AIO

BIO&NIO&AIO

作者: 哓晓的故事 | 来源:发表于2019-01-28 13:17 被阅读0次

    BIO/NIO/AIO原理

    IO模型

    一个输入操作通常包括两个阶段:

    1. 等待数据准备好
    2. 从内核向进程复制数据

    对于一个套接字上的输入操作
    第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区
    第二步就是把数据从内核缓冲区复制到应用进程缓冲

    什么是阻塞

    网络交互,系统可预见的没有读到数据,是阻塞
    磁盘交互,系统不可预见的抖动,导致IO阻塞一会,不是阻塞

    BIO和NIO

    BIO每个连接都需要占用一个线程没有读到IO流的时候都处于阻塞

    IO阻塞会导致线程无法释放,即便是不需要IO时也会阻塞
    会导致服务端线程增大,线程池也无法解决
    

    NIO每个连接都需要注册一个事件监听(select/poll/epoll)任何时候立即返回IO流的当前数据

    线程不会因为IO阻塞而无法释放,可以去做额外的事情
    线程只有在需要IO的时候才会被阻塞住
    

    IO多路复用

    操作系统提供一种机制(poll、select、epoll),允许注册IO请求,当有任何一个请求被触发,会有反馈
    poll、select每次都要遍历所有的注册,并且轮询
    epoll只会返回对应被触发的注册时间(并且提供了边缘触发,允许有条件的获取数据),并轮询

    NIO实现

    NIO = 非阻塞(立即放回)IO + IO多路复用(通知IO就绪了) + 缓存(ByteBuffer)
    NIO特有的是 selector(实现了IO多路复用策略) + buffer 只是更加优化的方式

    1. 单Reactor单线程模型
    2. 单Reactor多线程模型:有一个Thread-Acceptor线程用于监听接收客户端的TCP连接事件/IO读写事件(N条链路)。有一个ThreadPool-负责网络IO的读写操作
    3. 主从Reactor多线程模型:有一个Thread-Acceptor线程用于监听接收客户端的TCP连接事件,有一个ThreadPool专门负责IO读写事件。有一个ThreadPool-负责网络IO的读写操作
    4. Netty线程模型(对主从Reactor多线程模型做出细微改造)
      MainReactor负责客户端的连接请求,并将请求转交给 SubReactor
      SubReactor负责相应通道的IO读写请求
      非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理
    netty.png

    BIO & NIO 优势

    BIO少量的连接使用非常高的带宽,一次发送大量的数据
    NIO:(交易信息上传)如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据

    磁盘IO

    磁盘的最小操作单位簇(sector),对于Linux来说,虚拟文件系统(VFS)抽象了磁盘设备,统一称为“设备”(block device)。数据是按照一块块来组织的。操作系统可以随机的定位到某个“块”,读写某个“块”
    ->的转换,是由设备驱动来完成的
    在VFS上层的应用是感受不到“簇”的,他们只能感受到“块”
    即使你只想读取1个Byte,磁盘也至少要读取1个块;要写入1个Byte,磁盘也至少要写入一个块

    虚拟文件系统层VFS之上,是内存。这一层被称为Page Cache(在内核态)
    在内存之上的是应用程序,应用程序总是需要在用户态分配一段内存空间作为buffer,然后将Page Cache中的数据copy出来进行处理。处理完成后,将数据写回(copy回)到Page Cache

    image.png
    PageCache和BlockDevice.png

    Page Cache对于磁盘IO性能表现极度重要。比如,当通过write API写入数据到磁盘时,数据先会被写入到Page Cache。此时,这个Page被称为“dirty page”。dirty page会最终被写入到磁盘上,这个过程为称之为“写回”(writeback)。写回往往不会立刻发生。写回可能由于调用者直接使用类似于fsync这样的API,也有可能因为操作系统根据某种策略和算法决定自动写回。写回发生之前,如果机器挂了,就有可能丢失数据。这也是为什么有持久性要求的程序都需要用fsync来保证数据落地的原因。

    上图会出现两次CPU copy,分别是从内核态->用户态用户态->内核态

    AIO(异步IO)

    AIO在操作系统层面, 只支持磁盘IO, 不支持网络IO, 并且上层(nodejs,Java NIO)都会选择用线程池+BIO来模拟文件AIO
    网络IO,需要用epoll这样的IO多路复用技术结合

    为了避免2次CPU copy,可以采用mmapsendfile技术
    同步 I/O:将数据从内核缓冲区复制到应用进程缓冲区的阶段,应用进程会阻塞通知复制是阻塞和非阻塞IO来简化
    异步 I/O:不会阻塞

    mmap

    mmap.png
    采用Page Cache中的内核空间内存地址直接映射用户空间

    sendfile

    sendfile可以直接将Page Cache中某个fd的一部分数据传递给另外一个fd, 目标的fd可以是socket,而不用经过到应用层的两次copy无法做任何修改,称这种实现为“Zero Copy”

    sendfile.png

    Direct IO

    Linux允许应用程序在执行磁盘IO时绕过缓冲区高速缓存,从用户空间直接将数据传递到文件或磁盘设备,称为直接IO(direct IO)或者裸IO(raw IO
    DirectBuffer 需要 AlignedBlock对齐块

    相比“Buffered IO”,Direct IO必然会带来性能上的降低。所以Direct IO有特定的应用场景。比如,在数据库的实现中,为了保证数据持久,写入新数据到WAL(Write Ahead Log)必须直接写入到磁盘,不能等待。这里用Direct IO来实现WAL就非常理想。
    使用Direct IO的另外一种场景是,应用程序对磁盘数据缓存有特别定制的需要,而常规的Page Cache的各种策略并不能满足这种需要。于是开发人员可以自己设计和实现一套“Cache”,配合Direct IO。毕竟最熟悉数据访问场景的,是应用程序自己的需求。

    • 用于传递数据的缓冲区(buffer),其内存边界必须对齐(aligned block)块大小(block size)的整数倍
    • 数据传输的开始点,即文件和设备的偏移量offset,必须是块大小(block size)的整数倍
    • 待传递数据的长度必须是块大小(block size)的整数倍

    以上约束的是内存写入磁盘时, 内存的规定. 不遵守上述任一限制均将导致EINVAL错误

    相关文章

      网友评论

          本文标题:BIO&NIO&AIO

          本文链接:https://www.haomeiwen.com/subject/ldlajqtx.html