天天看點

Netty5源碼分析--4.總結

JAVA NIO 複習

請先參考我之前的博文JAVA學習筆記–3.Network IO的 NIO(NonBlocking IO) SOCKET 章節。這裡主要講下JAVA NIO其中幾個比較被忽略的細節,不求全,歡迎補充。

API

Select

當調用

ServerSocketChannel.accept();

時,如果該channel處于非阻塞狀态而且沒有等 待(pending)的連接配接,那麼該方法會傳回null;否則該方法會阻塞直到連接配接可用或者發生I/O錯誤。此時實際上Client發送了connect 請求并且服務端是處于non-blocking模式下,那麼這個accept()會傳回一個不為null的channel。

當調用

SocketChannel.connect()

時,如果該channel出于non-blocking模式,隻有在本地連接配接下才可能立即完成連接配接,并傳回true;在其他情況下,該方法傳回false,并且必須在後面調用

SocketChannel.finishConnect

方法。如果

SocketChannel.finishConnect

的執行結果為true,才意味着連接配接真正建立。

SocketChannel.write()

方法内部不涉及緩沖,它直接把資料寫到socket的send buffer中。

每次疊代selector.keys()完時,記得remove該SelectionKey,防止發生CPU100%的問題。

通常不該register OP_WRITE,一般來說socket 緩沖區總是可寫的,僅在write()方法傳回0時或者未完全寫完資料才需要register OP_WRITE操作。當資料寫完的時候,需要deregister OP_WRITE。 socket空閑時,即為可寫.有資料來時,可讀。對于nio的寫事件,隻在發送資料時,如果因為通道的阻塞,暫時不能全部發送,才注冊寫事件

key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);

。等通道可寫時,再寫入。同時判斷是否寫完,如果寫完,就取消寫事件即可

key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);

空 閑狀态下,所有的通道都是可寫的,如果你給每個通道注冊了寫事件,那麼肯定是死循環了,導緻發生CPU100%的問題。詳見Netty的 NioSocketChannel的父類方法setOpWrite()和clearOpWrite()方法。這個在iteye裡面的文章Java nio網絡程式設計的一點心得也有說明。

在register OP_CONNECT後,并且觸發OP_CONNECT後,需要再deregister OP_CONNECT。詳見Netty的NioEventLoop.processSelectedKey()方法後半部分。

另外,需要注意到Selector.wakeup()是一個昂貴的操作,一般需要減少是用。詳見NioEventLoop.run()方法内的處理方式。

還有各種各樣的坑,實在太多了。詳見Java NIO架構的實作技巧和陷阱

NIO操作在Netty裡面的展現

雖然筆者閱讀完幾個重要的互動過程,但是代碼細節較多,無法展現NETTY對NIO的使用。于是畫了兩幅圖,與讀者共享。

事先說明下,下圖圖檔上的标記2表示是一個新的EventLoop線程,非main線程。

另外,雖然Netty中大量使用了ChannelFuture異步,但是換種方式了解的話,可以了解為同步執行。

服務端啟動

Netty5源碼分析--4.總結

用戶端啟動

Netty5源碼分析--4.總結

服務端處理請求

這裡有個細節,就是boss線程注冊了OP_ACCEPT線程,然後當接收到用戶端的請求時,會使用work線程池裡面的線程來處理用戶端請求。代碼細節在

NioServerSocketChannel.doReadMessages

的tag1.1.2處以及

child.unsafe().register(child.newPromise());

處。

Netty整體架構

子產品結構

從Netty的Maven倉庫可以看

到,大緻分為如下子產品:

  • netty-transport:架構核心類Channel,ChannelPipeline,ChannelHandler都是在這個子產品裡面定義的,提供了對不同協定支援架構。
  • netty-buffer:對ByteBuffer的抽象,可以了解為網絡中的資料。
  • netty-codec:對不同格式的資料進行編碼,解碼。
  • netty-handler:對資料進行處理的handler
  • netty-common:公共處理類。

綜上,可以看出,首先對架構機制和傳輸的資料進行了抽象,然後又對資料如何在架構中傳輸進行了抽象。

他山之石

下面的介紹的思想比較零散,不成系統,主要是閱讀代碼過程中産生的碰撞,供讀者參考。

好的設計

Netty 同樣也采用了多個Selector并存的結構,主要原因是單個Selector的結構在支撐大量連接配接事件的管理和觸發已經遇到瓶頸。

Bootstrap.channel()方法通過傳遞class對象,然後通過反射來執行個體化具體的Channel執行個體,一定程度上避免了寫死類名字元串導緻未來版本變動時發生錯誤的可能性。

