Netty:核心功能Buffer

我們知道byte(字節)一直都是網絡數據的基本單位。然而Java NIO提供的字節容器ByteBuffer的作用受到限制,也沒有經過優化,所以使用ByteBuffer會讓事情變得繁瑣和複雜。
幸運的是,在Netty中提供了一個強大的緩衝實現類用來表示字節序列以及幫助你操作字節和自定義的POJO。這個新的緩衝類,ByteBuf,它的效率與JDK的ByteBuffer相當。設計ByteBuf是爲了在Netty的pipeline中傳輸數據。它是爲了解決ByteBuffer存在的一些問題以及滿足網絡程序開發者的需求,以提高他們的生產效率而被設計出來的。

Buffer API

  1. ByteBuf:持有需要緩存的數據
  2. ByteBufHolder:持有ByteBuf實例
  3. ByteBufAllocator: 管理ByteBuf 實例
  4. ByteBufUtil、Unpooled:工具類

原理:Netty 根據 reference-counting(引用計數)來確定何時可以釋放 ByteBuf 或 ByteBufHolder 和其他相關資源,利用池和其他技巧來提高性能和降低內存的消耗。注意:使用 ByteBuf 和 ByteBufHolder 時,你應該儘可能早地釋放池資源。

ByteBuf

1.基本原理

原理:ByteBuf 是一個已經經過優化的很好使用的數據容器,字節數據可以有效的被添加到 ByteBuf 中或者也可以從 ByteBuf 中直接獲取數據。ByteBuf中有兩個索引:一個用來讀,一個用來寫。這兩個索引達到了便於操作的目的。我們可以按順序的讀取數據,也可以通過調整讀取數據的索引或者直接將讀取位置索引作爲參數傳遞給get方法來重複讀取數據。

ByteBuf 類似於一個字節數組,最大的區別是讀和寫的索引可以用來控制對緩衝區數據的訪問。下圖顯示了一個容量爲16的空的 ByteBuf 的佈局和狀態,writerIndex 和 readerIndex 都在索引位置 0 :
在這裏插入圖片描述
值得注意的是,調用 ByteBuf 的以 “read” 或 “write” 開頭的任何方法都將自動增加相應的索引。另一方面,“set” 、 "get"操作字節將不會移動索引位置,它們只會在指定的相對位置上操作字節。ByteBuf 的默認最大容量限制是 Integer.MAX_VALUE。

2.ByteBuf中的數據存在何處?

第一類:HEAP BUFFER(堆緩衝區)
第二類:DIRECT BUFFER(直接緩衝區)
第三類:COMPOSITE BUFFER(複合緩衝區)

第一類:HEAP BUFFER(堆緩衝區)

最常用的是ByteBuf 將數據存儲在 JVM 的堆空間,這是通過將數據存儲在數組的實現。堆緩衝區可以快速分配,當不使用時也可以快速釋放。它還提供了直接訪問數組的方法,通過 ByteBuf.array() 來獲取 byte[]數據。

ByteBuf heapBuf = ...;
if (heapBuf.hasArray()) {      //1檢查 ByteBuf 是否有支持數組。
    byte[] array = heapBuf.array();  //2 如果有的話,得到引用數組。
    int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();                //3 計算第一字節的偏移量。
    int length = heapBuf.readableBytes();//4獲取可讀的字節數。
    handleArray(array, offset, length); //5使用數組,偏移量和長度作爲調用方法的參數。
}

第二類:DIRECT BUFFER(直接緩衝區)

基於JDK1.4,NIO 的ByteBuffer 類允許 JVM 通過本地方法調用分配內存,直接緩存區允許在JVM Heap外的內存中緩存數據。
優勢:

  1. 通過免去中間交換的內存拷貝, 提升IO處理速度; 直接緩衝區的內容可以駐留在垃圾回收掃描的堆區以外。
  2. DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的內存, GC對此”無能爲力”,也就意味着規避了在高負載下頻繁的GC過程對應用線程的中斷影響.(詳見http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html.)

缺點:
直接緩衝區的缺點是在內存空間的分配和釋放上比堆緩衝區更復雜,另外一個缺點是如果要將數據傳遞給遺留代碼處理,因爲數據不是在堆上,你可能不得不作出一個副本。

ByteBuf directBuf = ...
if (!directBuf.hasArray()) {  //1檢查 ByteBuf 是不是由數組支持。如果不是,這是一個直接緩衝區。
    int length = directBuf.readableBytes();//2 獲取可讀的字節數
    byte[] array = new byte[length];    //3分配一個新的數組來保存字節
    directBuf.getBytes(directBuf.readerIndex(), array);        //4字節複製到數組    
    handleArray(array, 0, length);  //5將數組,偏移量和長度作爲參數調用某些處理方法
}

