[分佈式] 基於Netty及websocket應用與總結

1、介紹

Netty 是一個異步的、基於事件驅動的網絡應用框架,用以快速開發高性能、高可靠性的網絡 IO 程序。

主要特性有以下幾點:

統一的 API,適用於不同的協議(阻塞和非阻塞)

基於靈活、可擴展的事件驅動模型

高度可定製的線程模型(Reactor單線程、多線程、主從線程)

更好的吞吐量,低延遲

更省資源

NIO零拷貝(FileChannel.transferTo)

應用於RPC等中間件框架底層網絡通信、大數據等領域

2. 網絡編程模型(同步阻塞BIO)


通過BIO通信模型的服務端,通常接收到客戶端的請求後為每個客戶端創建一個新的線程進行鏈路處理。處理完成後,通過輸出流應答返回給客戶端,線程銷燬,缺點是併發量高時,線程數膨脹,發生線程堆棧溢出。

同步非阻塞NIO


服務器實現模式為一個線程處理多個請求,即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O的請求進行處理

異步非阻塞AIO

AIO引入異步通道的概念,採用了proactor模式,簡化了程序編寫,有效的請求才啟動線程,它的特點是先由操作系統完成後才通知服務端程序啟動線程去處理,一般適用於連接數較多且連接時間較長的應用

3. 一個簡單的http server

public void bind(int port) throws Exception {

EventLoopGroup bossGroup= new NioEventLoopGroup();

EventLoopGroup workerGroup = new NioEventLoopGroup();

try{

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup,workerGroup)

.channel(NioServerSocketChannel.class)

.option(ChannelOption.SO_BACKLOG,1024)

.childHandler(new ChildChanelHandler());

ChannelFuture f = b.bind(port).sync();

f.channel().closeFuture().sync();

}finally {

bossGroup.shutdownGracefully();

workerGroup.shutdownGracefully();

}

}

}


5. Netty粘包/拆包問題


LineBasedFrameDecoder 和StringDecoder 都是Netty自帶的類。特別要說下的,就是StringDecoder,它的作用是將接收到ByteBuf二進制數據,轉換成字符串。另外,LineBasedFrameDecoder ,是一個非常簡單的幀解碼器,包含此解碼器在內,Netty中比較常用的幀解碼器,大致如下:

(1)固定長度幀解碼器 - FixedLengthFrameDecoder

適用場景:每個上層數據包的長度,都是固定的,比如 100。在這種場景下,只需要把這個解碼器加到 pipeline 中,Netty 會把底層幀,拆分成一個個長度為 100 的數據包 (ByteBuf),發送到下一個 channelHandler入站處理器。

(2)行分割幀解碼器 - LineBasedFrameDecoder

適用場景:每個上層數據包,使用換行符或者回車換行符做為邊界分割符。發送端發送的時候,每個數據包之間以換行符/回車換行符作為分隔。在這種場景下,只需要把這個解碼器加到 pipeline 中,Netty 會使用換行分隔符,把底層幀分割成一個一個完整的應用層數據包,發送到下一站。前面的例子,已經對這個解碼器進行了演示。

(3)自定義分隔符幀解碼器 - DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder 是LineBasedFrameDecoder的通用版本。不同之處在於,這個解碼器,可以自定義分隔符,而不是侷限於換行符。如果使用這個解碼器,在發送的時候,末尾必須帶上對應的分隔符。

(4)自定義長度幀解碼器 - LengthFieldBasedFrameDecoder

這是一種基於靈活長度的解碼器。在數據包中,加了一個長度字段(長度域),保存上層包的長度。解碼的時候,會按照這個長度,進行上層ByteBuf應用包的提取。

6. 序列化

MsgPack和Hessian序列化方式的區別

1) Hessian序列化的時候,會寫入字段名稱,然後字段值,可以想象為一個map。

2) MsgPack序列化的時候,不寫入字段名字,會按字段順序寫入值,可以想象為一個數組。

從這裡可以看出:

1) Hessian產生的數據包較大,MsgPack產生的數據包較小。網絡傳輸數據更小。

2) 序列化中Hessian的性能較差,MsgPack性能更佳

3) Hessian的擴展性更好,上下兼容時,可以隨意添加字段位置(相當於map可以隨便賦值);MsgPack的性能更佳,但是上下兼容時,需要保證字段順序(包括枚舉順序)。

4) 還包括其它一些差異:

例如:Hessian對Map/List等集合的支持就是全變成最普通的Hashmap或者ArrayList,一些指定的類型會丟失(例如LinkedHashMap–>HashMap),但是支持一些匿名的Map/List等集合類;

而MsgPack會保留集合類的類型(例如LinkedHashMap),但是不支持一些匿名集合類(例如List.subList(),Map.keySet(),Collections.emptyList(),Guava的匿名集合類,數據庫查詢結果直接返回的list等)。

總結: 如果需要支持字段順序不一樣的情況下調用,請使用Hessian序列化;但是為了性能及跨語言兼容性,在保證客戶端與服務端的接口類文件保持一致的情況下,請使用MsgPack序列化!!

7. Websocket介紹

WebSocket是HTML5新增的協議,它的目的是在瀏覽器和服務器之間建立一個不受限的雙向通信的通道,比如說,服務器可以在任意時刻發送消息給瀏覽器。

HTTP協議是一個請求-響應協議,請求必須先由瀏覽器發給服務器,服務器才能響應這個請求,再把數據發送給瀏覽器 WebSocket是一種在單個TCP連接上進行全雙工通訊的協議。

WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。

8、WebSocket握手請求

WebSocket是應用層協議,是TCP/IP協議的子集,通過HTTP/1.1協議的101狀態碼進行握手。

客戶端連接

GET / HTTP/1.1

Host: localhost:8080

Origin: http://127.0.0.1:3000

Connection: Upgrade

Upgrade: websocket

Sec-WebSocket-Version: 13

Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==

服務器響應

HTTP/1.1 101 Switching Protocols

Connection:Upgrade

Upgrade: websocket

Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PT

Connection 必須設置 Upgrade,表示客戶端希望連接升級

Upgrade 字段必須設置 Websocket,表示希望升級到 Websocket 協議。

Sec-WebSocket-Key 是隨機的字符串,服務器端會用這些數據來構造出一個 SHA-1 的信息摘要。

Sec-WebSocket-Version 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均應當棄用。

9、WebSocket 事件

以下是 WebSocket 對象的相關事件。

事件 事件處理程序 描述

open Socket.onopen 連接建立時觸發

message Socket.onmessage 客戶端接收服務端數據時觸發

error Socket.onerror 通信發生錯誤時觸發

close Socket.onclose 連接關閉時觸發

10、Netty線程模型

Netty 支持三種線程模型,分別是單線程模型,多線程模型,以及主從線程模型





實踐

1、協議異常,斷開連接,客戶端重連

public class XXXXHandler extends SimpleChannelInboundHandler {

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{

log 一下異常信息


if(ctx.channel().isActive()){
ctx.close();
}


}}

2、不要在EventLoopGroup中做耗時任務,如線程wait/sleep操作、耗時的數據計算、DB查詢等,放在業務線程池處理

3、ByteBuf分類的使用:

實際使用中在I/O通信線程的讀寫使用DirectByteBuf,而在後端消息業務中編解碼中使用HeapByteBuf。

4、診斷 byteBuf洩漏,參見 http://netty.io/wiki/reference-counted-objects.html ,基本把編碼過程中所有情況都列出,不再贅序。測試階段中一般使用-Dio.netty.leakDetectionLevel=paranoid,基本可以把大部分洩漏問題代碼檢測出來。