Netty 零拷貝(三)Netty 對零拷貝的改進

Netty 零拷貝(三)Netty 對零拷貝的改進

Netty 系列目錄 (http://www.javashuo.com/article/p-hskusway-em.html)html

Netty 的「零拷貝」主要體現如下幾個方面:java

  1. Netty 的接收和發送 ByteBuffer 採用 DIRECT BUFFERS,使用堆外直接內存進行 Socket 讀寫,不須要進行字節緩衝區的二次拷貝。若是使用傳統的堆內存(HEAP BUFFERS)進行 Socket 讀寫,JVM 會將堆內存 Buffer 拷貝一份到直接內存中,而後才寫入 Socket 中。相比於堆外直接內存,消息在發送過程當中多了一次緩衝區的內存拷貝。android

  2. Netty 提供了 CompositeByteBuf 類,它能夠將多個 ByteBuf 合併爲一個邏輯上的 ByteBuf,避免了傳統經過內存拷貝的方式將幾個小 Buffer 合併成一個大的 Buffer。數組

  3. 經過 FileRegion 包裝的 FileChannel.tranferTo 方法 實現文件傳輸,能夠直接將文件緩衝區的數據發送到目標 Channel,避免了傳統經過循環 write 方式致使的內存拷貝問題。app

  4. 經過 wrap 操做,咱們能夠將 byte[] 數組、ByteBuf、ByteBuffer 等包裝成一個 Netty ByteBuf 對象,進而避免了拷貝操做this

1、直接緩衝區的應用

Netty 中使用直接緩衝區來讀寫數據,首先看一下 read 方法中緩衝區的建立方法。.net

// AbstractNioByteChannel.NioByteUnsafe#read
public final void read() {
    final ChannelConfig config = config();
    final ByteBufAllocator allocator = config.getAllocator();
    final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
    ByteBuf byteBuf = allocHandle.allocate(allocator);
    // 省略...
}

Netty 中接收緩衝區 ByteBuffer 由 ChannelConfig 分配,而 ChannelConfig 建立 ByteBufAllocator 默認使用 Direct Buffer,這就避免了讀寫數據的二次內存拷貝問題,從而實現了讀寫 Socket 的零拷貝功能。debug

// ByteBufAllocator 用於分配緩衝區
private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;

// RecvByteBufAllocator 可伸縮的緩衝區,根據每次接收的數據大小自動分配緩衝區大小
private volatile RecvByteBufAllocator rcvBufAllocator;
public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

AdaptiveRecvByteBufAllocator 繼承自 DefaultMaxMessagesRecvByteBufAllocator,在 allocate 分配緩衝區。netty

// DefaultMaxMessagesRecvByteBufAllocator.MaxMessageHandle.allocate
public ByteBuf allocate(ByteBufAllocator alloc) {
    return alloc.ioBuffer(guess());
}

// AbstractByteBufAllocator
public ByteBuf ioBuffer(int initialCapacity) {
    if (PlatformDependent.hasUnsafe()) {
        return directBuffer(initialCapacity);
    }
    return heapBuffer(initialCapacity);
}

PlatformDependent.hasUnsafe() 經過判斷可否加載到 sun.misc.Unsafe 類就使用直接緩衝區,正常狀況下返回 truecode

private static final boolean HAS_UNSAFE = hasUnsafe0();
public static boolean hasUnsafe() {
    return HAS_UNSAFE;
}

// 默認若是能經過反射獲取 sun.misc.Unsafe 實例則使用直接緩衝區,由於直接緩衝區底層就是使用 Unsafe 類
private static boolean hasUnsafe0() {
    // 1. 加載 android.app.Application 類則返回 true
    if (isAndroid()) {
        return false;
    }
    
    // 2. -Dio.netty.noUnsafe=true
    if (PlatformDependent0.isExplicitNoUnsafe()) {
        return false;
    }

    // 3. 經過反射獲取 sun.misc.Unsafe 類。Unsafe.class.getDeclaredField("theUnsafe")
    try {
        boolean hasUnsafe = PlatformDependent0.hasUnsafe();
        logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable");
        return hasUnsafe;
    } catch (Throwable t) {
        logger.trace("Could not determine if Unsafe is available", t);
        return false;
    }
}

咱們再分析一直 ByteBufAllocator allocator = ByteBufAllocator.DEFAULT 中的默認 ByteBufAllocator。

ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

// ByteBufUtil,主要判斷是否將內存池化,由於直接緩衝區的分配和銷燬開銷比較大
static final ByteBufAllocator DEFAULT_ALLOCATOR;
static {
    String allocType = SystemPropertyUtil.get(
            "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
    allocType = allocType.toLowerCase(Locale.US).trim();

    ByteBufAllocator alloc;
    if ("unpooled".equals(allocType)) {
        alloc = UnpooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else if ("pooled".equals(allocType)) {
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else {
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
    }

    DEFAULT_ALLOCATOR = alloc;
}

// 除了 HAS_UNSAFE 外還須要判斷 io.netty.noPreferDirect 屬性
public static final PooledByteBufAllocator DEFAULT =
            new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());            
public static final UnpooledByteBufAllocator DEFAULT =
        new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred());
public static boolean directBufferPreferred() {
    return DIRECT_BUFFER_PREFERRED;
}
private static final boolean DIRECT_BUFFER_PREFERRED =
        HAS_UNSAFE && !SystemPropertyUtil.getBoolean("io.netty.noPreferDirect", false);

2、CompositeByteBuf

//定義兩個ByteBuf類型的 body 和 header 
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);

addComponents 方法將 header 與 body 合併爲一個邏輯上的 ByteBuf, 這兩個 ByteBuf 在 CompositeByteBuf 內部都是單獨存在的, CompositeByteBuf 只是邏輯上是一個總體。

3、經過 FileRegion 實現零拷貝

利用 nio 提供的 transferTo 實現零拷貝。

srcFileChannel.transferTo(position, count, destFileChannel);

4、經過 wrap / slice 實現零拷貝

// wrappedBuffer 和 slice 都是共享同一內存,並無拷貝
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

ByteBuf header = byteBuf.slice(0, 5);
ByteBuf body = byteBuf.slice(5, 10);

天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索