Netty核心前奏——零拷贝篇(六)

零拷贝基本介绍

  1. Java中,常用的零拷贝有mmap(内存映射),sendfile,dma,directl/O等
  2. 在操作系统中,零拷贝指的是避免在用户态 (User-space) 与内核态(Kernel-space) 之间来回拷贝数据.

OS的I/O普通读写流程

Netty核心前奏——零拷贝篇(六)_第1张图片

  1. 用户read发起系统调用,由用户态进入内核态,通过DMA技术将磁盘中的数据copy到内核缓冲区中
  2. 当DMA完成工作后,会发起一个中断通知CPU数据拷贝完成,然后CPU再将内核态中的数据copy到用户态中
  3. 内核唤醒对应线程,同时将用户态的数据返回给该线程空间
  4. 用户态线程进行业务处理(堆内存、堆外内存倒腾数据)
  5. 当服务器对请求进行响应的时候,会发起系统调用,由内核将用户态的数据copy到内核态中
  6. 复制完毕后,再有网络适配器通过DMA技术将内核态缓冲区中的数据copy到网卡中,完成后,内核态会返回到用户态
  7. 最后由网卡将数据发送出去

总结:在这个过程中,如果不考虑用户态的内存拷贝和物理设备到驱动的数据拷贝,我们会发现,这其中会涉及4次数据拷贝。同时也会涉及到4次进程上下文的切换。所谓的零拷贝,作用就是通过各种方式,在特殊情况下,减少数据拷贝的次数/减少CPU参与数据拷贝的次数

DMA(Direct Memory Access

  1. Direct Memory Access,DMA的作用就是直接将I/O设备的数据拷贝到内核缓区中
  2. 使用DMA的好处就是I/O设备到内核之间的数据拷贝不需要CPU的参与,CPU只需要给DMA发送copy指令即可,提高了处理器的利用效率

Netty核心前奏——零拷贝篇(六)_第2张图片

Mmap全称memory map

  1. mmap 通过内存映射,将文件映射到内核缓冲区,也就说将内核态和用户态的内存映射到一起,避免来回复制,也不必反复调用read/write等函数;同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。
  2. 一般来讲,mmap会代read方法:Netty核心前奏——零拷贝篇(六)_第3张图片
  3. 如果这个时候系统进行I/O的话,采用mmap + write的方式,内存拷贝的次数会变为3次,上下文切换则依旧是4次。

sendFile优化 

Linux 2.1提供了sendFile函数,原理:数据根本不经过用户态,直接从内核缓冲区进入到Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换

Netty核心前奏——零拷贝篇(六)_第4张图片

注意:零拷贝从操作系统角度来看没有cpu拷贝

sendfile + DMA Scatter/Gather

  • Linux2.4版本中做了一些修改它可以读page cache中的数据描述信息 (内存地址和偏移量) 记录到socket cache中,由 DMA 根据这些将数据从读缓冲区拷贝到网卡/协议栈,比之前版本减少了一次CPU拷贝的过程

Netty核心前奏——零拷贝篇(六)_第5张图片

这里其实有一次cpu拷贝 kernel buffer -> socket buffer 但是,拷贝的信息很少,比如 length,offset,消耗低,可以忽略

Direct I/O

  1. 之前的mmap可以让用户态和内核态共用一个内存空间来减少拷贝,其实还有一个方式,就是硬件数据不经过内核态的空间,直接到用户态的内存中,这种方式就是Direct I/O
  2. 换句话说,Direct I/O不会经过内核态,而是用户态和设备的直接交互,用户态的写入就是直接写入到磁盘,不会再经过操作系统刷盘处理。
  3. 这样确实拷贝次数减少,读取速度会变快,但是因为操作系统不再负责缓存之类的管理,这就必须交由应用程序自己去做,譬如MySQL就是自己通过Direct I/O完成的,同时MySQL也有一套自己的缓存系统
  4. 同时,虽然Direct I/O可以直接将文件写入磁盘中,但是文件相关的元信息还是要通过fsync缓存到内核空间中

对零拷贝的理解(补充:五个方面)

  1. 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)。
  2. 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算。
  3. 五个方面:
    • 直接使用堆外内存,避免JVM 堆内存到堆外内存的数据拷贝
    • CompositeByteBuf 类,可以组合多个 Buffer 对象合并成一个逻辑上的对象,避免通过传统内存拷贝的方式将几个 Buffer 合并成一个大的 Buffer。
    • 通过 Unpooled.wrappedBuffer 可以将 byte 数组包装成 ByteBuf对象,包装过程中不会产生内存拷贝
    • ByteBuf.slice 操作与 Unpooled.wrappedBuffer 相反,slice 操作可以将一个 ByteBuf 对象切分成多个ByteBuf 对象,切分过程中不会产生内存拷贝,底层共享一个 byte 数组的存储空间。
    • 使用 FileRegion 实现文件传输,FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓区和用户态缓区之间的数据拷贝,这属于操作系统级别的零拷贝。

mmap和sendFile的区别

  1. mmap适合小数据量读写,sendFile适合大文件传输
  2. mmap需要4次上下文切换,3次数据拷贝;sendFile需要3次上下文切换,最少2次数据拷贝
  3. sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

零拷贝应用案例--复制文件

NewIOServer

Netty核心前奏——零拷贝篇(六)_第6张图片

Netty核心前奏——零拷贝篇(六)_第7张图片

 AIO基本介绍

JDK7引入A Synchronous I/O,常用Reactor和Proactor模式。NIO就是用的Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。目前应用不多。

 原生NIO存在的问题

  1. NIO的类库和API繁杂,使用麻烦:需要熟练握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
  2. 要熟悉 Java 多线程编程,因为NIO编程涉及到Reactor模式,必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。
  3. 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。
  4. JDK NIO的Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。直到 JDK 1.7版本该问题仍旧存在,没有被根本解决。

你可能感兴趣的:(Netty,网络,java)