什麼是Netty?
- 在學習Netty之前,首先要明白Netty是什麼?
- 是什麼?不如去搞清楚它是用來幹嘛的
- 幹嘛的?來了,Netty是用來提高通信性能的一個開源的基於java的通信框架
- 想要提高分佈式系統下各個服務器之前的通信性能,Netty是一個不錯的選擇
初識Netty高性能
- 在IO編程過程中,如果要處理多個請求的時候。可以使用多線程或者IO多路複用技術進行處理
- java提供Selector選擇器可以把多個IO阻塞到同一個select上實現多路複用
- 那麼Netty是怎麼做的或者Netty是如何實現高性能的
Netty基本通信架構
-
Netty架構按照Reactor模式來設計實現
-
Reactor模式可以認爲是一種設計模式——即反應器模式
-
服務端通信序列
- 圖片引用自咕泡學院Tom老師的課堂筆記
- 圖片引用自咕泡學院Tom老師的課堂筆記
-
客戶端通信序列
- 圖片引用自咕泡學院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老師的課堂筆記
- 圖片引用自咕泡學院Tom老師的課堂筆記
-
Reactor多線程模型提供一組NIO線程處理IO操作
- 圖片引用自咕泡學院Tom老師的課堂筆記
- 圖片引用自咕泡學院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參數,能夠滿足各種不同的業務場景