Netty学习之零拷贝(OS零拷贝与Netty零拷贝)

前言:Netty作为异步事件驱动的网络框架,高性能主要来自于其I/O模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据。

 

一、Netty高性能的原因总结 :

  1. 基于I/O多路复用模型

  2. 零拷贝(用户空间里的零拷贝)

  3. 基于NIO的Buffer

  4. 基于内存池的缓冲区重用机制

  5. 无锁化的串行设计理念

  6. I/O操作的异步处理

  7. 提供对protobuf等高性能序列化协议支持

  8. 可以对TCP进行更加灵活地配置

 

二、何为零拷贝

“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.“ --wiki

这是wiki百科里面的介绍,通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式,避免了数据在用户空间和内存空间之间的拷贝。这是OS层面上的零拷贝,也是传统意义上的零拷贝。例子:Linux 提供的 mmap 系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后, 用户对这段内存区域的修改可以直接反映到内核空间

 

三、Netty中的零拷贝

1.Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

2.Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。(减少多余操作)

3.通过 FileRegion 包装的FileChannel.tranferTo方法 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环write方式导致的内存拷贝问题。(减少了文件缓存区到buffer的操作)

4.通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作。

 

CompositeByteBuf
我们知道在基于流的传输过程(如TCP/IP)中,数据包有可能会被重新封装在不同的数据包中,而单个的数据包对应用而言是没有意义的,只有当这些数据包组成一条完整的消息时才能做出正确的处理。为此我们可能会将这些零碎的数据包拼接成一个新的 ByteBuf 来处理,这其中无形之中会增加两次额外的数据拷贝操作了。

Netty 使用 CompositeByteBuf 类实现了将多个 ByteBuf 合并为一个逻辑上的 ByteBuf。我们看一下 CompositeByteBuf 的内部结构

  private final ByteBufAllocator alloc;
    private final boolean direct;
    private final List components;
    private final int maxNumComponents;
  private static final class Component {
        final ByteBuf buf;
        final int length;
        int offset;
        int endOffset;

        Component(ByteBuf buf) {
            this.buf = buf;
            length = buf.readableBytes();
        }

        void freeIfNecessary() {
            buf.release(); 
        }
    }

我们看到在 CompositeByteBuf 内部有一个包含Component 类型对象的list,而 Component 内部包含一个对 ByteBuf 对象的引用。虽然看起来 CompositeByteBuf 是由多个 ByteBuf 组合而成的,不过在 CompositeByteBuf 内部,这些个 ByteBuf 都是单独存在的,CompositeByteBuf 只是逻辑上是一个整体,这样就避免了数据的拷贝,实现了零拷贝。

 

FileRegion

Netty 文件传输类 DefaultFileRegion 通过 transferTo 方法将文件发送至目标 Channel 中,Netty 官网上有如下代码,

public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    RandomAccessFile raf = null;
    long length = -1;
    try {
        // 1. 通过 RandomAccessFile 打开一个文件.
        raf = new RandomAccessFile(msg, "r");
        length = raf.length();
    } catch (Exception e) {
        ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');
        return;
    } finally {
        if (length < 0 && raf != null) {
            raf.close();
        }
    }

    ctx.write("OK: " + raf.length() + '\n');
    if (ctx.pipeline().get(SslHandler.class) == null) {
        // SSL not enabled - can use zero-copy file transfer.
        // 2. 调用 raf.getChannel() 获取一个 FileChannel.
        // 3. 将 FileChannel 封装成一个 DefaultFileRegion
        ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
    } else {
        // SSL enabled - cannot use zero-copy file transfer.
        ctx.write(new ChunkedFile(raf));
    }
    ctx.writeAndFlush("\n");
}

  Netty 使用了 DefaultFileRegion 来封装一个 FileChannel,然后就可以直接通过它将文件的内容直接写入 Channel 中,而不需要像传统方式拷贝文件内容到临时 buffer,然后再将 buffer 写入 Channel。

                                                                                                                                                                         --《Netty权威指南 第二版》

 

 

你可能感兴趣的:(学习netty)