Tomcat處理請求的線程模型詳解
一、前言
JAVA后端項目,運行在容器tomcat中,由于現(xiàn)在springboot的內(nèi)置tomcat容器,其默認配置屏蔽了很多對tomcat的認知,但是對tomcat的學習和認識是比較重要的,所以專門查資料加深了理解,本文主要討論在springboot集成下的tomcat9的請求過程,線程模型為NIO。
二、tomcat結(jié)構(gòu)
找了張結(jié)構(gòu)圖,每個模塊的意思和作用就不詳解了,可以搜其他文章
三、探討tomcat是如何處理請求
自己畫了一個connector的結(jié)構(gòu)
1、初始化
在springboot啟動后,org.springframework.context.support.AbstractApplicationContext#finishRefresh
,這里進去調(diào)用org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.start()
方法啟動TomcatWebServer
,初始化tomcat。
通過這樣的調(diào)用鏈到達org.apache.tomcat.util.net.NioEndpoint#startInternal()
,進行初始化Endpoint
中的Acceptor
和Poller
,這兩者都實現(xiàn)了Runnable接口,初始化后就通過線程start啟動了。
2、如何處理客戶端請求
Acceptor
: 接收器,作用是接受scoket網(wǎng)絡請求,并調(diào)用setSocketOptions()
封裝成為NioSocketWrapper
,并注冊到Poller的events中。注意查看run方法org.apache.tomcat.util.net.Acceptor#run
@Override public void run() { int errorDelay = 0; try { // Loop until we receive a shutdown command while (!stopCalled) { // Loop if endpoint is paused while (endpoint.isPaused() && !stopCalled) { state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } if (stopCalled) { break; } state = AcceptorState.RUNNING; try { //if we have reached max connections, wait endpoint.countUpOrAwaitConnection(); // Endpoint might have been paused while waiting for latch // If that is the case, don't accept new connections if (endpoint.isPaused()) { continue; } U socket = null; try { // 等待下一個請求進來 socket = endpoint.serverSocketAccept(); } catch (Exception ioe) { // We didn't get a socket endpoint.countDownConnection(); if (endpoint.isRunning()) { // Introduce delay if necessary errorDelay = handleExceptionWithDelay(errorDelay); // re-throw throw ioe; } else { break; } } // Successful accept, reset the error delay errorDelay = 0; // Configure the socket if (!stopCalled && !endpoint.isPaused()) { // 注冊socket到Poller,生成PollerEvent事件 if (!endpoint.setSocketOptions(socket)) { endpoint.closeSocket(socket); } } else { endpoint.destroySocket(socket); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); String msg = sm.getString("endpoint.accept.fail"); // APR specific. // Could push this down but not sure it is worth the trouble. if (t instanceof Error) { Error e = (Error) t; if (e.getError() == 233) { // Not an error on HP-UX so log as a warning // so it can be filtered out on that platform // See bug 50273 log.warn(msg, t); } else { log.error(msg, t); } } else { log.error(msg, t); } } } } finally { stopLatch.countDown(); } state = AcceptorState.ENDED; }
Poller
:輪詢器,輪詢是否有事件達到,有請求事件到達后,以NIO的處理方式,查詢Selector取出所有請求,遍歷每個請求的需求,分配給Executor線程池執(zhí)行。查看org.apache.tomcat.util.net.NioEndpoint.Poller#run()
public void run() { // Loop until destroy() is called while (true) { boolean hasEvents = false; try { if (!close) { hasEvents = events(); if (wakeupCounter.getAndSet(-1) > 0) { // If we are here, means we have other stuff to do // Do a non blocking select keyCount = selector.selectNow(); } else { keyCount = selector.select(selectorTimeout); } wakeupCounter.set(0); } if (close) { events(); timeout(0, false); try { selector.close(); } catch (IOException ioe) { log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe); } break; } // Either we timed out or we woke up, process events first if (keyCount == 0) { hasEvents = (hasEvents | events()); } } catch (Throwable x) { ExceptionUtils.handleThrowable(x); log.error(sm.getString("endpoint.nio.selectorLoopError"), x); continue; } //查詢selector取出所有請求 Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; // Walk through the collection of ready keys and dispatch // any active event. while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); iterator.remove(); NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment(); //處理請求key if (socketWrapper != null) { processKey(sk, socketWrapper); } } // Process timeouts timeout(keyCount,hasEvents); } getStopLatch().countDown(); }
請求過程大致如下圖:
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
PowerJob的HashedWheelTimer工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的HashedWheelTimer工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01利用Spring Data MongoDB持久化文檔數(shù)據(jù)的方法教程
這篇文章主要給大家介紹了關(guān)于利用Spring Data MongoDB持久化文檔數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08