Netty入門(一):ByteBuf

新人十三發表於2021-08-05

       網路資料的基本單位總是位元組。Java NIO 提供了 ByteBuffer 作為它的位元組容器,但是這個類使用起來過於複雜,而且也有些繁瑣。Netty 的 ByteBuffer 替代品是 ByteBuf,一個強大的實現,既解決了 JDK API 的侷限性,又為網路應用程式的開發者提供了更好的 API

ByteBuf優勢

  • 它可以被使用者自定義的緩衝區型別擴充套件
  • 通過內建的複合緩衝區型別實現了透明的零拷貝
  • 容量可以按需增長
  • 在讀和寫這兩種模式之間切換不需要呼叫 ByteBuffer 的 flip()方法
  • 讀和寫使用了不同的索引
  • 支援方法的鏈式呼叫
  • 支援引用計數
  • 支援池化

ByteBuf實現原理

Netty入門(一):ByteBuf

如圖ByteBuf通維護了兩個不同的索引:一個用於讀取,一個用於寫入。

當你從 ByteBuf 讀取時,它的 readerIndex 將會被遞增已經被讀取的位元組數。同樣地,當你寫入 ByteBuf 時,它的writerIndex 也會被遞增

Netty入門(一):ByteBuf

當呼叫readBytes時,readIndex會相應移動length位,如果readIndex移動後大於writeIndex則會拋異常。

Netty入門(一):ByteBuf

當呼叫writeBytes時,writeIndex會相應移動length位,且通過ensureWritable方法實現自動擴容

其他常用API

getBytes 獲取可讀位元組陣列
setBytes 寫入位元組
discardReadBytes 廢棄已讀位元組
mark 標記index
reset 將index重置到之前標記的位置(配合mark使用)
isReadable 如果至少有一個位元組可供讀取,則返回 true
isWritable 如果至少有一個位元組可被寫入,則返回 true
readableBytes 返回可被讀取的位元組數
writableBytes 返回可被寫入的位元組數
capacity 返回 ByteBuf 可容納的位元組數。在此之後,它會嘗試再次擴充套件直到達到 maxCapacity()
maxCapacity 返回 ByteBuf 可以容納的最大位元組數
hasArray 如果 ByteBuf 由一個位元組陣列支撐,則返回 true
array 如果 ByteBuf 由一個位元組陣列支撐則返回該陣列;否則,它將丟擲一個UnsupportedOperationException 異常

ByteBuf緩衝分類

1、Heap buffer(堆緩衝區):

就是將資料存在JVM堆空間中,在沒有被池化的情況可以快速分配和釋放。

優點:由於資料是儲存在JVM堆中,因此可以快速的建立與快速的釋放,並且它提供了直接訪問內部位元組陣列的方法。

缺點:每次讀寫資料時,都需要先將資料複製到直接緩衝區中再進行網路傳輸。

2、Direct buffer(直接緩衝區):

直接緩衝區,在堆外直接分配記憶體空間,直接緩衝區並不會佔用堆的容量空間,因為它是由作業系統在本地記憶體進行的資料分配。

優點:在使用Socket進行資料傳遞時,效能非常好,因為資料直接位於作業系統的本地記憶體中,所以不需要從JVM將資料複製到直接緩衝區中 。

缺點:因為Direct Buffer是直接在作業系統記憶體中的,所以記憶體空間的分配與釋放要比堆空間更加複雜,而且速度要慢一些。

注意:

如果你的資料包含在一個在堆上的分配的緩衝區中,那麼事實上,在通過套接字傳送他之前,jvm將會在內部把你的緩衝區複製到一個直接緩衝區中;這樣分配釋放就比較浪費資源;

建議:

直接緩衝區並不支援通過位元組陣列的方式來訪問資料。對於後端業務的訊息編解碼來說,推薦使用HeapByteBuf;對於I/O通訊執行緒在讀寫緩衝區時,推薦使用DirectByteBuf;

3、Composite Buffer 複合緩衝區:

可以擁有以上兩種的緩衝區,通過一種聚合檢視來操作底層持有的多種型別Buffer。這種緩衝,jdk nio是沒有這種特性的。

ByteBuf主要實現類

pooled:池化,重用ByteBuf物件

Direct:直接記憶體,內部通過ByteBuffer實現,典型裝飾模式

Heap:堆記憶體,內部持有byte陣列

(1)UnpooledDirectByteBuf:

在堆外進行記憶體分配的非記憶體池ByteBuf,內部持有ByteBuffer物件,相關操作委託給ByteBuffer實現。

(2)UnpooledHeapByteBuf:

基於堆記憶體分配非記憶體池ByteBuf,即內部持有byte陣列。

(3)UnpooledUnsafeDirectByteBuf:

和另外一個類UnpooledDirectByteBuf差不多相同,區別在於UnpooledUnsafeDirectByteBuf內部使用基於PlatformDependent相關操作實現ByteBuf,依賴平臺。

(4)ReadOnlyByteBufferBuf:

只讀ByteBuf,內部持有ByteBuffer物件,相關操作委託給ByteBuffer實現,該ByteBuf限內部使用;

(5)FixedCompositeByteBuf:

用於將多個ByteBuf組合在一起,形成一個虛擬的只讀ByteBuf物件,不允許寫入和動態擴充套件。內部使用Object[]將多個ByteBuf組合在一起,一旦FixedCompositeByteBuf物件構建完成,則不會被更改。

(6)CompositeByteBuf:

用於將多個ByteBuf組合在一起,形成一個虛擬的ByteBuf物件,支援讀寫和動態擴充套件。內部使用List組合多個ByteBuf。一般使用使用ByteBufAllocator的compositeBuffer()方法,Unpooled的工廠方法compositeBuffer()或wrappedBuffer(ByteBuf... buffers)建立CompositeByteBuf物件。

(7)PooledByteBuf:

基於記憶體池的ByteBuf,主要為了重用ByteBuf物件,提升記憶體的使用效率;適用於高負載,高併發的應用中。主要有PooledDirectByteBuf,PooledHeapByteBuf,PooledUnsafeDirectByteBuf三個子類,PooledDirectByteBuf是在堆外進行記憶體分配的記憶體池ByteBuf,PooledHeapByteBuf是基於堆記憶體分配記憶體池ByteBuf,PooledUnsafeDirectByteBuf也是在堆外進行記憶體分配的記憶體池ByteBuf,區別在於PooledUnsafeDirectByteBuf內部使用基於PlatformDependent相關操作實現ByteBuf,具有平臺相關性。

ByteBufHolder

利用組合的方式對ByteBuf進行擴充套件。實際應用中我們經常發現,除了實際的資料負載之外,我們還需要儲存各種屬性值。HTTP 響應便是一個很好的例子,除了表示為位元組的內容,還包括狀態碼、cookie 等。為了處理這種常見的用例,Netty 提供了 ByteBufHolder。ByteBufHolder 也為 Netty 的高階特性提供了支援,如緩衝區池化,其中可以從池中借用 ByteBuf,並且在需要時自動釋放。

ByteBufAllocator

為了降低分配和釋放記憶體的開銷,Netty 通過 ByteBufAllocator 實現了(ByteBuf 的)池化,它可以用來分配我們所描述過的任意型別的 ByteBuf 例項。Netty提供了兩種ByteBufAllocator的實現:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的例項以提高效能並最大限度地減少記憶體碎片。

Netty入門(一):ByteBuf

ReferenceCounted

類似GC引用計數法,Netty中ByteBuf和ByteBufHolder都實現了該介面,其主要通過方法retain(release)來增加(減少)資源被引用的次數,當引用為0時,代表該資源可以被釋放。

相關文章