国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

對于 Netty ByteBuf 的零拷貝(Zero Copy) 的理解

ConardLi / 3168人閱讀

摘要:根據對的定義即所謂的就是在操作數據時不需要將數據從一個內存區域拷貝到另一個內存區域因為少了一次內存的拷貝因此的效率就得到的提升在層面上的通常指避免在用戶態與內核態之間來回拷貝數據例如提供的系統調用它可以將一段用戶空間內存映射到內

根據 Wiki 對 Zero-copy 的定義:

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. This is frequently used to save CPU cycles and memory bandwidth when transmitting a file over a network.

即所謂的 Zero-copy, 就是在操作數據時, 不需要將數據 buffer 從一個內存區域拷貝到另一個內存區域. 因為少了一次內存的拷貝, 因此 CPU 的效率就得到的提升.

在 OS 層面上的 Zero-copy 通常指避免在 用戶態(User-space)內核態(Kernel-space) 之間來回拷貝數據. 例如 Linux 提供的 mmap 系統調用, 它可以將一段用戶空間內存映射到內核空間, 當映射成功后, 用戶對這段內存區域的修改可以直接反映到內核空間; 同樣地, 內核空間對這段區域的修改也直接反映用戶空間. 正因為有這樣的映射關系, 我們就不需要在 用戶態(User-space)內核態(Kernel-space) 之間拷貝數據, 提高了數據傳輸的效率.

而需要注意的是, Netty 中的 Zero-copy 與上面我們所提到到 OS 層面上的 Zero-copy 不太一樣, Netty的 Zero-coyp 完全是在用戶態(Java 層面)的, 它的 Zero-copy 的更多的是偏向于 優化數據操作 這樣的概念.

Netty 的 Zero-copy 體現在如下幾個個方面:

Netty 提供了 CompositeByteBuf 類, 它可以將多個 ByteBuf 合并為一個邏輯上的 ByteBuf, 避免了各個 ByteBuf 之間的拷貝.

通過 wrap 操作, 我們可以將 byte[] 數組、ByteBuf、ByteBuffer等包裝成一個 Netty ByteBuf 對象, 進而避免了拷貝操作.

ByteBuf 支持 slice 操作, 因此可以將 ByteBuf 分解為多個共享同一個存儲區域的 ByteBuf, 避免了內存的拷貝.

通過 FileRegion 包裝的FileChannel.tranferTo 實現文件傳輸, 可以直接將文件緩沖區的數據發送到目標 Channel, 避免了傳統通過循環 write 方式導致的內存拷貝問題.

下面我們就來簡單了解一下這幾種常見的零拷貝操作.

通過 CompositeByteBuf 實現零拷貝

假設我們有一份協議數據, 它由頭部和消息體組成, 而頭部和消息體是分別存放在兩個 ByteBuf 中的, 即:

ByteBuf header = ...
ByteBuf body = ...

我們在代碼處理中, 通常希望將 header 和 body 合并為一個 ByteBuf, 方便處理, 那么通常的做法是:

ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
allBuf.writeBytes(header);
allBuf.writeBytes(body);

可以看到, 我們將 header 和 body 都拷貝到了新的 allBuf 中了, 這無形中增加了兩次額外的數據拷貝操作了.

那么有沒有更加高效優雅的方式實現相同的目的呢? 我們來看一下 CompositeByteBuf 是如何實現這樣的需求的吧.

ByteBuf header = ...
ByteBuf body = ...

CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);

上面代碼中, 我們定義了一個 CompositeByteBuf 對象, 然后調用

public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) {
...
}

方法將 headerbody 合并為一個邏輯上的 ByteBuf, 即:

不過需要注意的是, 雖然看起來 CompositeByteBuf 是由兩個 ByteBuf 組合而成的, 不過在 CompositeByteBuf 內部, 這兩個 ByteBuf 都是多帶帶存在的, CompositeByteBuf 只是邏輯上是一個整體.

上面 CompositeByteBuf 代碼還以一個地方值得注意的是, 我們調用 addComponents(boolean increaseWriterIndex, ByteBuf... buffers) 來添加兩個 ByteBuf, 其中第一個參數是 true, 表示當添加新的 ByteBuf 時, 自動遞增 CompositeByteBuf 的 writeIndex.
如果我們調用的是

compositeByteBuf.addComponents(header, body);

那么其實 compositeByteBufwriteIndex 仍然是0, 因此此時我們就不可能從 compositeByteBuf 中讀取到數據, 這一點希望大家要特別注意.

