前言
Spring Boot 版本 2.1.7.RELEASE
本文大致跟蹤了一遍Tomcat的啟動流程和http請求處理流程。
請邊debug源代碼邊看本文,因為很多變量細節在debug時才能清晰的觀察到,而本文是無法全部涵蓋所有細節的。
文中如有錯誤遺漏,請留言指正。
相關文章
正文
1 第一部分的調用棧如下
createWebServer:180, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onRefresh:153, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:543, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:743, SpringApplication (org.springframework.boot)
refreshContext:390, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1214, SpringApplication (org.springframework.boot)
run:1203, SpringApplication (org.springframework.boot)
main:13, XXXXXApplication (com.xxx.xxx) //自定義的函數入口
這一部分主要工作是:
- 實例化
org.apache.catalina.startup.Tomcat
- 將
Tomcat
實例作為構造函數的入參,實例化org.springframework.boot.web.embedded.tomcat.TomcatWebServer
- 在
TomcatWebServer
的構造函數執行的末尾處,調用Tomcat.start()方法
啟動Tomcat
大致流程如下:
1.1
首先在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer()
方法中,
ServletWebServerFactory factory = getWebServerFactory();
語句通過beanFactory構造出TomcatWebServer
的factory實例。
1.2
然后this.webServer = factory.getWebServer(getSelfInitializer());
語句通過TomcatWebServer
的factory構造TomcatWebServer
實例。Tomcat
的實例化及啟動流程就在此函數中完成。
代碼實現在org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
類的getWebServer()
方法中。
1.2.1
getWebServer()
方法的前半部分都是在實例化Tomcat
類。其中包括實例化Connector
,StandardServer
,StandardService
,StandardEngine
等類,最終組合成Tomcat
類。
1.2.2
getWebServer()
方法的最后一條語句getTomcatWebServer(tomcat);
,將Tomcat
實例作為構造函數的參數,構造了TomcatWebServer
。而在TomcatWebServer
的構造函數的末尾處,initialize();
方法會啟動Tomcat
。
initialize();
方法主要做了兩件事
-
this.tomcat.start();
啟動tomcat -
startDaemonAwaitThread();
啟動一條非daemon線程來執行TomcatWebServer.this.tomcat.getServer().await();
用途如源代碼中的注釋所寫。// Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown
先來看一下this.tomcat.start();
tomcat.start();
內部會初始化并啟動Server
,Service
,Engine
,Connector
,Http11NioProtocol
,NioEndpoint
等
這個過程中會多次發布Lifecycle
中的事件,但默認只有StandardContext
的lifecycleListeners
中有事件監聽者。
StandardContext.startInternal()
方法中會調用initializers
的onStartup()
方法,其中包括了ServletWebServerApplicationContext.selfInitialize()
方法。
1.2.3 selfInitialize()
方法
1.2.3.1
selfInitialize()
方法首先將ServletWebServerApplicationContext
以attribute的方式注入到當前的ServletContext
中。
ServletWebServerApplicationContext
功能齊全,可以獲取bean,可以獲取environment,進而獲取property。
所以一種通過靜態方法獲取ApplicationContext
的方式是
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
ServletContext servletContext = servletRequestAttributes.getRequest().getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
}
注
有時候會需要在自定義的Filter中注入Spring維護的bean。如果是使用當前這種通過springboot的方式啟動服務,直接通過注解注入是沒有問題的。
但如果是通過tomcat方式啟動服務,Filter初始化的時候是沒有在Spring的上下文中的。這時候是無法通過注解注入Spring bean依賴的,可以嘗試使用DelegatingFilterProxy
這個類來實現。
1.2.3.2
接下來會調動ServletContextInitializerBeans
類的構造函數。
在這個構造函數中加載了Servlet
和Filter
。
加載的方式有兩種
a) addServletContextInitializerBeans
方法
從beanFactory中加載ServletContextInitializer
接口的類。FilterRegistrationBean
等類就實現了這一接口,這種filter的聲明方式會在這個階段被加載進來。
b) addAdaptableBeans()
方法
從beanFactory中加載Servlet
和Filter
接口的類。
代碼如下
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
代碼中的adapter會將Servlet
和Filter
封裝成RegistrationBean
,這樣就與a方式相同了。
2 第二部分的調用棧如下
這一部分主要工作是:調用TomcatWebServer.start()方法啟動TomcatWebServer
start:195, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
startWebServer:297, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
finishRefresh:163, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:552, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:743, SpringApplication (org.springframework.boot)
refreshContext:390, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1214, SpringApplication (org.springframework.boot)
run:1203, SpringApplication (org.springframework.boot)
main:13, XXXXXApplication (com.xxx.xxx) //自定義的函數入口
大致流程如下:
2.1 addPreviouslyRemovedConnectors();
在 1.2.2 的TomcatWebServer.initialize()
方法中有這樣一段代碼:
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
這段代碼的意思是在StandardContext
中添加Lifecycle
事件監聽器,監聽Lifecycle.START_EVENT
事件。當接收到此事件時,執行removeServiceConnectors();
,執行的目的如注釋所示。
因此,本節的addPreviouslyRemovedConnectors();
的目的就是把之前移除的connecters再添加回來。
2.1.1 addPreviouslyRemovedConnectors();
方法中使用service.addConnector(connector);
方法添加connector。
這一方法曾經在 1.2.1 的TomcatServletWebServerFactory.getWebServer()
方法中被調用過一次,調用處的語句為tomcat.getService().addConnector(connector);
。
不過在TomcatServletWebServerFactory.getWebServer()
方法中被調用時,由于getState().isAvailable()
== false,沒能繼續執行后半部分,此時StandardService.state
== LifecycleState.NEW
;而在本節的addPreviouslyRemovedConnectors();
方法中調用時,StandardService.state
== LifecycleState.STARTED
,可以繼續執行connector.start();
2.1.2 connector.start();
方法中會連續調用,Connector.startInternal()
,Http11NioProtocol.start()
,NioEndpoint.start()
。而主要工作在NioEndpoint.start()
中執行,這里面包含了兩個重要語句
bindWithCleanup();
startInternal();
2.1.2.1 NioEndpoint.bindWithCleanup()
- 創建并配置
ServerSocketChannel
- Initialize SSL if needed
-
selectorPool.open(getName());
NioSelectorPool.sharedSelector = Selector.open();
NioSelectorPool.blockingSelector = new NioBlockingSelector();
NioSelectorPool.blockingSelector.poller = new BlockPoller();
- 啟動"BlockPoller"線程,執行
org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller.run()
2.1.2.2 NioEndpoint.startInternal()
創建executor
啟動"ClientPoller"線程,執行org.apache.tomcat.util.net.NioEndpoint.Poller.run()
啟動"Acceptor"線程,執行org.apache.tomcat.util.net.Acceptor.run()
2.1.2 續
至此,啟動了一些線程總結如下
線程名 | 執行方法 |
---|---|
http-nio-8080-Acceptor | org.apache.tomcat.util.net.Acceptor.run() |
http-nio-8080-BlockPoller | org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller.run() |
http-nio-8080-ClientPoller | org.apache.tomcat.util.net.NioEndpoint.Poller.run() |
http-nio-8080-exec-1 | org.apache.tomcat.util.threads.ThreadPoolExecutor (這是executor的worker線程,沒有具體的執行任務) |
http-nio-8080-exec-2 | 同上,共有10條線程 |
2.2 performDeferredLoadOnStartup();
這里會調用javax.servlet.GenericServlet.init()
方法,對org.apache.catalina.servlets.DefaultServlet
進行初始化。
而org.springframework.web.servlet.DispatcherServlet
是延遲初始化的,它會在將來有網絡請求到來時才調用init()
。
3 tomcat處理網絡請求
3.1 首先總結一下基本信息
3.1.1 線程信息如 2.1.2 續 所述
3.1.2 類信息摘要
其中
NioEndpoint.poller != NioBlockingSelector.poller
NioSelectorPool.sharedSelector == NioBlockingSelector.sharedSelector == BlockPoller.selector (圖中藍色框的Selector為同一個對象)
NioEndpoint$Poller.selector != NioSelectorPool.sharedSelector (圖中紅色框的Selector與藍色框的Selector不是同一個對象)
3.2 Accept線程
3.2.1
"Acceptor"線程中的org.apache.tomcat.util.net.Acceptor.run()
方法在執行的時候,會阻塞在socket = endpoint.serverSocketAccept();
語句處。
當接受到新的網絡請求時,此語句解除阻塞,socket
得到賦值,繼續執行。
3.2.2
socket
獲得賦值后,會來到如下代碼段
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
其中endpoint.setSocketOptions(socket)
語句內內部,會將socket
封裝成NioChannel
類,NioChannel
類又被進一步封裝成NioSocketWrapper
類。
不過NioChannel
類和NioSocketWrapper
類二者內部都有著對方的引用。代碼摘要如下
NioChannel channel = new NioChannel(socket, bufhandler);
NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this);
channel.setSocketWrapper(socketWrapper);
接下來org.apache.tomcat.util.net.NioEndpoint.Poller.register()
方法將其注冊到Poller
中。
Poller.register()
方法中又將NioChannel
進一步封裝成PollerEvent
,然后添加到Poller.events
隊列中,隊列的類型是SynchronizedQueue<PollerEvent> events
。
3.3 ClientPoller線程
此線程主體是一個while(true)輪詢邏輯。
3.3.1
首先調用events();
函數處理events
隊列中的PollerEvent
,當 3.2.2 的末尾處有新PollerEvent
加入隊列后,events();
方法內部會對其處理,過程如下
3.3.1.1
PollerEvent.run()
方法內部,3.2.2 末尾處注冊時的賦值,致使interestOps == OP_REGISTER
,進而執行SocketChannel.register()
方法,將這個socket
注冊到Poller.selector
上,監聽SelectionKey.OP_READ
,attachment是NioSocketWrapper
。
3.3.1.2
PollerEvent.reset()
方法重置PollerEvent
對象。
eventCache.push(pe);
將重置后的PollerEvent
對象放入cache,方便以后重用。
3.3.2
events();
調用完成后,會調用selector.selectedKeys().iterator()
。3.3.1.1 中注冊到selector
中的socket
會被select出來。processKey(sk, socketWrapper);
方法會對這個socket
做進一步處理,其中socketWrapper
是當時注冊時的attachment。
processKey(sk, socketWrapper);
方法內部會將socketWrapper
封裝成NioEndpoint.SocketProcessor
類,交給NioEndpointEndpoint.executor
去執行,也就是交給worker線程了。
3.4 worker線程
3.4.1
接 3.3.2 的末尾,worker線程接到新的任務后,執行的是SocketProcessor.doRun()
方法。
其中關鍵語句是state = getHandler().process(socketWrapper, event);
,即ConnectionHandler.process()
方法,這個方法超長。
這個方法內部處理語句是state = processor.process(wrapper, status);
,即Http11Processor.process()
。然后是層層調用如下,直到執行filter。
doFilter:166, ApplicationFilterChain (org.apache.catalina.core) [1]
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:490, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:408, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:853, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1587, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1130, ThreadPoolExecutor (java.util.concurrent)
run:630, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:832, Thread (java.lang)
在org.apache.catalina.core.StandardWrapperValve.invoke()
方法中,
servlet = wrapper.allocate();
獲取DispatcherServlet
;
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
獲取filterChain。
org.apache.catalina.core.StandardContext.filterMaps
中記錄了所有filter。
filterChain.doFilter(request.getRequest(), response.getResponse());
執行filter。
3.4.2 filter的執行
ApplicationFilterChain.doFilter()
-> ApplicationFilterChain.internalDoFilter()
-> if (pos < n)
-> Filter.doFilter()
-> ApplicationFilterChain.doFilter()
上述是鏈式的執行所有filter。
當filter全部執行完畢后,if (pos < n)
將為false,繼續執行ApplicationFilterChain.internalDoFilter()
的后半部分。
最終執行到servlet.service(request, response);
,servlet
是DispatcherServlet
。
3.4.3 DispatcherServlet.doDispatch()
DispatcherServlet.getHandler()
會根據請求url尋找對應controller。
AbstractHandlerMapping.getHandlerExecutionChain()
會添加intercepter。
最終返回HandlerExecutionChain
。
HandlerExecutionChain
的執行順序如下:
-
mappedHandler.applyPreHandle()
調用HandlerInterceptor.preHandle()
。 -
ha.handle()
調用controller內的執行方法,這個方法是被@Aspect修飾過的。 -
mappedHandler.applyPostHandle()
調用HandlerInterceptor.postHandle()
。 -
mappedHandler.triggerAfterCompletion()
調用HandlerInterceptor.afterCompletion()
。
3.4.3 ha.handle()
中會調用ServletInvocableHandlerMethod.invokeAndHandle()
方法。
該方法中的Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
會最終調用controller的方法,并獲得返回值。
然后this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
會將controller的返回值轉化成response body,并分片發送response。
分片發送依次調用
CoyoteOutputStream.flush()
org.apache.coyote.Response.action()
actionCode == ActionCode.CLIENT_FLUSH
Http11Processor.action()
NioEndpoint.NioSocketWrapper.doWrite()
NioSelectorPool.write()
NioBlockingSelector.write()
然后再經過層層filter以及其他操作,最終回到了org.apache.catalina.connector.CoyoteAdapter.service()
方法,該方法中會執行以下語句
request.finishRequest();
response.finishResponse();
org.apache.catalina.connector.Response.finishResponse()
中會調用coyoteResponse.action(ActionCode.CLOSE, null);
-> org.apache.coyote.http11.Http11Processor.finishResponse()
發送結束分片。http response至此結束。
4 Tomcat 線程池
Tomcat基于jdk中提供的線程池,實現了自己的線程池。
4.1
首先梳理一下jdk中的線程池運行方式。
java.util.concurrent.ThreadPoolExecutor
的構造方式中有3個重要參數 corePoolSize
, maximumPoolSize
, workQueue
。
我們以java.util.concurrent.LinkedBlockingQueue
類作為workQueue
舉例說明。
此線程池構造完畢后,線程池內初始有 0 條線程。然后開始提交任務。
如果 當前線程數 < corePoolSize
,建立一條新線程執行任務。
如果 當前線程數 == corePoolSize
,將任務放入workQueue
隊列等待執行。
如果 當前線程數 == corePoolSize
且 workQueue
滿了,建立一條新線程執行任務。
如果 workQueue
滿了 且 當前線程數 == maximumPoolSize
,執行reject()
方法,拒絕新任務,拒絕新任務的處理方式默認是拋出RejectedExecutionException
異常。
4.2
Tomcat的線程池是在org.apache.tomcat.util.net.AbstractEndpoint.createExecutor()
方法中執行創建的。
org.apache.tomcat.util.threads.ThreadPoolExecutor
類繼承了java.util.concurrent.ThreadPoolExecutor
。
Tomcat 使用的隊列類為 org.apache.tomcat.util.threads.TaskQueue
,該類繼承了java.util.concurrent.LinkedBlockingQueue<Runnable>
,且capacity == Integer.MAX_VALUE
,即隊列承載力極大。
他們組合使用構成的線程池的運行方式如下:
此線程池構造完畢后,線程池內初始有 corePoolSize
條線程。然后開始提交任務。
由于初始化時就建立了一些線程,所以最初提交的幾個任務可以直接被執行。
如果 當前線程數 == corePoolSize
且 沒有空閑線程,建立一條新線程執行任務。
如果 當前線程數 == maximumPoolSize
,將任務放入workQueue
隊列等待執行。
如果 workQueue
滿了 且 當前線程數 == maximumPoolSize
,執行reject()
方法,拒絕新任務,拒絕新任務的處理方式默認是拋出RejectedExecutionException
異常。
4.3
對比上述二者的執行方式,可知其間的差異。
jdk自帶的線程池,初始化時不會啟動新線程,當有任務提交時才會啟動線程。當活躍線程達到corePoolSize
時,會優先將任務放入隊列等待,當等待隊列滿時,才會再次新增線程數量,直到達到maximumPoolSize
。
而Tomcat使用的線程池,初始化時就會啟動corePoolSize
條線程。如果線程都在忙于處理任務時,再提交新任務,會繼續創建新線程,直到達到maximumPoolSize
條線程。如果線程都在忙于處理任務,且線程數量達到了上限時,再提交新任務,才會將其放入等待隊列。
可見,二者的顯著區分處有三點。
- jdk線程池初始化時不會啟動任何線程,第一個任務提交時才會創建線程。
而Tomcat線程池初始化就會啟動corePoolSize
條線程。 - 在活躍線程達到
corePoolSize
,且所有線程都在忙碌中(即都在執行任務)時,再次提交新任務時,二者對新任務的處理方式的優先級不同。
jdk線程池會優先將新任務放入任務隊列中等待執行,當任務隊列滿了才會再次新建線程。
而Tomcat線程池會優先創建線程執行任務,但線程數量達到上限且都在忙碌執行時,才會將任務放入等待隊列中。 - jdk線程池中的任務隊列可自由使用任意類。
而Tomcat線程池需要搭配使用TaskQueue
類,該類的capacity == Integer.MAX_VALUE
。
所以Tomcat的線程池的策略是盡可能用線程立即執行到來的新任務,達到線程數量上限才會讓任務排隊等待。Tomcat的任務隊列的承載能力又是極大的,capacity == Integer.MAX_VALUE
。
4.4
tomcat線程池創建的調用棧
<init>:77, ThreadPoolExecutor (org.apache.tomcat.util.threads)
createExecutor:987, AbstractEndpoint (org.apache.tomcat.util.net)
startInternal:331, NioEndpoint (org.apache.tomcat.util.net)
start:1293, AbstractEndpoint (org.apache.tomcat.util.net)
start:614, AbstractProtocol (org.apache.coyote)
startInternal:1072, Connector (org.apache.catalina.connector)
start:183, LifecycleBase (org.apache.catalina.util)
addConnector:239, StandardService (org.apache.catalina.core)
addPreviouslyRemovedConnectors:282, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
start:213, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
start:43, WebServerStartStopLifecycle (org.springframework.boot.web.servlet.context)
doStart:182, DefaultLifecycleProcessor (org.springframework.context.support)
access$200:53, DefaultLifecycleProcessor (org.springframework.context.support)
start:360, DefaultLifecycleProcessor$LifecycleGroup (org.springframework.context.support)
startBeans:158, DefaultLifecycleProcessor (org.springframework.context.support)
onRefresh:122, DefaultLifecycleProcessor (org.springframework.context.support)
finishRefresh:895, AbstractApplicationContext (org.springframework.context.support)
refresh:554, AbstractApplicationContext (org.springframework.context.support)
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:755, SpringApplication (org.springframework.boot)
refresh:747, SpringApplication (org.springframework.boot)
refreshContext:402, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1247, SpringApplication (org.springframework.boot)
run:1236, SpringApplication (org.springframework.boot)
main:18, Application (com.jindi.search.platform)
5 其他
5.1 http status code
在HttpServletResponse
類中,定義了多個http狀態碼的常量,可以用來反向查找,查看在哪些情況下會返回哪種狀態碼。