netty原始碼解解析(4.0)-7 執行緒模型-IO執行緒EventLoopGroup和NIO實現(二)
阿新 • • 發佈:2018-12-18
把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方法的呼叫,轉換規則如下:
processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) 方法會把NIO事件轉成對NioTask的方法呼叫:
控制執行緒執行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();