除了上面直接使用 CompositeByteBuf 類外, 我們還可以使用 Unpooled.wrappedBuffer 方法, 它底層封裝了 CompositeByteBuf 操作, 因此使用起來更加方便:

ByteBuf header = ...
ByteBuf body = ...

ByteBuf allByteBuf = Unpooled.wrappedBuffer(header, body);
通過 wrap 操作實現零拷貝

例如我們有一個 byte 數組, 我們希望將它轉換為一個 ByteBuf 對象, 以便于后續的操作, 那么傳統的做法是將此 byte 數組拷貝到 ByteBuf 中, 即:

byte[] bytes = ...
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(bytes);

顯然這樣的方式也是有一個額外的拷貝操作的, 我們可以使用 Unpooled 的相關方法, 包裝這個 byte 數組, 生成一個新的 ByteBuf 實例, 而不需要進行拷貝操作. 上面的代碼可以改為:

byte[] bytes = ...
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);

可以看到, 我們通過 Unpooled.wrappedBuffer 方法來將 bytes 包裝成為一個 UnpooledHeapByteBuf 對象, 而在包裝的過程中, 是不會有拷貝操作的. 即最后我們生成的生成的 ByteBuf 對象是和 bytes 數組共用了同一個存儲空間, 對 bytes 的修改也會反映到 ByteBuf 對象中.

Unpooled 工具類還提供了很多重載的 wrappedBuffer 方法:

public static ByteBuf wrappedBuffer(byte[] array)
public static ByteBuf wrappedBuffer(byte[] array, int offset, int length)

public static ByteBuf wrappedBuffer(ByteBuffer buffer)
public static ByteBuf wrappedBuffer(ByteBuf buffer)

public static ByteBuf wrappedBuffer(byte[]... arrays)
public static ByteBuf wrappedBuffer(ByteBuf... buffers)
public static ByteBuf wrappedBuffer(ByteBuffer... buffers)

public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays)
public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers)
public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers)

這些方法可以將一個或多個 buffer 包裝為一個 ByteBuf 對象, 從而避免了拷貝操作.

通過 slice 操作實現零拷貝

slice 操作和 wrap 操作剛好相反, Unpooled.wrappedBuffer 可以將多個 ByteBuf 合并為一個, 而 slice 操作可以將一個 ByteBuf 切片 為多個共享一個存儲區域的 ByteBuf 對象.
ByteBuf 提供了兩個 slice 操作方法:

public ByteBuf slice();
public ByteBuf slice(int index, int length);

不帶參數的 slice 方法等同于 buf.slice(buf.readerIndex(), buf.readableBytes()) 調用, 即返回 buf 中可讀部分的切片. 而 slice(int index, int length) 方法相對就比較靈活了, 我們可以設置不同的參數來獲取到 buf 的不同區域的切片.

下面的例子展示了 ByteBuf.slice 方法的簡單用法:

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

slice 方法產生 header 和 body 的過程是沒有拷貝操作的, header 和 body 對象在內部其實是共享了 byteBuf 存儲空間的不同部分而已. 即:

通過 FileRegion 實現零拷貝

Netty 中使用 FileRegion 實現文件傳輸的零拷貝, 不過在底層 FileRegion 是依賴于 Java NIO FileChannel.transfer 的零拷貝功能.

首先我們從最基礎的 Java IO 開始吧. 假設我們希望實現一個文件拷貝的功能, 那么使用傳統的方式, 我們有如下實現:

public static void copyFile(String srcFile, String destFile) throws Exception {
    byte[] temp = new byte[1024];
    FileInputStream in = new FileInputStream(srcFile);
    FileOutputStream out = new FileOutputStream(destFile);
    int length;
    while ((length = in.read(temp)) != -1) {
        out.write(temp, 0, length);
    }

    in.close();
    out.close();
}

上面是一個典型的讀寫二進制文件的代碼實現了. 不用我說, 大家肯定都知道, 上面的代碼中不斷中源文件中讀取定長數據到 temp 數組中, 然后再將 temp 中的內容寫入目的文件, 這樣的拷貝操作對于小文件倒是沒有太大的影響, 但是如果我們需要拷貝大文件時, 頻繁的內存拷貝操作就消耗大量的系統資源了.
下面我們來看一下使用 Java NIO 的 FileChannel 是如何實現零拷貝的:

public static void copyFileWithFileChannel(String srcFileName, String destFileName) throws Exception {
    RandomAccessFile srcFile = new RandomAccessFile(srcFileName, "r");
    FileChannel srcFileChannel = srcFile.getChannel();

    RandomAccessFile destFile = new RandomAccessFile(destFileName, "rw");
    FileChannel destFileChannel = destFile.getChannel();

    long position = 0;
    long count = srcFileChannel.size();

    srcFileChannel.transferTo(position, count, destFileChannel);
}

