用Netty開發中介軟體:網路編程基礎

來源:互聯網
上載者:User

標籤:ack   group   指標   site   java程式   coder   write   重新整理   讀取資料   

用Netty開發中介軟體:網路編程基礎

《Netty權威指南》在網上的評價不是非常高,尤其是第一版,第二版能稍好些?入手後高速翻看了大半本,不免還是想對《Netty權威指南(第二版)》吐槽一下:

  • 前半本的代碼排版太糟糕了,簡直就是直接列印Word的版式似的。

    原始碼解析部分的條理性和代碼排版好多了,感覺比其它部分的品質高多了。

  • 假設你是剛開始學習的人可能會感覺非常具體,差點兒每部分都會來一套client和服務端的Demo。假設你不是入門者的話可能會感覺水分比較多。
  • 最後一部分進階特性。內容有些混亂,不少內容都在不同的章節裡反覆了好幾遍。

不管如何,假設你是網路通訊或後台中介軟體的入門者。尤其是Java程式猿,那麼這本書還是值得入手的。尤其是書中對I/O模型、協議解析、可靠性等方面的點撥還是會讓你有非常多收穫的。好了吐槽就到這了,下面就是《Netty權威指南(第二版)》的重點摘錄,抽掉了水分,全部乾貨都在這裡了。

1.Linux和Java的I/O演化之路

Linux從select -> poll -> epoll機制。簡要說epoll的長處就是:從主動輪詢+線性掃描變為被動事件通知,mmap避免到使用者態的拷貝,更加簡單的API。

Java方面呢,JDK 1.3之前僅僅有堵塞I/O,到1.4增加了NIO。

在JDK 1.5 update 10和Linux 2.6以上版本號碼。JDK使用epoll替換了select/poll。1.7增加了AIO。

2.四種I/O模型2.1 堵塞BIO

堵塞BIO是我們最常見的一種形式。就不具體說了。

2.2 偽非同步I/O

偽非同步I/O利用堵塞I/O的Acceptor+線程池實現的是偽非同步I/O。它僅僅是對同步堵塞I/O在系統資源方面使用方面做了“一小點”的最佳化(重用了線程)。可是 它沒法從根本上解決同步I/O導致的通訊線程堵塞問題

TCP/IP知識複習:當訊息接收方處理緩慢時,將不能及時從TCP緩衝區讀取資料,這將會導致發送方的TCP window size不斷變小直到為0。

此時兩方處於Keep-Alive狀態,發送方將不能再向TCP緩衝區寫入訊息。

假設使用的是同步堵塞I/O,write操作將無限期堵塞直到window size大於0或發生I/O異常。

2.3 非堵塞NIO

非堵塞NIO的特點是:

  • 1)全部資料都是用緩衝區(ByteBuffer)處理的;
  • 2)使用全雙工系統的Channel而不是輸入/輸出流,能更好地映射底層作業系統的API。
  • 3)多工器是基礎。

NIO提供了非堵塞的讀寫操作。相比於BIO的確是非同步。因此從這個角度我們能夠說NIO是非同步非堵塞的。

然而假設嚴格依照UNIX網路編程模型定義的話,NIO並不能算是非同步,由於當事件完畢時不是由系統觸發回呼函數,而是須要我們不斷輪詢

2.4 非同步AIO

AIO才是真正的非同步I/O:NIO僅僅是實現了讀寫操作的非堵塞。但它還是要靠輪詢而非事件通知(雖然前面說過JDK 1.5裡升級為epoll。但上層API還是輪詢沒有變化)。說它是非同步事實上就是想說它是非堵塞的。JDK 1.7 NIO 2中提供的AIO才是真正的非同步I/O。

3.Netty介紹3.1 為什麼選擇Netty

使用原生NIO開發的特點就是功能開發相對easy,但興許的可靠性方面的工作量非常大。須要我們自己處理如斷連重連、半包讀寫、網路擁堵等問題。而且,NIO中還可能有bug,如“臭名昭著”的Selector空輪詢導致CPU使用率100%(大學做大作業就碰到過這個問題。當時還納悶呢,原來是個bug啊)。

所以,要想自己高速開發出健壯可靠的高效能網路公用組件,還真不是件easy事!

Netty為我們提供了開箱即用的高效能、高可靠、安全可擴充的網路組建,同一時候還修複了NIO的一些bug,社區非常活躍。版本號碼升級快。相比而言,Netty真是個不錯的選擇!

