Netty實戰一之異步和事件驅動


Netty是一款異步的事件驅動的網絡應用程序框架,支持快速地開發可維護的高性能的面向協議的服務器和客戶端。

使用Netty你可以並不是很需要網絡編程、多線程處理、並發等專業Java知識的積蓄。

Netty的架構方法和設計原則是:每個小點都和它的技術性內容一樣重要,窮其精妙,因此我們也借此可以了解更多方面: 關注點分離——業務和網絡邏輯解耦 模塊化和可復用性 可測試性作為首要的要求

1、Java網絡編程

早期的Java API只支持由本地系統套接字庫(需要了解復雜的C語言套接字庫)提供的所謂的阻塞函數。

以下給出阻塞I/O示例,代碼清單1-1 輸入圖片說明代碼清單1-1實習點了Socket API的基本模式之一。 ——ServerSocket上的accept()方法將會一直阻塞到一個連接建立,隨后返回一個新的Socket用於客戶端和服務器之間的通信。該ServerSocket將繼續監聽傳入的連接。 ——BufferedReader和PrintWriter都衍生於Socket的輸入輸出流,前者從一個字符輸入流中讀取文本,后者打印對象的格式化的表示到文本輸出流。 ——readLine()方法將會阻塞,直到由換行符或者回車符結尾的字符串被讀取。

以上代碼段可以明顯看出,只能同時處理一個連接,要管理多個並發客戶端,需要為每個新的客戶端Socket創建一個新的Thread。如下圖1-1所示 輸入圖片說明

我們可以嘗試着分析這個方案:

第一,在任何時候都可能又大量的線程處於休眠狀態,只是等待輸入或者輸出數據,這可以算是一種資源浪費;

第二,需要為每個線程的調用棧分配內存,其默認值大小區間為64KB到1KB;

第三,即使JVM在物理上可以支持非常大的數量的線程,但是遠在到達極限之前,上下文切換所帶來的的開銷就會有很多麻煩。

當然,這種並發方案對於支撐中小數量的客戶端來說可以接收,但是為了支撐100000或更多的並發連接所需資源使得它很不理想。

2、Java NIO

除了阻塞調用,本地套接字庫很早也提供了非阻塞調用,其為網絡資源的利用率提供了相當多的控制:

——可以使用setsocket()方法配置套接字,以便讀/寫調用再沒有數據的時候立即返回

(有關NIO的解釋:新的I/O,非阻塞I/O)

3、選擇器

圖1-2展示了一個非阻塞設計,消除了上一節的弊端。 輸入圖片說明Java.nio.channels.Selector是java的非阻塞I/O實現的關鍵,使用時間通知API以確定在一組非阻塞套接字中,哪些已經就緒能夠進行I/O相關的操作。

一個單一的線程便可以處理多個並發的連接。

與阻塞I/O模型相比,這種模型提供了更好的資源管理: ——較少的線程,減少了內存管理和上下文切換所帶來的的開銷 ——沒有I/O操作時,線程可以用於其他任務

但是在高負載下可靠和高效地處理和調度I/O操作時一項繁瑣而且容易出錯的任務,即使Java NIO也很難完美解決這個問題。

4、Netty簡介

首先我們一直要關注和注意到:直接使用底層的API暴露了復雜性,並且引入了對往往供不應求的技能的關鍵性依賴,這也是面向對象基本概念:用較簡單的抽象隱藏底層實現的復雜性。

在網絡編程領域,Netty是java的卓越框架,我們在了解Netty之前,先看看它具備的而且經常被提及的特性

設計 ——統一的API,支持多種傳輸類型,阻塞的和非阻塞的,簡單而強大的線程模型,真正的無連接數據報套接字支持,鏈接邏輯組件以支持復用

易於使用 ——詳實的JavaDoc和大量的示例集,不需要超過JDK1.6的依賴,最新版需要JDK1.8

性能 ——擁有比Java的核心API更高的吞吐量以及更低的延遲,得益於池化和復用,擁有更低的資源消耗,最少的內存復制

健壯性 ——不會因為慢速、快速或者超載的連接而導致OutOfMemoryError,消除在高速網絡中NIO應用程序常見的不公平讀/寫比率

安全性 ——完整的SSL/TLS以及StarTLS支持,可用於受限環境下,如Applet和OSGI

社區驅動 ——發布快速而且頻繁

5、異步和事件驅動

Netty中,或者說Netty的講解中使用量較大的是“異步”這個詞,因此我們也簡述下所謂 異步 :通常,你只有在已經問了一個問題之后才會得到一個和它對應的答案,而在你等待它的同時你也可以做點別的事情。

本質上,對於計算機而言,一個既是異步的又是事件驅動的系統會表現出一種特殊的、對我來說極具價值的行為:它可以以任意的順序響應在任意的時間點產生的事件。

實現最高級別的可伸縮性:一種系統、網絡或者進程在需要處理的工作不斷增長時,可以通過某種可行的方式或者擴大它的處理能力來適應這種增長的能力。

異步和可伸縮性之間的聯系?

——非阻塞網絡調用使得我們可以不必等待一個操作的完成,完全異步的I/O正是基於這個特性構建的,並且更進一步:異步方法會立即返回,並且在它完成時,會直接或者在稍后的某個時間點通知用戶 ——選擇器使得我們能夠通過較少的線程便可監視許多連接上的事件。

至此,至少我們明顯知道非阻塞是更優於阻塞的。