架構必備的類,大家還可以看下common工程,裡面真是一個寶藏。

  • InternalLogger

    用來避免對第三方日志架構的依賴,如slf,log4j等等。
  • ChannelOption

    靈活地使用泛型機制,避免使用者設定參數發生低級錯誤。
  • SystemPropertyUtil

    提供對xt屬性的通路方法
  • DefaultThreadFactory

    提供自定義線程工程類,友善定位問題。
  • PlatformDependent

    如果需要支援不同平台的話,可以把平台相關的操作都放在一起進行管理。

列印日志時,可以參考這樣來拼接參數,

String.format("nThreads: %d (expected: > 0)", nThreads)

@Sharable表示該類是無狀态的,僅僅起“文檔”标記作用。

在子接口裡僅僅把父接口方法傳回值覆寫了,然後什麼都不做。這樣一定程度上避免了強制轉型的尴尬。

把前一個future作為下一個調用方法的參數,這樣可以異步執行。然後後面的邏輯可以先判斷future結果後再進行處理,進而提升性能。

中立

addLast0 等以0為結尾的方法表示私有的含義。

有些方法的getter、setter字首省略,有點類似jQuery裡面命名風格。不過不太建議在自己的産品中使用這種風格,盡量使用和和産品内的一樣的命名風格。

疑惑

心中存疑,請大家不吝賜教。

在NioEventLoop.run()方法中,好像每次用完SelectionKey沒有remove 掉它,可能和SelectedSelectionKeySet實作機制有關。但是沒直接看出來具體之間的聯系,

為什麼boss也要是個線程池?目前來看,服務端2個線程保持不變,main線程出于wait狀态,boss線程池其中的一個線程進行接收用戶端連接配接請求,然後把請求轉發給worker線程池。

javaChannel().register(eventLoop().selector, 0, this);

,為什麼ops參數預設是0?

socketChannel.register() and key.interestOps()

還是有點不太明白,估計我的思路鑽進死胡同了。

瑕疵

b.bind(port)這個裡面的内容非常複雜,不僅僅是bind一個port那麼簡單。是以該方法命名不是很好。

父接口依賴子接口,也不是很好。

DefaultChannelPipeline.addLast 這個方法太坑了,并不是把handler加到最後一個上面,而是加到tail前一個。

無一類外的是,繼承體系相對複雜。父類,子類的命名通常不能展現出誰是父類,誰是子類,除了一個Abstract能夠直接看出來。

注意事項

用戶端單執行個體,防止消耗過多線程。這個在http://hellojava.info/中多次提到。

其他

目前網上的NIO例子都基本都不太靠譜,BUG多多。建議可以參考下《JAVA 分布式JAVA應用 基礎與實踐》。

JAVA NIO不一定就比OIO快,重點是更加Scalable。

架構幫趟坑,不要輕易制造輪子,除非現有輪子很差勁。JAVA NIO裡面的坑太多了,Netty裡面的大量的issue充分說明了問題。技術某種程度上不是最重要的,如果大家努力程度差不多的話,技術上不會差到哪裡去。開源産品主要是生态圈的建設。

接收對端資料時,資料通過netty從網絡中讀取,進行其他各種處理,然後供應用程式使用。發送本地資料時,應用程式首先完成資料處理, 然後通過netty進行各種處理,最終把資料發出。但是為什麼要區分inbound,outbound,或者說提供了head->tail以及 tail->head的周遊?

當我們跳出裡面的細節時,考慮一下,如果你是作者的話,會如何考慮。整體的一個算法 。不同的通訊模型,nio,sun jdk bug, option(預設和使用者設定),異步future、executor,pipeline、context、handler, 設計模式 。

我想象中操作應該是這樣的,handler鍊管理不需要走pipeline,event鍊也不需要走pipiline,僅僅對資料的發送,接收和操 作才走pipeline。事件機制應該起一些增強型、輔助型作用,不應該影響到核心流程的執行或者起到什麼關鍵作用,主要是應該給第三方擴充用。而 netty中,所有的一切都要走pipelne。

太多異步,怎麼測試?

招式vs心法。招式,相當于api;心法,相當于api工作原理,利與弊。還是要了解底層,否則還是可能了解不清楚。cpu,記憶體,io,網絡,而最終浮于招式。

看完了麼?知道了Netty是什麼,内部大概是怎麼運作的,但是有些細節還不知道為什麼。真的就了解精髓了麼?NIO的坑.. 底層OS的坑.. Netty的精髓是在于對各種細節的處理,坑的處理,對性能的處理,而不是僅僅一個XX模式運用。JAVA NIO細節和坑實在太多,估計再給我一周的時間,也研究不完。有點小沮喪。

源于:http://my.oschina.net/geecoodeer/blog/193941