第三類:COMPOSITE BUFFER(複合緩衝區)

定義:最後一種模式是複合緩衝區,我們可以創建多個不同的 ByteBuf,然後提供一個這些 ByteBuf 組合的視圖。複合緩衝區就像一個列表,我們可以動態的添加和刪除其中的 ByteBuf,JDK 的 ByteBuffer 沒有這樣的功能。
Netty 提供了 ByteBuf 的子類 CompositeByteBuf 類來處理複合緩衝區,CompositeByteBuf 只是一個視圖。其中,CompositeByteBuf.hasArray() 總是返回 false,因爲它可能既包含堆緩衝區,也包含直接緩衝區。
用例
例如,一條消息由 header 和 body 兩部分組成,將 header 和 body 組裝成一條消息發送出去,可能 body 相同,只是 header 不同,使用CompositeByteBuf 就不用每次都重新分配一個新的緩衝區。

CompositeByteBuf messageBuf = ...;
ByteBuf headerBuf = ...; // 可以是堆緩存或者是直接緩存
ByteBuf bodyBuf = ...; // 可以是堆緩存或者是直接緩存
messageBuf.addComponents(headerBuf, bodyBuf);//1 追加 ByteBuf 實例的 CompositeByteBuf
messageBuf.removeComponent(0); // 移除頭    //2刪除 索引1的 ByteBuf

for (int i = 0; i < messageBuf.numComponents(); i++) {                        //3遍歷所有 ByteBuf 實例。
    System.out.println(messageBuf.component(i).toString());
}

你可以簡單地把 CompositeByteBuf 當作一個可迭代遍歷的容器。 CompositeByteBuf 不允許訪問其內部可能存在的支持數組,也不允許直接訪問數據,這一點類似於直接緩衝區模式。

3.ByteBuf基本使用

參考:https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-vea728bc.html

ByteBufHolder

需求:我們時不時的會遇到這樣的情況:即需要另外存儲除有效的實際數據各種屬性值。HTTP響應就是一個很好的例子;與內容一起的字節的還有狀態碼,cookies等
方案:Netty 提供的 ByteBufHolder 可以對這種常見情況進行處理。 ByteBufHolder 還提供了對於 Netty 的高級功能,如緩衝池,其中保存實際數據的 ByteBuf 可以從池中借用,如果需要還可以自動釋放。

ByteBufAllocator

爲了減少分配和釋放內存的開銷,Netty 通過支持池類 ByteBufAllocator,用於分配 我們已經描述過的任何 ByteBuf類型的實例。下表是基本方法。
在這裏插入圖片描述

1.如何獲得ByteBufAllocator?

得到一個 ByteBufAllocator 的引用很簡單。你可以得到從 Channel (在理論上,每 Channel 可具有不同的 ByteBufAllocator ),或通過綁定到 ChannelHandler 的 ChannelHandlerContext 得到它,用它實現了你數據處理邏輯。

Channel channel = ...;
ByteBufAllocator allocator = channel.alloc(); //1從 channel 獲得 ByteBufAllocator
....
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc(); //2從 ChannelHandlerContext 獲得 ByteBufAllocator
...

Netty 提供了兩種 ByteBufAllocator 的實現,一種是 PooledByteBufAllocator,用ByteBuf 實例池改進性能以及內存使用降到最低,此實現使用一個“jemalloc”內存分配。其他的實現不池化 ByteBuf 情況下,每次返回一個新的實例。
Netty 默認使用 PooledByteBufAllocator,我們可以通過 ChannelConfig 或通過引導設置一個不同的實現來改變。

Unpooled (非池化)緩存
當未引用 ByteBufAllocator 時,無法訪問到 ByteBuf。對於這個用例 Netty 提供一個實用工具類稱爲 Unpooled,,它提供了靜態輔助方法來創建**非池化的 ByteBuf 實例。**例如Unpooled的部分源碼。

private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT;
public static ByteBuf buffer() {
        return ALLOC.heapBuffer();
    }

Netty中的引用計數器

在Netty 4中爲 ByteBuf 和 ByteBufHolder(兩者都實現了 ReferenceCounted 接口)引入了引用計數器。
引用計數器本身並不複雜;它能夠在特定的對象上跟蹤引用的數目,實現了ReferenceCounted 的類的實例會通常開始於一個活動的引用計數器爲 1。而如果對象活動的引用計數器大於0,就會被保證不被釋放。當數量引用減少到0,將釋放該實例。需要注意的是“釋放”的語義是特定於具體的實現。最起碼,一個對象,它已被釋放應不再可用。
這種技術就是諸如 PooledByteBufAllocator 這種減少內存分配開銷的池化的精髓部分。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章