標籤:ack group 指標 site java程式 coder write 重新整理 讀取資料
用Netty開發中介軟體:網路編程基礎
《Netty權威指南》在網上的評價不是非常高,尤其是第一版,第二版能稍好些?入手後高速翻看了大半本,不免還是想對《Netty權威指南(第二版)》吐槽一下:
不管如何,假設你是網路通訊或後台中介軟體的入門者。尤其是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模型能夠細分成三種:
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開發中介軟體:網路編程基礎