Tomcat請求處理流程與原始碼淺析

2023-05-28 18:00:32

系列文章目錄和關於我

一丶Connector

在tomcat中,Connector負責開啟socket並且監聽使用者端請求,返回響應資料。

其中:

  • Endpoint:tomcat中沒有這個介面,只有AbstractEndpoint,它負責啟動執行緒來監聽伺服器埠,並且在接受到資料後交給Processor處理
  • Processor:Processor讀取到使用者端請求後按照請求地址對映到具體的容器進行處理,這個過程請求對映,Processor實現請求對映依賴於Mapper物件,在容器發生註冊和登出的時候,MapperListener會監聽到對應的事件,從而來變更Mapper中維護的請求對映資訊。
  • ProtocolHandler:協定處理器,針對不同的IO方式(NIO,BIO等)和不同的協定(Http,AJP)具備不同的實現,ProtocolHandler包含一個Endpoint來開啟埠監聽,並且包含一個Processor用於按照協定讀取資料並將請求交給容器處理。
  • Acceptor:Acceptor實現了Runnable介面,可以作為一個執行緒啟動,使用Socket API監聽指定埠,用於接收使用者請求。
  • Poller:主要用於監測註冊在原始 scoket 上的事件是否發生,Acceptor接受到請求後,會註冊到Poller的佇列中。

二丶NioEndpoint 初始化ServerSocketChannel

springboot內嵌tomcat,一般預設使用NioEndpoint,在NioEndpoint#start方法中,會觸發NioEndpoint#bind

三丶NioEndpoint 啟動Poller和Acceptor執行緒

NioEndpoint#start方法最後會觸發Poller執行緒和Acceptor執行緒的啟動

可以看到NioEndpoint內部的Poller,和Acceptor都是單獨使用一個守護執行緒來執行。

四丶Acceptor接收請求

1.endpoint.countUpOrAwaitConnection()限制連線數

其內部使用LimitLatch#countUpOrAwait方法限制連線數,如果連線數達到了上限,那將掛起當前執行緒,也就是掛起Acceptor執行緒,從而導致無法有更多的請求連線上來,最大連線數預設為8*1024。

LimitLatch 內部持有一個AbstractQueuedSynchronizer,限制連線數將呼叫其acquireSharedInterruptibly(1),然後會呼叫到AQS的tryAcquireShared,其內部使用AtomicLong來進行連線的計數。

2.NioEndpoint#serverSocketAccept 接收Socket連線

由於NioEndpoint前面呼叫了ServerSocketChannel#configureBlocking(true),所以serverSock#accept,在沒有連線上來時,不會立馬返回null,而是阻塞直到連線來到。

3.NioEndpoint#setSocketOptions將SocketChannel註冊到Poller

在Acceptor執行緒接收到SocketChannel後,會呼叫Poller#register方法進行註冊,Acceptor只負責接受請求,請求後續的處理由Poller執行緒負責

最終請求被包裝為PollerEvent丟到Poller的事件佇列SynchronizedQueue中,SynchronizedQueue使用synchronized保證執行緒安全。

wakeupCounter 是AtomicLong型別,Acceptor接受到請求,將請求封裝為PollerEvent後會呼叫wakeupCounter#incrementAndGet方法,進行+1操作

Poller在使用Selector,進行IO多路複用的時候,會進行如下操作

可以看到,如果wakeupCounter大於0,Poller會呼叫 selector.selectNow()(非阻塞立馬返回),反之呼叫selector.select(selectorTimeout)(超時並阻塞)。

也就說Acceptor接受到請求越多,wakeupCounter越大,越會讓Poller呼叫selector.selectNow()減少阻塞,從而讓Poller更快的檢查事件是否就緒,從而讓請求更及時的被處理。

五丶Poller處理事件

1.events方法檢視事件佇列是否具備事件

上面我們說到Acceptor在建立連線後,將SocketChannel包裝成NioSocketWrapper塞到了Poller的事件佇列中。而Poller執行緒則會一直輪詢這個佇列進行事件的獲取

2.Poller 使用Selector進行select

通過Selector獲取獲取當前就緒的IO,keyCount記錄就緒數目。

3.Poller 處理就緒IO

processKey會呼叫到processSocket,最終使用tomcat執行緒池中的執行緒進行非同步處理

最終會找到Processor進行處理(預設使用快取的,避免重複new物件,頻繁gc,如果快取沒有那麼使用ProtocolHandler 建立出一個),這裡的Processor就是Http11Processor

然後根據事件型別進行不同的處理,如果是讀事件那麼會呼叫Http11Processor#service進行處理,然後會繼續交給CoyoteAdapter呼叫其service進行處理。

六丶CoyoteAdapter處理請求

1.使用Mapper找到請求對應的Host,Context,Wrapper

下圖是的模型,如果使用了SpringMVC,這裡的Wrapper會存在DispatchServlet

如下是Mapper找到的MappingData

2.Pipeline執行

上面說到,Mapper會找到當前請求所屬的host,context和對應的Wrapper,緊接著會進行Pipeline的執行。

為了增強擴充套件性,tomcat定義了Pipeline(管道)和Valve(閥),Pipeline使用職責鏈的方式串聯多個Valve——來自使用者端的請求如同流水一樣流淌在管道中,受到每一個閥的作用。

Pipeline中維護了基礎的Valve,始終位於Pipeline末端,通過Pipeline#addValve新增的Valve違約基礎的Valve之前。

在Tomcat中Engine,Host,Context,Wrapper都有對應的Valve實現,同時維護了一個Pipeline,從而讓我們可以對請求的處理進行擴充套件。

下面是比較重要的Valve

  • StandardEngineValve :Engine對應的Valve,負責請求是否通過mapper找到了對應的Host,並觸發Host對應的Valve

  • ErrorReportValve: 錯誤報告Valve讓後續的Valve繼續執行,如果執行出現錯誤那麼會重新整理響應流,讓使用者端收到響應

  • StandardHostValve:Host對應的Valve,如果請求沒有匹配的context返回404,反之呼叫Context對應的Valve

  • StandardContextValve:Context對應的Valve,如果請求路徑以/META-INF/,或者/WEB-INF/開頭,會直接返回404,反之繼續呼叫Wrapper對應的

  • StandardWrapperValve:Wrapper對應的Valve,會負責組裝Servlet和Filter,並執行FilterChain#doFilter方法

    Filter的匹配主要通過DispatchType和Filter設定的路徑,

    在SpringBoot專案中可以使用FilterRegistrationBean#setDispatcherTypes,和addUrlPatterns進行指定。

3.FilterChain執行

在Tomcat中ApplicationFilterChain實現了Java Servlet規範中的FilterChain。

其中使用ApplicationFilterConfig是對FilterConfig的實現,內部持有一個Filter。

ApplicationFilterChain包含多個ApplicationFilterConfig,使用陣列和pos屬性記錄當前執行到第幾個Filter

Filter都執行結束後,將執行Servlet#service方法

在SpringMVC專案中,會呼叫到DispatcherServlet#service,最終呼叫到Controller。