Netty(一)之初識Netty

什麼是Netty?

  • 在學習Netty之前,首先要明白Netty是什麼?
  • 是什麼?不如去搞清楚它是用來幹嘛的
  • 幹嘛的?來了,Netty是用來提高通信性能的一個開源的基於java的通信框架
  • 想要提高分佈式系統下各個服務器之前的通信性能,Netty是一個不錯的選擇

初識Netty高性能

  • 在IO編程過程中,如果要處理多個請求的時候。可以使用多線程或者IO多路複用技術進行處理
    • java提供Selector選擇器可以把多個IO阻塞到同一個select上實現多路複用
  • 那麼Netty是怎麼做的或者Netty是如何實現高性能的

Netty基本通信架構

  • Netty架構按照Reactor模式來設計實現

  • Reactor模式可以認爲是一種設計模式——即反應器模式

  • 服務端通信序列

    • 圖片引用自咕泡學院Tom老師的課堂筆記
      在這裏插入圖片描述
  • 客戶端通信序列

    • 圖片引用自咕泡學院Tom老師的課堂筆記
      在這裏插入圖片描述
  • Java NIO通信實現與Netty相比較

    • Java通過選擇器Selector實現NIO通信
//  java 實現
private Selector selector;
while(true) {
    this.selector.select();
    // to do...
}
    • Netty中提供了NioEventLoop聚合了Java的Selector
    • 可同時創建多個channel併發處理多個客戶端請求
    • 實現了非阻塞 讀/寫

Netty—零拷貝

  • 通常在進行IO讀寫的時候,數據需要經過緩衝區才最終到達內存
  • Netty 提供 Direct Buffers 跳過緩衝區,直接使用堆外直接內存進行Socket讀寫
  • Netty 提供 組合Buffer 對象,可以聚合多個 Buffer 對象進行操作。優於通過內存拷貝合併Buffer
  • Netty 提供 transferTo()方法,直接將文件緩衝區數據發送至目標Channel。優於循環Write()方式

Netty實現零拷貝的源碼分析

直接讀寫
  • 找到Netty的一個類 AbstractNioByteChannel,定位其read()方法

  • 爲什麼找這個類?————————站在巨人的肩膀上學習

  • 在read()方法中有如下代碼
    在這裏插入圖片描述

  • 通過調用 allocHandle.allocate(allocator) 方法創建了一個 ByteBuf 對象

  • 跟進調用到 DefaultMaxBytesRecvByteBufAllocator 類的 allocate 方法

// 通過 allocate() 方法直接創建 ByteBuf 對象,避免了從堆內存中拷貝對象
public ByteBuf allocate(ByteBufAllocator alloc) {
    return alloc.ioBuffer(this.guess());
}
public int guess() {
    return Math.min(this.individualReadMax, this.bytesToRead);
}
ByteBuf聚合
  • CompositeByteBuf 類提供了ByteBuf聚合的方式,可以理解爲一個 ByteBuf 的包裝器
    在這裏插入圖片描述

  • 不需要做內存拷貝的代碼體現

  • 注意觀察註釋 No need to consolidate - just add a component to the list

    • 不需要合併,只需要向列表中添加一個組件
   private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) {
        assert buffer != null;
        boolean wasAdded = false;
        try {
            checkComponentIndex(cIndex);

            int readableBytes = buffer.readableBytes();

            // No need to consolidate - just add a component to the list.
            @SuppressWarnings("deprecation")
            Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
            if (cIndex == components.size()) {
                wasAdded = components.add(c);
                if (cIndex == 0) {
                    c.endOffset = readableBytes;
                } else {
                    Component prev = components.get(cIndex - 1);
                    c.offset = prev.endOffset;
                    c.endOffset = c.offset + readableBytes;
                }
            } else {
                components.add(cIndex, c);
                wasAdded = true;
                if (readableBytes != 0) {
                    updateComponentOffsets(cIndex);
                }
            }
            if (increaseWriterIndex) {
                writerIndex(writerIndex() + buffer.readableBytes());
            }
            return cIndex;
        } finally {
            if (!wasAdded) {
                buffer.release();
            }
        }
    }
文件傳輸零拷貝
  • 直接看 transferTo 方法,這裏該方法屬於Netty,不屬於Spring
//    DefaultFileRegion.java
long written = file.transferTo(this.position + position, count, target);

//  抽象方法  transferTo  來自 FileChannel.java
public abstract long transferTo(long position, long count,WritableByteChannel target) throws IOException;
  • 該方法有一段 API DOC是這樣的
    在這裏插入圖片描述

  • 從該通道中向指定的可寫通道寫入數據,不存在拷貝操作

Netty—內存池

  • Netty支持多種內存管理策略,實現了基於內存池的緩衝區重用機制。避免了不必要的回收操作。
  • Netty 的ByteBuf類提供了很多實現,通過在相關配置可以實現差異化定製
    在這裏插入圖片描述
  • 通過效率對比,Netty很牛