6、Netty的核心組件——Channel

Java NIO的一個基本構造,代表一個到實體的開放連接,如讀操作和寫操作,可以把Channel看作是傳入或者傳出數據的載體,因此它可以被打開或者被關閉,連接或者斷開鏈接。

7、Netty的核心組件——回調

即一個方法,一個指向已經被提供給另外一個方法的方法的引用。回調在廣泛的編程場景中都有應用,而且也是在操作完成之后通知相關方最常見的方式之一。

Netty在內部使用了回調來處理事件,當一個回調被觸發時,相關的事件可以被一個interfaceChannelHandler的實現處理,代碼清單1-2展示了一個例子,當一個新的連接已經被建立時,ChannelHandler的channelActive()回調方法將會被調用,並將打印一條信息。

輸入圖片說明

8、Netty的核心組件——Future

Futrue提供了另一種在操作完成時通知應用程序的方式,這個對象可以看作是一個異步操作的結構的占位符;它將在未來的某個時刻完成,並提供對其結果的訪問。

JDK預置了interface java.util.concurrent.Future,但是其所提供的實現,只允許手動檢查對應的操作是否已經完成,或者一直阻塞直到它完成,這是比較繁瑣,所以Netty提供了它自己的現實——ChannelFuture,用於在執行異步操作的時候使用。

ChannelFuture 提供了幾種額外的方法,這些方法使得我們能夠注冊一個或者多個操作完成時被調用。然后監聽器可以判斷該操作是成功完成或出錯,后者則會產生Throwable,簡而言之,由ChannelFutureListener提供的通知機制消除了手動檢查對應的操作是否完成的必要。

每個Netty的出站I/O操作都將返回一個ChannelFuture;也就是說,他們都不會阻塞,也就是說Netty完全是異步和事件驅動的。

代碼清單1-3 展示了一個ChannelFuture作為一個I/O操作的一部分返回的例子,這里,connect()方法將會直接返回,而不會阻塞,該調用將會在后台完成。這究竟什么時候會發生則取決於若干的元素,但這個關注點已經從代碼中抽象出來了,因為線程不用阻塞以等待對應的操作完成,所以它可以同時做其他的工作,從而更加有效地利用資源。 輸入圖片說明

代碼清單1-4 顯示了如何利用ChannelFutureListener,首先,要連接到遠程節點上,然后,要注冊一個新的ChannelFutureListener到對connect()方法調用所返回的ChannelFuture上,當該監聽器被通知連接已經建立的時候,要檢查對應的狀態。如果成功,那么將數據寫到該Channel,否則,要從ChannelFuture中檢索對應的Throwable。 輸入圖片說明

需要注意的是,對錯誤的處理完全取決於你、目標,當然也包括目前任何對於特定類型的錯誤加以限制。

如果你把ChannelFutureListener 看作是回調的一個更加精細的版本,那么你是對的,事實上,回調和Future是相互補充的機制,他們相互結合,構成了Netty本身的關鍵構件塊之一。

9、事件和ChannelHandler

Netty使用不同的事件來通知我們狀態的改變或是操作的狀態。這使得我們能夠基於已經發生的事件來觸發適當的動作。

——記錄日志

——數據轉換

——流控制

——應用程序邏輯

Netty是一個網絡編程框架,所以事件是按照他們與入站或出站數據流的相關性進行分類的

——連接已被激活或者連接失活

——數據讀取

——用戶事件

——錯誤事件

出站事件是未來將會觸發的某個動作的操作結果,包括:

——打開或者關閉到遠程節點的連接

——將數據寫到或者沖刷到套接字

每個事件都可以被分發給ChannelHandler類中的某個用戶實現的方法。這是一個將事件驅動范式直接轉換為應用程序構件塊的例子,圖1-3展示了一個事件是如何被一個這樣的ChannelHandler鏈處理的。 輸入圖片說明

Netty的ChannelHandler為處理器提供了基本的抽象,如圖1-3所示,目前我們可以認為每個ChannelHandler的實例都類似於一種為了響應特定事件而被執行的回調。

Netty提供了大量預定義的開箱即用的ChannelHandler實現,在內部,ChannelHandler自己也使用了事件和Future,使得它們成為了你的應用程序將使用的相同抽象的消費者。

10、本章回顧

有關於Future、回調、ChannelHandler

Netty的異步編程模型是建立在Future和回調的概念智商,而將事件派發到ChannelHandler的方法則發生在更深的層次上,結合在一起,提供了一個處理環境,使得我們的應用程序邏輯可以獨立於任何網絡操作相關的顧慮而獨立地演變。

攔截操作以及高速地轉換入站數據和出站數據,都只需要你提供回調或者利用操作所返回的Future,這使得鏈接操作變得即簡單又高效,並且促進了可重用的通用代碼的編寫。

有關選擇器、事件、EventLoop

Netty通過觸發事件將Selector從應用程序中抽象出來,消除了所有本來將要需要手動編寫的派發代碼。在內部,將會為每個Channel分配一個EventLoop,用以處理所有事件: ——注冊感興趣的事件 ——將事件派發給ChannelHandler ——安排進一步的動作

EventLoop本身只由一個線程驅動,其處理了一個Channel的所有I/O事件,並且在該EventLoop的整個生命周期內都不會改變,我們並不用對同步有任何顧慮,僅僅需要專注於提供正確的邏輯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM