Netty框架學習之(一):Netty框架簡介
1. 簡介
官方定義為:”Netty 是一款非同步的事件驅動的網路應用程式框架,支援快速地開發可維護的高效能的面向協議的伺服器
和客戶端”,按照慣例貼上一張High Level的架構圖:
縱觀Java系的多種伺服器/大資料框架,都離不開Netty做出的貢獻,本文對Netty做一個簡單的概述
2. 主要特性
Netty有很多重要的特性,主要特性如下:
- 優雅的設計
- 統一的API介面,支援多種傳輸型別,例如OIO,NIO
- 簡單而強大的執行緒模型
- 豐富的文件
- 卓越的效能
- 擁有比原生Java API 更高的效能與更低的延遲
- 基於池化和複用技術,使資源消耗更低
- 安全性
- 完整的SSL/TLS以及StartTLS支援
- 可用於受限環境,如Applet以及OSGI
Netty的以上特性,比較適合客戶端資料較大的請求/處理場景,例如web伺服器等,要想知道有哪些系統使用了Netty,可以參考:http://netty.io/wiki/adopters.html
3. 主要術語
在正式開始之前,先對Netty涉及到的一些術語做個簡單的說明
3.1 IO模型:BIO/NIO/Netty
3.1.1 BIO(Blocking IO):阻塞IO
早期的Java API(java.net)提供了由本地系統套接字型檔提供的所謂的阻塞函式,樣例程式碼如下:
ServerSocket serverSocket = new ServerSocket(portNumber);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
while ((request = in.readLine()) != null) {
if ("Done".equals(request)) {
break;
}
response = processRequest(request );
out.println(response);
}
這段程式碼片段將只能同時處理一個連線,要管理多個併發客戶端,需要為每個新的客戶端
Socket 建立一個新的 Thread,執行緒模型如下圖所示:
該種模型存在以下兩個問題:
1. 在任何時候都可能有大量的執行緒處於休眠狀態,只是等待輸入或者輸出資料就緒,這可能算是一種資源浪費
2. 需要為每個執行緒的呼叫棧都分配記憶體
3. 即使 Java 虛擬機器(JVM) 在物理上可以支援非常大數量的執行緒, 但是遠在到達該極限之前, 上下文切換所帶來的開銷就會帶來麻煩
3.1.2 NIO(Non Blocking IO):非阻塞IO
Java的NIO特性在JDK 1.4中引入,其結構如下:
從該圖可以看出Selector 是Java 的非阻塞 I/O 實現的關鍵。它使用了事件通知 API
以確定在一組非阻塞套接字中有哪些已經就緒能夠進行 I/O 相關的操作。因為可以在任何的時間檢查任意的讀操作或者寫操作的完成狀態。該種模型下,一個單一的執行緒便可以處理多個併發的連線。
與BIO相比,該模型有以下特點:
1. 使用較少的執行緒便可以處理許多連線,因此也減少了記憶體管理和上下文切換所帶來開銷
2. 當沒有 I/O 操作需要處理的時候,執行緒也可以被用於其他任務
雖然Java 的NIO在效能上比BIO已經相當的優秀,但是要做到如此正確和安全並
不容易。特別是,在高負載下可靠和高效地處理和排程 I/O 操作是一項繁瑣而且容易出錯的任務,此時就時Netty上場的時間了。
3.1.3 Netty
Netty對NIO的API進行了封裝,通過以下手段讓效能又得到了一定程度的提升
1. 使用多路複用技術,提高處理連線的併發性
2. 零拷貝:
1. Netty的接收和傳送資料採用DIRECT BUFFERS,使用堆外直接記憶體進行Socket讀寫,不需要進行位元組緩衝區的二次拷貝
2. Netty提供了組合Buffer物件,可以聚合多個ByteBuffer物件進行一次操作
3. Netty的檔案傳輸採用了transferTo方法,它可以直接將檔案緩衝區的資料傳送到目標Channel,避免了傳統通過迴圈write方式導致的記憶體拷貝問題
3. 記憶體池:為了減少堆外直接記憶體的分配和回收產生的資源損耗問題,Netty提供了基於記憶體池的緩衝區重用機制
4. 使用主從Reactor多執行緒模型,提高併發性
5. 採用了序列無鎖化設計,在IO執行緒內部進行序列操作,避免多執行緒競爭導致的效能下降
6. 預設使用Protobuf的序列化框架
7. 靈活的TCP引數配置
詳細說明,可參考: http://www.infoq.com/cn/articles/netty-high-performance#anch111813
3.1.4 簡單的效能測試
通過在本地分別使用BIO,NIO,Netty NIO實現了一個簡單的服務端程式(該程式接收到請求後,sleep 1毫秒,並返回簡單的一句話)分別對三種方式使用Jemeter進行效能測試(一百個併發,每個併發傳送一百個相同訊息),結果如下:
單執行緒的java net:
NIO:
Netty NIO:
以上結果或是受到其他條件的影響,結果僅供供參考
3.2 Callback:
回撥在廣泛的程式設計場景中都有應用,一般是在完成某個特定的操作後對相關方法進行呼叫。
Netty 在內部使用回撥來處理事件;當一個回撥被觸發時,相關的事件可以被一個 interfaceChannelHandler 的實現處理,例如Channel啟用時會呼叫ChannelActive方法,樣例程式碼如下:
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx)throws Exception {
System.out.println("Client " + ctx.channel().remoteAddress() + connected");
}
}
3.3 Future:
Future一般用在當執行非同步操作時需要獲取未來的某個時候才能獲取到的結果。
JDK 預置了 interface java.util.concurrent.Future,但是其所提供的實現,只
允許手動檢查對應的操作是否已經完成,或者一直阻塞直到它完成。這是非常繁瑣的,所以 Netty提供了它自己的實現——ChannelFuture,用於在執行非同步操作的時候使用。
ChannelFuture提供了幾種額外的方法,這些方法使得我們能夠註冊一個或者多個
ChannelFutureListener例項。監聽器的回撥方法operationComplete(),將會在對應的
操作完成時被呼叫。然後監聽器可以判斷該操作是成功地完成了還是出錯了。如果是後者,我們可以檢索產生的Throwable。 通過使用ChannelFutureListener機制可以避免對
操作結果進行手動檢查。
每個 Netty 的出站 I/O 操作都將返回一個ChannelFuture,即不會阻塞後續的操作。
下面的例子中的connect()方法會直接返回,後續的成功或失敗將由其註冊的FutureListener來處理。
try {
// 使用非同步的方式連線Server,不管成功失敗,都是執行下面System.out的語句,最後的連線結果由FutureListener進行處理
ChannelFuture future = bootstrap.connect();
System.out.println("Finished connect operation");
future.addListener((ChannelFutureListener) future1 -> {
if (future1.isSuccess()){
ByteBuf buffer = Unpooled.copiedBuffer(
"Hello", Charset.defaultCharset());
ChannelFuture wf = future1.channel()
.writeAndFlush(buffer);
System.out.println("Connect successful!");
}else{
System.out.println("Connect failed!");
Throwable cause = future1.cause();
cause.printStackTrace();
}
});
System.out.println("Finished connect operation2");
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
最後的列印結果如下:
Finished connect operation
Finished connect operation2
Connect failed!
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: localhost/127.0.0.1:8888
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:325)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect
...............................................
Caused by: java.net.ConnectException: Connection refused: no further information
... 11 more
3.4 Event
Netty 使用不同的事件來通知狀態的改變或者是操作的狀態。事件可能包括:
- 連線已被啟用或者連線失活
- 資料讀取;
- 使用者事件;
- 錯誤事件。
- 開啟或者關閉到遠端節點的連線;
- 將資料寫到或者沖刷到套接字。
每個事件都可以被分發給 ChannelHandler 類中的某個使用者實現的方法。這是將事件驅動正規化直接轉換為應用程式邏輯處理比較理想的位置。
下圖展示了事件是怎麼被處理的:
對每個事件可以進行,記錄日誌,資料轉換,應用程式邏輯處理等操作,
Netty 提供了大量預定義的可以開箱即用的 ChannelHandler 實現,包括用於各種協議
(如 HTTP 和 SSL/TLS)的 ChannelHandler。後續博文會對一些Handler進行簡單的介紹