public class NettyPool {
    private static int loop = 1800000;
    private final byte[] CONTENT = new byte[1024];
    public void usePool() {
        long startTime = System.currentTimeMillis();
        ByteBuf poolBuffer = null;
        for (int i = 0; i < loop; i++) {
            poolBuffer = PooledByteBufAllocator.DEFAULT.directBuffer(1024);
            poolBuffer.writeBytes(CONTENT);
            poolBuffer.release();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("使用內存池耗時" + (endTime - startTime) + "ms.");
    }
    public void unUsePool() {
        long startTime = System.currentTimeMillis();
        ByteBuf buffer = null;
        for (int i = 0; i < loop; i++) {
            buffer = Unpooled.directBuffer(1024);
            buffer.writeBytes(CONTENT);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("不使用內存池耗時" + (endTime - startTime) + "ms.");
    }
}
//運行結果
使用內存池耗時499ms.
不使用內存池耗時1409ms.

源碼分析學習

  • 首先是 directBuffer 方法 AbstractByteBufAllocator類
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
    if (initialCapacity == 0 && maxCapacity == 0) {
        return emptyBuf;
    }
    validate(initialCapacity, maxCapacity);
    return newDirectBuffer(initialCapacity, maxCapacity);
}
  • newDirectBuffer方法
    在這裏插入圖片描述

  • 跳轉到 PooledByteBufAllocator 類

protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    PoolThreadCache cache = threadCache.get();
    //  從 cache  中獲取 PoolArena 內存區域
    PoolArena<ByteBuffer> directArena = cache.directArena;

    ByteBuf buf;
    if (directArena != null) {
        buf = directArena.allocate(cache, initialCapacity, maxCapacity);
    } else {
        if (PlatformDependent.hasUnsafe()) {
            buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
        } else {
            buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
        }
    }
    return toLeakAwareBuffer(buf);
}
  • PoolArena 類
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
    PooledByteBuf<T> buf = newByteBuf(maxCapacity);
    allocate(cache, buf, reqCapacity);
    return buf;
}
  • newByteBuf 實現
    在這裏插入圖片描述

  • DirectArena.java 顧名思義,直接內存區域

protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
    if (HAS_UNSAFE) {
        return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
    } else {
        return PooledDirectByteBuf.newInstance(maxCapacity);
    }
}
//  到這裏可以發現,使用內存池,之後其實就是循環調用,體現在 RECYCLER.get()方法
static PooledDirectByteBuf newInstance(int maxCapacity) {
    PooledDirectByteBuf buf = RECYCLER.get();
    buf.reuse(maxCapacity);
    return buf;
}

Netty—高效Reactor線程模型

  • Reactor單線程模型

    • 圖片引用自咕泡學院Tom老師的課堂筆記
      在這裏插入圖片描述
  • Reactor多線程模型提供一組NIO線程處理IO操作

    • 圖片引用自咕泡學院Tom老師的課堂筆記
      在這裏插入圖片描述
  • Reactor模型的特點

    • 單獨提供一個使用NIO線程實現的Acceptor線程用於監聽服務端,接收客戶端的TCP連接請求;
    • 由一個單獨的NIO線程池負責IO操作、消息的讀取、解碼、編碼和發送
    • 一個NIO線程可同時處理N條鏈路,但是一個鏈路只能對應一個NIO線程,防止發生併發操作問題

主從Reactor模型

  • 一個NIO線程負責監聽和處理所有客戶端連接,當併發連接數量過大,性能堪憂

  • 使用主從Reactor模型

    • 服務端用於接收請求的也是一個獨立的NIO線程池

    • Acceptor僅用於處理客戶端登陸,認證等操作

    • 認證完成後,將會創建新的SocketChannel註冊到IO線程池(sub reactor)中某個線程

    • 由該線程完成SocketChannel的讀寫和編碼

    • 圖片引用自咕泡學院Tom老師的課堂筆記

在這裏插入圖片描述

  • 可以通過創建不同的EventLoopGroup實例和參數配置靈活配置Reactor線程模型

Netty—無鎖串行化

  • 通常我們的認知中單線程串行化執行是一種效率不高的方式

  • Netty採用穿行無鎖化設計配合NIO線程池的參數設置

  • 可同時啓動多個串行化的線程並同時運行—避免了併發場景下的鎖競爭

  • 圖片引用自咕泡學院Tom老師的課堂筆記
    在這裏插入圖片描述

  • 從上圖可以發現從NioEventLoop讀取到消息之後,直到返回,都是採用單線程執行

  • 避免了鎖競爭

總結:Netty—高效的併發編程

  • 迴歸本質,Netty高效併發編程
  • 其實也就是靈活並正確使用了volatile、CAS和原子類。通過源碼學習可探知其冰山一角
  • 從Netty提供的NIO模型中可以學習到其對線程容器的高明使用
  • 合理使用讀寫鎖提供IO操作性能
  • 同時,Netty提供了對 Google Protobuf (高性能序列化框架)的支持
  • Netty擴展接口也可實現其它高性能序列化框架
    • 不同序列化和反序列化性能對比

​ 圖片引用自咕泡學院Tom老師的課堂筆記
在這裏插入圖片描述

  • 在Netty啓動輔助類中可以靈活配置TCP參數,能夠滿足各種不同的業務場景
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章