1. 程式人生 > >netty原始碼解解析(4.0)-7 執行緒模型-IO執行緒EventLoopGroup和NIO實現(二)

netty原始碼解解析(4.0)-7 執行緒模型-IO執行緒EventLoopGroup和NIO實現(二)

把NIO事件轉換成對channel unsafe的呼叫或NioTask的呼叫 processSelectedKeys()方法是處理NIO事件的入口: private void processSelectedKeys() { if (selectedKeys != null) { processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } } 這個方法會呼叫processSelectedKeysOptimized或processSelectedKeysPlain開真正的NIO事件處理,這個兩個方法的功能大致一樣,不同的是前者是後者的優化版,優化點就在於它每次不用呼叫selector#selectedKeys()就能得到觸發事件的SelectionKey。在processSelectedKeysOptimized中是通過遍歷selectedKeys得到SelectionKey: for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i]; selectedKeys.keys[i] = null; final Object a = k.attachment(); if (a instanceof AbstractNioChannel) { processSelectedKey(k, (AbstractNioChannel) a); } else { @SuppressWarnings("unchecked") NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task); } } 標紅的程式碼就是processSelectedKeysOptimized和processSelectedKeysPlain的不同之處。 processSelectedKey(SelectionKey k, AbstractNioChannel ch) 方法會把NIO轉換成Channel Unsafe方法的呼叫,轉換規則如下:
NIO事件 Channel Unsafe方法
異常 close
SelectionKey.OP_CONNECT finishConnect
SelectionKey.OP_WRITE forceFlush
SelectionKey.OP_READ, SelectionKey.OP_ACCEPT read
processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) 方法會把NIO事件轉成對NioTask的方法呼叫:
NIO事件 Channel Unsafe方法
所有正常的NIO事件 channelReady
異常 channelUnregistered
控制執行緒執行I/O操作和排隊任務的用時比例 在run方法中,通過ioRatio屬性值來控制事件NIO和executor任務的時間比例。可以呼叫setIoRatio(int ioRatio)方法設定ioRatio的值,它的取值範圍是[0, 100], 當它的值是100時: try { processSelectedKeys(); } finally { runAllTasks(); } 此時會先處理完所有的NIO事件再執行所有的executor任務,等於完全沒有用時控制。當它的值是[0, 100)時: final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); } finally { final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } 此時會以處理NIO事件的時間為基準計算執行exeuctor任務的期望時間,之所以叫期望時間,原因是runAllTasks並不能有效地控制自己的執行時間,它每執行64個任務才會檢查一次用時,如果這64個任務中有一個任務的執行時間過大,runAllTasks執行時間就會遠大於期望時間。只有所有的executor任務執行時間足夠短,runAllTasks才能較精確地控制自己的執行時間。為了能讓這個時間控制機制有效地發揮作用,提交給NioEventLoop的任務應該是一些簡單的任務,任務中尤其不能有導致執行緒阻塞的操作。 處理epoll selector cpu 100%的bug 在select方法中,如果呼叫selector.select(timeoutMillis)的呼叫次數大於SELECTOR_AUTO_REBUILD_THRESHOLD(它的值必須>0, 才有效),可以認為selector出現異常,此時會呼叫rebuildSelector方法重新建立selector。 SELECTOR_AUTO_REBUILD_THRESHOLD的值由-Dio.netty.selectorAutoRebuildThreshold決定,如果沒有設定這個屬性,SELECTOR_AUTO_REBUILD_THRESHOLD的預設值是512, 如這個值<0, SELECTOR_AUTO_REBUILD_THRESHOLD被設定成0。因此如果要SELECTOR_AUTO_REBUILD_THRESHOLD生效-Dio.netty.selectorAutoRebuildThreshold值必須>2或不設定這個屬性。 正常情況下,在一次select呼叫中selector.select(timeoutMillis)被呼叫的次數不會大於2次,一次是正常的由於NIO事件或超時導致,另一次是在run方中的selector.wakeup()導致。如果selector.select(timeoutMillis)呼叫次數大於2,很有可能觸發了JDK epoll selector cpu 100%的bug, NioEventLoop解決這個問題的辦法是重新建立selector。 rebuildSelector方法是重新建立selector的入口,它呼叫rebuildSelector0方法執行真正的重建selector的操作,重建步驟如下: 1. 儲存舊的selector final Selector oldSelector = selector; 2. 呼叫openSelector方法建立新的selector newSelectorTuple = openSelector(); 3. 把舊selector上註冊的Channel轉移到新的selector上 for (SelectionKey key: oldSelector.keys()) { Object a = key.attachment(); int interestOps = key.interestOps(); key.cancel(); SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a); } 4. 關閉舊的selector oldSelector.close();