IO與Netty瞭解一下!網路通訊框架是這樣構成的!
隨著網際網路應用對高併發、高可用的要求越來越高,傳統的垂直架構由於其自身的侷限性逐漸被分散式、彈性伸縮的微服務架構替代。
微服務將單體應用拆分為多個獨立的微服務應用,每個應用獨立執行,每個服務間通過遠端呼叫(RPC)進行通訊,此時高效能的通訊方式就顯得尤為重要,實現RPC通訊的底層框架Netty由於其穩定性、拓展性以及框架成熟度的優秀表現,在RPC框架領域應用廣泛,著名的Hadoop、Dubbo、RocketMQ等框架都使用了Netty作為其網路通訊的底層框架。
Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網路通訊框架,對比於BIO(Blocking I/O,阻塞IO),它的併發效能得到了很大提高。接下來,帶大家瞭解下BIO與NIO的區別。
BIO (blocking io)
BIO即傳統IO,是一種同步阻塞的通訊模式,模式簡單,使用方便。但併發處理能力低,通訊耗時,依賴網速。應用程式在獲取網路資料的時候,如果網路傳輸資料很慢,那麼程式就一直等著,直到傳輸完畢為止。
採用BIO通訊模型的服務端,通常由一個獨立的Acceptor執行緒負責監聽客戶端的連線,它接收到客戶端連線請求之後為每個客戶端建立一個新的執行緒進行鏈路處理,每處理完成後,通過輸出流返回應答給客戶端,執行緒銷燬。即典型的請求——應答通訊模型。
1、Server端
Client端
這種處理方式在高併發的情況下,會創建出大量的執行緒,以至於最終耗盡資源,無法對外服務。所以傳統的BIO執行緒模型,在現代情景的網際網路應用中無法作為底層的通訊模型進行使用。
NIO (non-blocking io)
NIO也稱為New IO,是一種同步非阻塞的通訊模式,NIO 相對於BIO來說是一大進步。客戶端和伺服器之間通過Channel通訊,NIO可以在Channel進行讀寫操作,這些Channel都會被註冊在Selector多路複用器上。Selector通過一個執行緒不停的輪詢這些Channel,找出已經準備就緒的Channel執行IO操作。NIO 通過一個執行緒輪詢,再基於輪詢到的事件進行處理,不需要再為每個連線單獨開個執行緒處理,從而以較高的資源複用率處理成千上萬個客戶端的請求,這就是非阻塞NIO的特點。
NIO的三大核心為Selector(選擇器),Buffer(緩衝區),Channel(通道)。
Java架構交流群:895244712,群內提供Java學習資料(架構技術視訊,面試,就業指導,java書籍,工具&軟體等)歡迎大家加入。
▲緩衝區Buffer
它是NIO與BIO的一個重要區別。BIO是將資料直接寫入或讀取到Stream物件中,而NIO的資料操作都是在緩衝區進行的。緩衝區實際上也是一個數組,通常是一個位元組陣列(ByteBuffer),這個陣列為緩衝區提供了資料的訪問讀寫等操作屬性,如位置、容量、上限等概念。(ByteBuffer由於只有一個位置指標處理讀寫操作,因此每次讀寫的時候都需要額外呼叫flip()將指標復位,否則功能將出錯)
▲通道Channel
和流不同,通道是雙向的。通道分為兩大類:一類是網路讀寫(SelectableChannel),一類是用於檔案操作(FileChannel),我們使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子類。最關鍵的是channel有多種狀態位,可以與selector結合起來,方便selector去識別。
▲選擇器(多路複用器)Selector
是NIO程式設計的基礎,非常重要,提供選擇已經就緒的任務的能力。當Channel註冊到選擇器後,Selector會分配給每個通道一個key值。Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以取得就緒的Channel集合,從而進行後續的IO操作。
1、Server端
2、Client端
為什麼選擇Netty
Netty是基於Java NIO的網路應用框架,使用Netty可以快速開發網路應用,例如伺服器和客戶端的協議。Netty提供了一種新的方式來開發網路應用,使得它很容易使用和有很強的擴充套件性。Netty內部的實現是複雜的,但是Netty提供了簡單易用的API從網路處理程式碼中解耦業務邏輯。
▲Netty的優點有:
1.相較於JDK原生的NIO提供的API,Netty的API使用簡單,開發門檻低;
2.功能強大,預置了多種編解碼功能,支援多種協議開發;
3.定製能力強,可以通過ChannelHandler進行擴充套件;
4.效能高,對比其它NIO框架,Netty綜合性能最優;
5.經歷了大規模的應用驗證。在網際網路、大資料、網路遊戲、企業應用、電信軟體得到成功,很多著名的框架通訊底層就用了Netty,比如Dubbo;
6.穩定,修復了已經發現的NIO Bug。
Java架構交流群:895244712,群內提供Java學習資料(架構技術視訊,面試,就業指導,java書籍,工具&軟體等)歡迎大家加入。
Netty的使用
▲步驟:
1.建立兩個NioEventLoopGroup例項,NioEventLoopGroup是個執行緒組,它包含了一組NIO執行緒,專門用於網路事件處理。這裡建立兩個的原因是一個用於服務端接收客戶端的連線,另一個用於進行SocketChannel的網路讀寫;
2.建立一個ServerBootstrap物件,它是Netty用於啟動NIO服務端的輔助啟動類。配置Netty的一系列引數,例如將兩個NIO執行緒組當作入參傳遞到ServerBootstrap中,設定建立的Channel,接受傳出資料的快取大小等;
3.建立一個實際處理資料的類Channellnitalizer,繫結IO事件的處理類ChannelHandler,進行初始化的準備工作,比如設定接受傳出資料的字元、格式以及實際處理資料的介面;
4.繫結介面,執行同步阻塞方法;
5.最後呼叫NIO執行緒組的shutdownGracefully進行優雅退出,它會釋放跟shutdownGracefully相關聯的資源。
1、Server端
ChannelHandler類似於Servlet的Filter過濾器,負責對IO事件或者IO操作進行攔截和處理,它可以選擇性地攔截和處理自己感興趣的事件。Netty提供了ChannelHandlerAdapter基類,如果使用者ChannelHandler關心某個事件,只需要覆蓋ChannelHandlerAdapter對應的方法即可,對於不關心的,可以直接繼承使用父類的方法,這樣子類的程式碼就會非常簡潔和清晰。
ChannelHandlerContext物件提供了許多操作,這裡我們呼叫了write(Object)方法來逐字地把接受到的訊息寫入。ctx.write(Object)方法不會使訊息寫入到通道上,他被緩衝在了內部,你需要呼叫ctx.flush()方法來把緩衝區中資料強行輸出。或者你可以用更簡潔的cxt.writeAndFlush(msg)以達到同樣的目的。
還有ByteBuf是一個引用計數物件,這個物件需要呼叫release()方法來釋放。
2、ServerHandler
3、Client端
4、ClientHandler
TCP拆包和粘包的問題解決
在基於流的傳輸裡比如TCP/IP,接收到的資料會先被儲存到一個socket接收緩衝裡。不幸的是,基於流的傳輸並不是一個數據包佇列,而是一個位元組佇列。即使你傳送了2個獨立的資料包,作業系統也不會作為2個訊息處理,而僅僅是作為一連串的位元組而言。因此這是不能保證你遠端寫入的資料就會準確地讀取。舉個例子,讓我們假設作業系統的TCP/TP協議棧已經接收了3個數據包:
但是基於流傳輸的性質,在你的應用程式讀取資料的時候很可能會被分成下面的片段。
因此,一個接收方不管他是客戶端還是服務端,都應該把接收到的資料整理成一個或者多個更有意思並且能夠讓程式的業務邏輯更好理解的資料。在上面的例子中,接收到的資料應該被構造成下面的格式:
▲解決方式
1.訊息定長,例如每個報文的大小固定為200個位元組,如果不夠,空位補空格。
2.在包尾部增加特殊字元進行分割,例如加”$”
3.將訊息分為訊息頭和訊息體,訊息頭中包含表示資訊的總長度(或者訊息體長度)的欄位。
用POJO代替ByteBuf
在ChannelHandler中使用POJO的優點是顯而易見的; 處理程式將從ByteBuf中提取資訊的程式碼分離出來,使程式更易維護和可重用。
下面的例子通過MarshallingCodeCFactory工廠類建立了MarshallingDecoder解碼器、MarshallingEncoder編碼器,並新增到ChannelPipeline中。
1、POJO類
2、在ChannelPipeline加上編解碼的Handler
3、MarshallingCodeCFactory工廠類