Netty原理與基礎(三)

1.Pipeline流水線

Netty的業務處理器流水線ChannelPipeline是基於責任鏈設計模式(Chain of Responsibility)來設計的,內部是一個雙向鏈表結構,能夠支持動態地添加和刪除Handler業務處理器。

  • 入站操作


  • 出站操作
    出站流水從後往前


1.1ChanneHandlerContext上下文

  • 不論定義那種類型的Handler業務處理器,最終形式都是以雙向鏈表方式保存的
  • 在Handler業務處理器被添加到流水線中時,會創建一個通道處理器上下文ChannelHandlerContext,它代表了ChannelHandler通道處理器和ChannelPipeline通道流水線之間的關聯。
  • 主要分類
    1.獲取上下文所關聯的Netty組件實例,如所關聯的通道、所關聯的流水線、上下文內部Handler業務處理器實例等;
  1. 是入站和出站處理方法
  • Channel、Handler、ChannelHandlerContext三者的關係爲:
    Channel通道擁有一條ChannelPipeline通道流水線,每一個流水線節點爲一個ChannelHandlerContext通道處理器上下文對象,每一個上下文中包裹了一個ChannelHandler通道處理器。在ChannelHandler通道處理器的入站/出站處理方法中,Netty都會傳遞一個Context上下文實例作爲實際參數。通過Context實例的實參,在業務處理中,可以獲取ChannelPipeline通道流水線的實例或者Channel通道的實例。

1.2階段流水線的處理方式

  • 1.入站在channelRead方法中,不去調用父類的channelRead入站方法
    -2.出站處理流程中,只要開始就不能被階段

2.ByteBuf緩衝區

  • ByteBuf的優勢:
    • Pooling (池化,這點減少了內存複製和GC,提升了效率)
    • 複合緩衝區類型,支持零複製
    • 不需要調用flip()方法去切換讀/寫模式
    • 擴展性好,例如StringBuffer
    • 可以自定義緩衝區類型
    • 讀取和寫入索引分開
    • 方法的鏈式調用
    • 可以進行引用計數,方便重複使用
  • ByteBuf是一個字節容器,內部是一個字節數組


2.1ByteBuf的重要屬性

  • readerIndex(讀指針):指示讀取的起始位置。每讀取一個字節,readerIndex自動增加1。一旦readerIndex與writerIndex相等,則表示ByteBuf不可讀了。
  • writerIndex(寫指針):指示寫入的起始位置。每寫一個字節,writerIndex自動增加1。一旦增加到writerIndex與capacity()容量相等,則表示ByteBuf已經不可寫了。capacity()是一個成員方法,不是一個成員屬性,它表示ByteBuf中可以寫入的容量。注意,它不是最大容量maxCapacity。
  • maxCapacity(最大容量):表示ByteBuf可以擴容的最大容量。當向ByteBuf寫數據的時候,如果容量不足,可以進行擴容。擴容的最大限度由maxCapacity的值來設定,超過maxCapacity就會報錯。

2.2ByteBuf的三組方法

  • 容量系列:capacity()、maxCapacity()
  • 寫入系列: isWritable()、writableBytes() 等
  • 讀取系列: isReadable( )、 readableBytes( )等

2.3代碼示例

public class WriteReadTest {

    @Test
    public void testWriteRead() {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        print("動作:分配 ByteBuf(9, 100)", buffer);
        buffer.writeBytes(new byte[]{1, 2, 3, 4});
        print("動作:寫入4個字節 (1,2,3,4)", buffer);
        Logger.info("start==========:get==========");
        getByteBuf(buffer);
        print("動作:取數據 ByteBuf", buffer);
        Logger.info("start==========:read==========");
        readByteBuf(buffer);
        print("動作:讀完 ByteBuf", buffer);
    }

    //讀取一個字節
    private void readByteBuf(ByteBuf buffer) {
        while (buffer.isReadable()) {
            Logger.info("讀取一個字節:" + buffer.readByte());
        }
    }


    //讀取一個字節,不改變指針
    private void getByteBuf(ByteBuf buffer) {
        for (int i = 0; i < buffer.readableBytes(); i++) {
            Logger.info("讀取一個字節:" + buffer.getByte(i));
        }
    }

}

日誌打印:

[main|PrintAttribute.print] |>  after ===========動作:分配 ByteBuf(9, 100)============ 
[main|PrintAttribute.print] |>  1.0 isReadable(): false 
[main|PrintAttribute.print] |>  1.1 readerIndex(): 0 
[main|PrintAttribute.print] |>  1.2 readableBytes(): 0 
[main|PrintAttribute.print] |>  2.0 isWritable(): true 
[main|PrintAttribute.print] |>  2.1 writerIndex(): 0 
[main|PrintAttribute.print] |>  2.2 writableBytes(): 256 
[main|PrintAttribute.print] |>  3.0 capacity(): 256 
[main|PrintAttribute.print] |>  3.1 maxCapacity(): 2147483647 
[main|PrintAttribute.print] |>  3.2 maxWritableBytes(): 2147483647 
[main|PrintAttribute.print] |>  after ===========動作:寫入4個字節 (1,2,3,4)============ 
[main|PrintAttribute.print] |>  1.0 isReadable(): true 
[main|PrintAttribute.print] |>  1.1 readerIndex(): 0 
[main|PrintAttribute.print] |>  1.2 readableBytes(): 4 
[main|PrintAttribute.print] |>  2.0 isWritable(): true 
[main|PrintAttribute.print] |>  2.1 writerIndex(): 4 
[main|PrintAttribute.print] |>  2.2 writableBytes(): 252 
[main|PrintAttribute.print] |>  3.0 capacity(): 256 
[main|PrintAttribute.print] |>  3.1 maxCapacity(): 2147483647 
[main|PrintAttribute.print] |>  3.2 maxWritableBytes(): 2147483643 
[main|WriteReadTest.testWriteRead] |>  start==========:get========== 
[main|WriteReadTest.getByteBuf] |>  讀取一個字節:1 
[main|WriteReadTest.getByteBuf] |>  讀取一個字節:2 
[main|WriteReadTest.getByteBuf] |>  讀取一個字節:3 
[main|WriteReadTest.getByteBuf] |>  讀取一個字節:4 
[main|PrintAttribute.print] |>  after ===========動作:取數據 ByteBuf============ 
[main|PrintAttribute.print] |>  1.0 isReadable(): true 
[main|PrintAttribute.print] |>  1.1 readerIndex(): 0 
[main|PrintAttribute.print] |>  1.2 readableBytes(): 4 
[main|PrintAttribute.print] |>  2.0 isWritable(): true 
[main|PrintAttribute.print] |>  2.1 writerIndex(): 4 
[main|PrintAttribute.print] |>  2.2 writableBytes(): 252 
[main|PrintAttribute.print] |>  3.0 capacity(): 256 
[main|PrintAttribute.print] |>  3.1 maxCapacity(): 2147483647 
[main|PrintAttribute.print] |>  3.2 maxWritableBytes(): 2147483643 
[main|WriteReadTest.testWriteRead] |>  start==========:read========== 
[main|WriteReadTest.readByteBuf] |>  讀取一個字節:1 
[main|WriteReadTest.readByteBuf] |>  讀取一個字節:2 
[main|WriteReadTest.readByteBuf] |>  讀取一個字節:3 
[main|WriteReadTest.readByteBuf] |>  讀取一個字節:4 
[main|PrintAttribute.print] |>  after ===========動作:讀完 ByteBuf============ 
[main|PrintAttribute.print] |>  1.0 isReadable(): false 
[main|PrintAttribute.print] |>  1.1 readerIndex(): 4 
[main|PrintAttribute.print] |>  1.2 readableBytes(): 0 
[main|PrintAttribute.print] |>  2.0 isWritable(): true 
[main|PrintAttribute.print] |>  2.1 writerIndex(): 4 
[main|PrintAttribute.print] |>  2.2 writableBytes(): 252 
[main|PrintAttribute.print] |>  3.0 capacity(): 256 
[main|PrintAttribute.print] |>  3.1 maxCapacity(): 2147483647 
[main|PrintAttribute.print] |>  3.2 maxWritableBytes(): 2147483643 

使用get取數據不會影想ByteBuf的指針

2.4ByteBuf的引用計數

  • Netty的ByteBuf的內存回收工作是通過引用計數的方式管理的
  • Netty採用“計數器”來追蹤ByteBuf的生命週期,一是對PooledByteBuf的支持,二是能夠儘快地“發現”那些可以回收的ByteBuf(非Pooled),以便提升ByteBuf的分配和銷燬的效率
  • 什麼是Pooled(池化)的ByteBuf緩衝區呢?

