Netty源碼分析NioEventLoop執(zhí)行select操作入口
分析完了selector的創(chuàng)建和優(yōu)化的過程, 這一小節(jié)分析select相關(guān)操作
select操作的入口
NioEventLoop的run方法
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
//輪詢io事件(1)
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
//默認(rèn)是50
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
//記錄下開始時(shí)間
final long ioStartTime = System.nanoTime();
try {
//處理輪詢到的key(2)
processSelectedKeys();
} finally {
//計(jì)算耗時(shí)
final long ioTime = System.nanoTime() - ioStartTime;
//執(zhí)行task(3)
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
//代碼省略
}
}代碼比較長, 其實(shí)主要分為三部分:
1. 輪詢io事件
2. 處理輪詢到的key
3. 執(zhí)行task
這一小節(jié), 主要剖析第一部分
輪詢io事件
首先switch塊中默認(rèn)會(huì)走到SelectStrategy.SELECT中, 執(zhí)行select(wakenUp.getAndSet(false))方法
參數(shù)wakenUp.getAndSet(false)代表當(dāng)前select操作是未喚醒狀態(tài)
進(jìn)入到select(wakenUp.getAndSet(false))方法中
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
//當(dāng)前系統(tǒng)的納秒數(shù)
long currentTimeNanos = System.nanoTime();
//截止時(shí)間=當(dāng)前時(shí)間+隊(duì)列第一個(gè)任務(wù)剩余執(zhí)行時(shí)間
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
//阻塞時(shí)間(毫秒)=(截止時(shí)間-當(dāng)前時(shí)間+0.5毫秒)
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
//進(jìn)行阻塞式的select操作
int selectedKeys = selector.select(timeoutMillis);
//輪詢次數(shù)
selectCnt ++;
//如果輪詢到一個(gè)事件(selectedKeys != 0), 或者當(dāng)前select操作需要喚醒(oldWakenUp),
//或者在執(zhí)行select操作時(shí)已經(jīng)被外部線程喚醒(wakenUp.get()),
//或者任務(wù)隊(duì)列已經(jīng)有任務(wù)(hasTask), 或者定時(shí)任務(wù)隊(duì)列中有任務(wù)(hasScheduledTasks())
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
//省略
//記錄下當(dāng)前時(shí)間
long time = System.nanoTime();
//當(dāng)前時(shí)間-開始時(shí)間>=超時(shí)時(shí)間(條件成立, 執(zhí)行過一次select操作, 條件不成立, 有可能發(fā)生空輪詢)
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
//代表已經(jīng)進(jìn)行了一次阻塞式select操作, 操作次數(shù)重置為1
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
//省略日志代碼
//如果空輪詢的次數(shù)大于一個(gè)閾值(512), 解決空輪詢的bug
rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
//代碼省略
} catch (CancelledKeyException e) {
//省略
}
}首先通過 long currentTimeNanos = System.nanoTime() 獲取系統(tǒng)的納秒數(shù)
繼續(xù)往下看:
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
delayNanos(currentTimeNanos)代表距定時(shí)任務(wù)中第一個(gè)任務(wù)剩余多長時(shí)間, 這個(gè)時(shí)間+當(dāng)前時(shí)間代表這次操作不能超過的時(shí)間, 因?yàn)槌^之后定時(shí)任務(wù)不能嚴(yán)格按照預(yù)定時(shí)間執(zhí)行, 其中定時(shí)任務(wù)隊(duì)列是已經(jīng)按照?qǐng)?zhí)行時(shí)間有小到大排列好的隊(duì)列, 所以第一個(gè)任務(wù)則是最近需要執(zhí)行的任務(wù), selectDeadLineNanos就代表了當(dāng)前操作不能超過的時(shí)間
然后就進(jìn)入到了無限for循環(huán)
for循環(huán)中我們關(guān)注:
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L
selectDeadLineNanos - currentTimeNanos+500000L 代表截止時(shí)間-當(dāng)前時(shí)間+0.5毫秒的調(diào)整時(shí)間, 除以1000000表示將計(jì)算的時(shí)間轉(zhuǎn)化為毫秒數(shù)
最后算出的時(shí)間就是selector操作的阻塞時(shí)間, 并賦值到局部變量的timeoutMillis中
后面有個(gè)判斷 if(imeoutMillis<0) , 代表當(dāng)前時(shí)間已經(jīng)超過了最后截止時(shí)間+0.5毫秒, selectCnt == 0 代表沒有進(jìn)行select操作, 滿足這兩個(gè)條件, 則執(zhí)行selectNow()之后, 將selectCnt賦值為1之后跳出循環(huán)
如果沒超過截止時(shí)間, 就進(jìn)行了 if(hasTasks() && wakenUp.compareAndSet(false, true)) 判斷
這里我們關(guān)注hasTasks()方法, 這里是判斷當(dāng)前NioEventLoop所綁定的taskQueue是否有任務(wù), 如果有任務(wù), 則執(zhí)行selectNow()之后, 將selectCnt賦值為1之后跳出循環(huán)(跳出循環(huán)之后去執(zhí)行任務(wù)隊(duì)列中的任務(wù))
hasTasks()方法可以自己跟一下, 非常簡(jiǎn)單
如果沒有滿足上述條件, 就會(huì)執(zhí)行 int selectedKeys = selector.select(timeoutMillis) 進(jìn)行阻塞式輪詢, 并且自增輪詢次數(shù), 而后會(huì)進(jìn)行如下判斷:
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}selectedKeys != 0代表已經(jīng)有輪詢到的事件, oldWakenUp代表當(dāng)前select操作是否需要喚醒, wakenUp.get()說明已經(jīng)被外部線程喚醒, hasTasks()代表任務(wù)隊(duì)列是否有任務(wù), hasScheduledTasks()代表定時(shí)任務(wù)隊(duì)列是否任務(wù), 滿足條件之一, 就跳出循環(huán)
long time = System.nanoTime() 記錄了當(dāng)前的時(shí)間, 之后有個(gè)判斷:
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos)
這里的意思是當(dāng)前時(shí)間-阻塞時(shí)間>方法開始執(zhí)行的時(shí)間, 這里說明已經(jīng)完整的執(zhí)行完成了一個(gè)阻塞的select()操作, 將selectCnt設(shè)置成1
如果此條件不成立, 說明沒有完整執(zhí)行select()操作, 可能觸發(fā)了一次空輪詢, 根據(jù)前一個(gè)selectCnt++這步我們知道, 每觸發(fā)一次空輪詢selectCnt都會(huì)自增
之后會(huì)進(jìn)入第二個(gè)判斷
SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD
其中SELECTOR_AUTO_REBUILD_THRESHOLD默認(rèn)是512, 這個(gè)判斷意思就是空輪詢的次數(shù)如果超過512次, 則會(huì)認(rèn)為是發(fā)生了epoll bug, 這樣會(huì)通過rebuildSelector()方法重新構(gòu)建selector, 然后將重新構(gòu)建的selector賦值到局部變量selector, 執(zhí)行一次selectNow(), 將selectCnt初始化1, 跳出循環(huán)
rebuildSelector()方法
rebuildSelector()方法中, 看netty是如何解決epoll bug的
public void rebuildSelector() {
//是否是由其他線程發(fā)起的
if (!inEventLoop()) {
//如果是其他線程發(fā)起的, 將rebuildSelector()封裝成任務(wù)隊(duì)列, 由NioEventLoop進(jìn)行調(diào)用
execute(new Runnable() {
@Override
public void run() {
rebuildSelector();
}
});
return;
}
final Selector oldSelector = selector;
final Selector newSelector;
if (oldSelector == null) {
return;
}
try {
//重新創(chuàng)建一個(gè)select
newSelector = openSelector();
} catch (Exception e) {
logger.warn("Failed to create a new Selector.", e);
return;
}
int nChannels = 0;
for (;;) {
try {
//拿到舊select中所有的key
for (SelectionKey key: oldSelector.keys()) {
Object a = key.attachment();
try {
Object a = key.attachment();
//代碼省略
//獲取key注冊(cè)的事件
int interestOps = key.interestOps();
//將key注冊(cè)的事件取消
key.cancel();
//注冊(cè)到重新創(chuàng)建的新的selector中
SelectionKey newKey = key.channel().register(newSelector, interestOps, a);
//如果channel是NioChannel
if (a instanceof AbstractNioChannel) {
//重新賦值
((AbstractNioChannel) a).selectionKey = newKey;
}
nChannels ++;
} catch (Exception e) {
//代碼省略
}
}
} catch (ConcurrentModificationException e) {
continue;
}
break;
}
selector = newSelector;
//代碼省略
}首先會(huì)判斷是不是當(dāng)前NioEventLoop線程執(zhí)行的, 如果不是, 則將構(gòu)建方法封裝成task由當(dāng)前NioEventLoop執(zhí)行
final Selector oldSelector = selector 表示拿到舊的selector
然后通過 newSelector = openSelector() 創(chuàng)建新的selector
通過for循環(huán)遍歷所有注冊(cè)在selector中的key
Object a = key.attachment() 是獲取channel, 第一章講過, 在注冊(cè)時(shí), 將自身作為屬性綁定在key上
for循環(huán)體中, 通過 int interestOps = key.interestOps() 獲取其注冊(cè)的事件
key.cancel()將注冊(cè)的事件進(jìn)行取消
SelectionKey newKey = key.channel().register(newSelector, interestOps, a)
將channel以及注冊(cè)的事件注冊(cè)在新的selector中
if (a instanceof AbstractNioChannel) 判斷是不是NioChannel
如果是NioChannel, 則通過 ((AbstractNioChannel) a).selectionKey = newKey 將自身的屬性selectionKey賦值為新返回的key
selector = newSelector 將自身NioEventLoop屬性selector賦值為新創(chuàng)建的newSelector
至此, 就是netty解決epoll bug的步驟, 其實(shí)就是創(chuàng)建一個(gè)新的selector, 將舊selector中注冊(cè)的channel和事件重新注冊(cè)到新的selector中, 然后將自身selector屬性替換成新創(chuàng)建的selector
以上就是Netty源碼分析NioEventLoop執(zhí)行select操作入口的詳細(xì)內(nèi)容,更多關(guān)于Netty分布式NioEventLoop selector操作的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Netty分布式NioSocketChannel注冊(cè)到selector方法解析
- Netty客戶端接入流程N(yùn)ioSocketChannel創(chuàng)建解析
- Netty分布式NioEventLoop任務(wù)隊(duì)列執(zhí)行源碼分析
- Netty分布式NioEventLoop優(yōu)化selector源碼解析
- Netty源碼分析NioEventLoop線程的啟動(dòng)
- Netty源碼分析NioEventLoop初始化線程選擇器創(chuàng)建
- Netty源碼解析NioEventLoop創(chuàng)建的構(gòu)造方法
- Netty實(shí)戰(zhàn)源碼解析NIO編程
相關(guān)文章
Spring Cache監(jiān)控配置與使用規(guī)范的建議
這篇文章主要介紹了Spring Cache監(jiān)控配置與使用規(guī)范的建議,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
java實(shí)現(xiàn)短信驗(yàn)證碼5分鐘有效時(shí)間
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)短信驗(yàn)證碼5分鐘有效時(shí)間,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
mybatis?plus樂觀鎖及實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了mybatis?plus樂觀鎖及實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
spring?boot?validation參數(shù)校驗(yàn)與分組嵌套各種類型及使用小結(jié)
參數(shù)校驗(yàn)基本上是controller必做的事情,畢竟前端傳過來的一切都不可信,validation可以簡(jiǎn)化這一操作,這篇文章主要介紹了spring?boot?validation參數(shù)校驗(yàn)分組嵌套各種類型及使用小結(jié),需要的朋友可以參考下2023-09-09
Mybatis?XML配置文件實(shí)現(xiàn)增刪改查的示例代碼
本文主要介紹了Mybatis?XML配置文件實(shí)現(xiàn)增刪改查的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
Mybatis-Plus實(shí)現(xiàn)公共字段自動(dòng)賦值的方法
這篇文章主要介紹了Mybatis-Plus實(shí)現(xiàn)公共字段自動(dòng)賦值的方法,涉及到通用字段自動(dòng)填充的最佳實(shí)踐總結(jié),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07