3.2 核心API簡單介紹

Netty有下面幾個核心API:

  • ByteBuf:JDK的ByteBuffer僅僅有一個位置指標,每次讀寫都要flip(),clear()等。ByteBuf有readerIndex和writerIndex兩個指標。(0,readerIndex)是已讀資料。[readerIndex,writerIndex)是未讀的資料。[writerIndex,capacity)是可寫空間。

  • Channel:封裝了JDK Channel的操作,統一了介面。
  • EventLoop:負責輪詢事件並分發給相應Channel的線程。
4.協議解析設計4.1 TCP拆包和粘包

TCP是流協議,TCP底層並不瞭解上層業務資料的含義,它會依據TCP緩衝區的實際情況進行包的劃分,一個完整的包可能被TCP拆分成多個包發送。也可能與其它小包封裝成一個大的資料包發送,這就是所謂的拆包和粘包。

發生拆包的原因可能有:

  • 1)應用程式write寫入的資料大小大於Socket發送緩衝區大小;
  • 2)進行MSS大小的TCP分段;
  • 3)乙太網路幀的payload大於MTU進行IP分區。

經常使用的解決方案策略:

  • 1)訊息定長(FixedLengthFrameDecoder)。
  • 2)包尾加切割符,如斷行符號(DelimiterBasedFrameDecoder);
  • 3)將訊息分為訊息頭和訊息體。在訊息頭中包括訊息或訊息體的長度(LengthFieldPrepender和LengthFieldBasedFrameDecoder)。
4.2 還原序列化

我們能夠在自己定義Decoder和Encoder中實現序列化和還原序列化,如常見的Jackson,MsgPack。ProtoBuf等等。

5.高效能設計5.1 Reactor模型

Reactor模型主要由多工器(Acceptor)、事件分發器(Dispatcher)、事件處理器(Handler)三部分組成。

深入研究的話,Reactor模型能夠細分成三種:

  • 單線程:全部I/O操作都由一個線程完畢,即多工、事件分發和處理都是在一個Reactor線程上完畢的。由於全部I/O操作都不會堵塞。所以理論上是可能的。

    在一些小型應用情境下也的確能夠使用單執行緒模式。但對於高並發應用是不合適的。即便這個NIO線程將CPU跑滿也無法滿足海量訊息的編解碼和讀寫。此外這種模型在可靠性上也存在問題。由於一旦這個NIO線程進入死迴圈就會導致整個系統的不可用。

  • 多線程:一個專門的NIO線程(Acceptor線程)負責監聽和接收client的TCP串連請求。而讀寫由一個NIO線程池負責。每一個NIO能夠相應多個鏈路。但為了防止並發問題。每一個鏈路僅僅相應一個NIO線程。絕大多數情境下,多執行緒模式都能夠滿足效能需求了。但在處理百萬client串連,或須要對client進行比較耗時的安全認證時,單一Acceptor還是可能存在效能不足的問題。
  • 主從Reactor:Acceptor不再是一個單獨的線程,而是獨立的線程池,負責client的登入、握手和安全認證,一旦鏈路建立成功就將鏈路注冊到後端負責I/O讀寫的SubReactor線程池上。

Netty對這三種都支援。通過調整線程池的線程個數、是否共用線程池等參數在三種方式間方便的切換。一般的Netty最佳實務例如以下:

  • 建立兩個NioEventLoopGroup來隔離Acceptor和I/O線程。
  • 假設商務邏輯非常easy。就不要在Handler中啟動使用者線程,直接在I/O線程中完畢業務。
  • 假設商務邏輯複雜,有可能導致線程堵塞的磁碟、資料庫、網路等操作,則可將解碼後的訊息封裝成Task派發到業務線程池運行。

  • 不要在使用者線程中解碼,而要在I/O線程上的解碼Handler中完畢。
5.2 無鎖化

由於在Handler內的資料讀寫、協議解析經常要儲存一些狀態,所以為了避免資源競爭。Netty對Handler採用序列化設計。即一個I/O線程會對我們配置到Netty中的Handler鏈的運行“負責究竟”。

正是有了這種設計,我們就能夠放心的在Handler中儲存各種狀態。甚至使用ThreadLocal,全然無鎖化的設計。

Netty的Handler在這一點上是不是與Struts2中的Action有點像呢?

5.3 零拷貝

在Netty內部,ByteBuffer預設使用堆外記憶體(Direct Buffer)作為緩衝區。這就避免了傳統堆記憶體作緩衝區時的拷貝問題。使用傳統堆記憶體時進行Socket讀寫時,JVM會先將堆記憶體緩衝區中的資料複製到直接記憶體中,然後再寫入Socket。

此外。Netty也提供給開發人員一些工具實現零拷貝,這些工具都是我們能夠利用的,比如:

  • ByteBufHolder:由於不同的協議訊息體能夠包括不同的協議欄位和功能,使用者繼承ByteBufHolder介面後能夠按需封裝自己的實現。比如Netty內部已提供的MemcacheContent就是繼承自ByteBufHolder。
  • CompositeByteBuf:對外將多個ByteBuf“裝飾”成一個ByteBuf,但實際上未產生不論什麼資料拷貝。

  • DefaultFileRegion:提供了transferTo方法,將檔案內容直接發送到目標Channel,實現了檔案傳輸的零拷貝。
5.4 記憶體池

隨著JVM虛擬機器和JIT即時編譯技術的發展。對象的分配和回收成了一件非常輕量級的工作。可是對於緩衝區。特別是對於堆外直接記憶體,分配和回收卻仍然是一件耗時的操作。所以,Netty提供了記憶體池來實現緩衝區的重用機制。

這裡再簡單介紹一下Netty內部的記憶體管理機制。

首先,Netty會預先申請一大塊記憶體。在記憶體管理器中一般叫做Arena。

Netty的Arena由很多Chunk組成。而每一個Chunk又由一個或多個Page組成。Chunk通過二叉樹的形式組織Page,每一個葉子節點表示一個Page,而中間節點表示記憶體地區,節點自己記錄它在整個Arena中的位移地址。當地區被分配出去後,中間節點上的標記位會被標記,這樣就表示這個中間節點下面的全部節點都已被分配了。

6.可靠性設計6.1 心跳檢測

在淩晨等業務低穀期。假設發生網路閃斷、串連Hang住等問題時,由於沒有業務訊息,應用進程非常難發現。到了白天業務高峰期時,會發生大量的網路通訊失敗。導致應用進程一段時間內無法處理業務訊息。因此能夠採用心跳檢測機制。一旦發現網路故障則馬上關閉鏈路。並主動重連。

具體來看。心跳檢測機制一般的設計思路是:

1)當連續周期T沒有讀寫訊息,client主動發送Ping心跳訊息給服務端。
2)假設在下一周期T到來時沒有收到服務端的Pong心跳或業務訊息,則心跳失敗計數器加1。
3)每當client接收到服務端的Pong心跳或業務訊息,則心跳失敗計數器清零。當計數器達到N次,則關閉鏈路。間隔INTERVAL後發起重連操作(保證服務端有充足的時間釋放資源。所以不能失敗後馬上重連)。
4)同理,服務端也要用上面的方法檢測client(保證不管通訊哪一方出現網路故障,都能被及時檢測出來)。

6.2 記憶體保護

Netty依據ByteBuf的maxCapacity保護記憶體不會超過上限。

此外預設的TailHandler會負責自己主動釋放ByteBuf的緩衝區。

6.3 優雅停機

Netty利用JVM注冊的Shutdown Hook攔截到退出訊號量。然後運行退出操作:釋放各個模組的佔用資源、將緩衝區中剩餘的訊息處理完畢或者清空、將待重新整理的資料持久化磁碟或資料庫等。

6.安全性設計

(略)

7.擴充性設計7.1 靈活的TCP參數配置

在Netty中能夠非常方便地改動TCP的參數。比如緩衝區大小的參數SO_RCVBUF/SO_SNDBUF、關閉將大量小包最佳化成大包的Nagle演算法的參數SO_TCPNODELAY參數來避免對時延敏感應用的影響、以及Linux非強制中斷等。

- Netty協議棧不區分服務端和client,開發完畢後可同一時候支援。

- 可靠性設計:心跳機制。重連機制- 安全性設計:內網採取IP白名單進行安全過濾。外網採取更加嚴格的SSL/TSL安全傳輸。

- 擴充性設計:業務功能能夠在訊息頭中附加流水號等。利用Netty提供的attachment欄位擴充。

7.2 可定製的API

Netty中關鍵的類庫都提供了介面或抽象類別以及大量的工廠類供開發人員擴充,像Handler則是直接提供了ChannelPipeline實現了責任鏈模式。方便我們做隨意的組合和擴充。

用Netty開發中介軟體:網路編程基礎

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.