在通信程序的執行過程中,Buffer緩衝區實例會被頻繁創建、使用、釋放。大家都知道,頻繁創建對象、內存分配、釋放內存,系統的開銷大、性能低,如何提升性能、提高Buffer實例的使用率呢?從Netty4版本開始,新增了對象池化的機制。即創建一個Buffer對象池,將沒有被引用的Buffer對象,放入對象緩存池中;當需要時,則重新從對象緩存池中取出,而不需要重新創建

  • 引用計數的大致規則如下:

在默認情況下,當創建完一個ByteBuf時,它的引用爲1;每次調用retain()方法,它的引用就加1;每次調用release()方法,就是將引用計數減1;如果引用爲0,再次訪問這個ByteBuf對象,將會拋出異常;如果引用爲0,表示這個ByteBuf沒有哪個進程引用它,它佔用的內存需要回收。

  • 在Netty中,引用計數爲0的緩衝區不能再繼續使用


  • 爲了確保引用計數不會混亂,在Netty的業務處理器開發過程中,應該堅持一個原則:retain和release方法應該結對使用。
  • 當引用計數已經爲0, Netty會進行ByteBuf的回收。分爲兩種情況:

(1)Pooled池化的ByteBuf內存,回收方法是:放入可以重新分配的ByteBuf池子,等待下一次分配。(2)Unpooled未池化的ByteBuf緩衝區,回收分爲兩種情況:如果是堆(Heap)結構緩衝,會被JVM的垃圾回收機制回收;如果是Direct類型,調用本地方法釋放外部內存(unsafe.freeMemory)

2.5 ByteBuf的Allocator分配器

Netty通過ByteBufAllocator分配器來創建緩衝區和分配內存空間。Netty提供了ByteBufAllocator的兩種實現:PoolByteBufAllocator和UnpooledByteBufAllocator。

  • PoolByteBufAllocator(池化ByteBuf分配器)將ByteBuf實例放入池中,提高了性能,將內存碎片減少到最小;這個池化分配器採用了jemalloc高效內存分配的策略,該策略被好幾種現代操作系統所採用。
  • UnpooledByteBufAllocator是普通的未池化ByteBuf分配器,它沒有把ByteBuf放入池中,每次被調用時,返回一個新的ByteBuf實例;通過Java的垃圾回收機制回收。
  • 內存管理的策略可以靈活調整,這是使用Netty所帶來的又一個好處。只需一行簡單的配置,就能獲得到池化緩衝區帶來的好處
public class AllocatorTest {
    @Test
    public void showAlloc() {
        ByteBuf buffer = null;
        //方法一:默認分配器,分配初始容量爲9,最大容量100的緩衝
        buffer = ByteBufAllocator.DEFAULT.buffer(9, 100);
        //方法二:默認分配器,分配初始爲256,最大容量Integer.MAX_VALUE 的緩衝
        buffer = ByteBufAllocator.DEFAULT.buffer();
        //方法三:非池化分配器,分配基於Java的堆內存緩衝區
        buffer = UnpooledByteBufAllocator.DEFAULT.heapBuffer();
        //方法四:池化分配器,分配基於操作系統的管理的直接內存緩衝區
        buffer = PooledByteBufAllocator.DEFAULT.directBuffer();
        //…..其他方法

    }
}
  • 根據內存的管理方不同,分爲堆緩存區和直接緩存區,也就是Heap ByteBuf和Direct ByteBuf。


2.6 ByteBuf的自動釋放

  • TailHandler自動釋放
    如果每個InboundHandler入站處理器,把最初的ByteBuf數據包一路往下傳,那麼TailHandler末尾處理器會自動釋放掉入站的ByteBuf實例

(1)手動釋放ByteBuf。具體的方式爲調用byteBuf.release()。
(2)調用父類的入站方法將msg向後傳遞,依賴後面的處理器釋放ByteBuf。具體的方式爲調用基類的入站處理方法super.channelRead(ctx,msg)。

  • SimpleChannelInboundHandler自動釋放

· 手動釋放ByteBuf實例。
· 繼承SimpleChannelInboundHandler,利用它的自動釋放功能。

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