Netty是一款高效的NIO框架和工具,基于Java NIO實現,Java NIO的Selector給Reactor模式提供了基礎,netty結合Selector和Reactor模式實現自身高效的線程模型。本文先對Reactor模式的多線程和主從模型進行簡單介紹,而后結合netty中的幾個重要組件對netty線程模型和工作流程進行簡單分析。這里可以參看李林鋒的文章。
多線程Reactor
與單線程不同的地方,在于多線程Reactor模型將監聽連接和連接的I/O操作放到了不同的線程里面,看下面的示意圖:
它的主要特點如下:
- 有專門的Acceptor線程用于監聽、接收客戶端的連接請求
- 網絡I/O的讀寫操作則由另外的線程池負責,包含一個任務隊列和多個線程
- 一個NIO線程可以處理多個鏈路,但每個鏈路只對應一個線程
大部分情況下,一個線程負責監聽和處理所有的客戶端都能滿足需要,但在特殊場景下,如并發量過高,或者在連接接入的時候需要進行安全認證等問題,單個Acceptor線程不足以滿足性能,這時候就需要主從Reactor模型。
主從多線程Reactor
服務端用于接收連接的不再是一個單獨的NIO線程,而是一個獨立的線程池:
工作流程:
- 從主線程池中選擇一個Reactor線程作為Acceptor線程,綁定監聽端口,接收客戶端連接
- 接收到連接請求后,將其注冊到主線程其他線程上,由它們負責接入認證等工作
- 鏈路建立完成后,就鏈路從主線程池的多路復用器上摘除,重新注冊到Sub線程池的NIO線程上,負責后續IO操作
netty的線程模型不是一成不變的,通過對啟動參數的配置,netty可以支持多種Reactor線程模型,最常用的是多線程模型。
下面說一下netty中幾個重要的組件。
Selector
Selector是Java NIO提供的多路復用器,負責配合操作系統的select/epoll操作將就緒的IO事件分離出來,落地為SelectionKey,我們可以將SelectionKey看做Reactor模式中的資源
EventLoop/EventLoopGroup
EventLoopGroup其實就是一個EventLoop線程組,netty中通常有多個EventLoop同時工作,每個EventLoop維護著一個Selector實例(類似單線程Reactor工作)。如果沒有顯式指定,默認每個EvenLoopGroup中的線程數為可用的CPU內核數*2。
通常每個netty服務端有兩個EventLoopGroup。
一個用作Acceptor線程池,負責處理客戶端的連接請求,通常一個服務端口對應一個EventLoop線程,根據實際需要配置線程組的線程數量。Acceptor線程通過不斷輪詢Selector上的Accept事件,將accept的SocketChannel交給另外一個EventLoop線程組。
另一個EventLoopGroup會根據線程組的順序next一個可用的EventLoop將這個SocketChannel注冊到其維護的Selector上,并處理其后續的I/O的事件。
ChannelPipleline
每個SocketChannel都有一個Pipleline實例,而每個Pipleline中維護了一個ChannelHandler鏈表隊列。Pipleline和ChannelHandler的關系類似servlet和filter過濾器的作用。EventLoop從Selector中分離出就緒的channel以后,會將它傳遞的消息傳輸到Pipleline中,通過ChannelHandler處理鏈進行層層處理,用戶可以在Handler中添加自己的業務邏輯。
ChannelPipleline中本身維護著兩個不可見的HeadHandler和TailHandler,head靠近網絡層,tail靠近用戶。netty中有兩類事件類型,inbound和outbound。inbound可以理解為從網絡數據外部流向內部,如讀取消息;outbound為網絡數據從內部流向外部,如寫消息。Pipleline會根據事件的類型, 自上而下或自下而上調用事件相關聯的ChannelHandler對消息進行處理。如讀取消息的時候會依次執行HeadHandler、ChannelHandler1...ChannelHandlerN、TailHandler。
寫在最后
通過以上幾個組件的作用,應該可以對netty的工作流程有個大體的認識。其實筆者接觸netty是從Vert.x開始的,netty可能更偏向網絡傳輸這個層面,一些RPC框架底層傳輸用的也是netty。
netty中還有其他要點,比如粘包拆包、緩沖區這些問題筆者還沒有認真研究過,待有時間再研究源碼吧,感嘆時間真不夠用啊!