把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();