可以看到, 使用了 FileChannel 后, 我們就可以直接將源文件的內容直接拷貝(transferTo) 到目的文件中, 而不需要額外借助一個臨時 buffer, 避免了不必要的內存操作.

有了上面的一些理論知識, 我們來看一下在 Netty 中是怎么使用 FileRegion 來實現零拷貝傳輸一個文件的:

@Override
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() + "
");
        return;
    } finally {
        if (length < 0 && raf != null) {
            raf.close();
        }
    }

    ctx.write("OK: " + raf.length() + "
");
    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("
");
}

上面的代碼是 Netty 的一個例子, 其源碼在 netty/example/src/main/java/io/netty/example/file/FileServerHandler.java
可以看到, 第一步是通過 RandomAccessFile 打開一個文件, 然后 Netty 使用了 DefaultFileRegion 來封裝一個 FileChannel 即:

new DefaultFileRegion(raf.getChannel(), 0, length)

當有了 FileRegion 后, 我們就可以直接通過它將文件的內容直接寫入 Channel 中, 而不需要像傳統的做法: 拷貝文件內容到臨時 buffer, 然后再將 buffer 寫入 Channel. 通過這樣的零拷貝操作, 無疑對傳輸大文件很有幫助.

本文由 yongshun 發表于個人博客, 采用 署名-相同方式共享 3.0 中國大陸許可協議.
Email: yongshun1228@gmail .com
本文標題為: 對于 Netty ByteBuf 的零拷貝(Zero Copy) 的理解
本文鏈接為: https://segmentfault.com/a/1190000007560884

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69818.html

相關文章

  • NettyZero-copy 實現(上)

    摘要:維基百科中對的解釋是零拷貝技術是指計算機執行操作時,不需要先將數據從某處內存復制到另一個特定區域。維基百科里提到的零拷貝是在硬件和操作系統層面的,而本文主要介紹的是在應用層面的優化。 維基百科中對 Zero-copy 的解釋是 零拷貝技術是指計算機執行操作時,CPU不需要先將數據從某處內存復制到另一個特定區域。這種技術通常用于通過網絡傳輸文件時節省CPU周期和內存帶寬。 維基百科里提到...

    sf_wangchong 評論0 收藏0
  • NettyZero-copy 實現(下)

    摘要:系統調用返回,產生了第四次上下文切換。現在這個方法不僅減少了上下文切換,而且消除了參與的數據拷貝。 上一篇說到了 CompositeByteBuf ,這一篇接著上篇的講下去。 FileRegion 讓我們先看一個Netty官方的example // netty-netty-4.1.16.Finalexamplesrcmainjavaio ettyexamplefileFileServe...

    endiat 評論0 收藏0
  • 徹底理解Netty,這一篇文章就夠了

    摘要:如果什么事都沒得做,它也不會死循環,它會將線程休眠起來,直到下一個事件來了再繼續干活,這樣的一個線程稱之為線程。而請求處理邏輯既可以使用單獨的線程池進行處理,也可以跟放在讀寫線程一塊處理。 Netty到底是什么 從HTTP說起 有了Netty,你可以實現自己的HTTP服務器,FTP服務器,UDP服務器,RPC服務器,WebSocket服務器,Redis的Proxy服務器,MySQL的P...

    yy13818512006 評論0 收藏0
  • Netty入門學習-ByteBuf

    摘要:使用來優化套接字操作,盡可能消除由的緩沖區實現所導致的性能以及內存使用率的懲罰,這種優化發生在的核心代碼中,不會被暴露出來。當前將會被增加所寫入的字節數。 ByteBuf是Java NIO ByteBuffer的替代品,是網絡數據基本單位字節的容器。 ByteBuf的API Netty的數據處理API通過兩個組件暴漏:抽象類ByteBuf和接口ByteBufHolder ByteBuf...

    beanlam 評論0 收藏0
  • Netty ByteBuf

    摘要:提供了作為它的字節容器但是這個類使用起來過于復雜而且也有些繁瑣的的代替品是的的數據處理通過兩個組件暴露下面是的優點它可以被用戶自定義的緩沖區類擴展通過內置的復合緩沖區類型實現了透明的零拷貝容量可以按需增長在讀和寫這兩種模式之間雀環不需要調用 Java NIO 提供了 ByteBuffer 作為它的字節容器, 但是這個類使用起來過于復雜, 而且也有些繁瑣. Netty 的 ByteBuf...

    whataa 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<