NioEventLoop是什麼?
如圖,NioEventLoop是worker threads中的thread,也就是處理請求的線程,屬於NioEventLoopGroup,那麼多個線程每次選擇哪個線程來處理請求呢?
1.1 Netty給Channel分配Nio Event Loop的規則
看下圖,EventLoopGroup是線程組,每個EventLoop是一個線程,那麼線程處理請求是怎麼分配的呢?我們看一下源碼
1.1.1 我們通過 MultithreadEventLoopGroup.register方法定位到next()方法。
該方法負責NioEventLoop。
1.1.2 進入super.next()方法,我們看到MultithreadEventExecutorGroup的屬性chooser
點擊chooser,我們找到它賦值的地方在構造方法裏面,chooser的正是通過DefaultEventExecutorChooserFactory的newChooser獲取的,
進入newChooser方法,根據傳入的executors長度是否2的指數返回不同的實現類,該類實現了一個策略模式。
注意到這裏isPowerOfTwo只有一行代碼,為什麼還有單獨寫一個方法呢,雖然只有一行代碼但是不容易看懂,通過方法名我們一下就看懂了,這是一種可讀性更好的實現方式
1.1.3 我們繼續看chooser.next()方法,
有兩個實現類GenericEventExecutorChooser和PowerOfTwoEventExecutorChooser,都是在DefaultEventExecutorChooserFactory中。
看兩個實現類實現的有何不同
兩者都是通過idx來獲取索引值的,idx是遞增的,這種模式類似輪詢的方式。但是idx一直遞增下去的話,可能會整數類型的越界,
為了防止越界兩者做了不同的處理
- GenericEventExecutorChooser,idx對executors.length取模,並取絕對值
- PowerOfTwoEventExecutorChooser, idx對executors.length - 1做與運算確定位置,這種方式類似於HashMap中對桶位置運算的處理,與運算比數學運算快很多,但是隻能在executors.length是2的指數的時候才可以發揮作用。
例如: executors.length = 2^4, 其二進制為10000,executors.length-1=01111。任何值與1111與運算都不會超過executors.length,同時任何小於executors.length的值和1111與運算都是其本身。這是這個特性才能實現這樣的優化。
這裏PowerOfTwoEventExecutorChooser對next方法做了優化,所以在長度是2的指數的時候我們可以更快的處理
1.1.4 總結
- 事件處理EventLoop的分配方式是輪詢的,通過記錄調用次數對EventLoop的數量取餘來確定,這種方式能比較好的保證負載均衡
- 同時根據EventLoop數量是否2的指數做了優化
1.2 多路複用怎麼跨平台的
在不同的系統平台上,EventLoop如何跨平台呢?我們看一下源碼
1.2.1 我 們看一下new NioEventLoopGroup()
點進這個方法,繼續進入,我們找到下面這個方法,看Selector來自SelectorProvider.provider
進入SelectorProvider.provider,這裏的run方法裏面有三個分支。
- 第一個是從配置的類加載provider
- 第二個是從系統加載provider
- 如果前兩個都沒有,看第三個sun.nio.ch.DefaultSelectorProvider.create,這個方法是jre虛擬機的方法,不同平台的jdk版本上,該方法不一樣,
Selector通過這種方式來實現跨平台
下圖是openjdk不同平台是Selector實現:
1.2.2 總結
我們可以看到跨平台是通過不同平台的jvm實現的,不同jvm的平台有不同的實現類,在不同的平台會調用jvm的實現類。