Tomcat 架構概述
Tomcat 是一個 Web 應用服務器,它是對 HTTP 和 Servlet 規範的實現,簡單來說它做了這幾件事:處理 HTTP 協議、執行 Servlet 和處理網絡 I/O。這裏以 6.0.53 版本為例(實現了 HTTP/1.1、Servlet2.5),研究其基本結構。
關於源碼版本,我使用的是 tomcat6,因為 7 為了重構有太多的抽象,看著實在費勁,6 代碼雖有冗余但讀起來很直觀,並且低版本也不影響理解 Tomcat 的核心流程。
體系結構
從 server.xml
中就能夠看出 Tomcat 各組件的層次結構,具體結構圖如下:
- Server:代表整個容器,它可能包含一個或多個 Service 和全局 JNDI 資源;
- Service:包含一個或多個 Connector,這些連接器與一個 Engine 相關聯;
- Engine:表示請求處理流水線(pipeline),它接收所有連接器的請求,並將響應交給適當的連接器返回給客戶端;
- Host:網絡名稱(域名)與 Tomcat 服務器的關聯,默認主機名 localhost,一個 Engine 可包含多個 Host;
- Connector:處理與客戶端的通信,網絡 I/O;
- Context:表示一個 Web 應用程序,一個 Host 包含多個上下文。
服務器模型
服務器模型(或 I/O 模型),描述的是 TCP 連接的處理方式,以及 Socket 讀寫時線程的狀態。Java 裏常用的是 BIO 和 NIO,分別對應同步阻塞和同步非阻塞兩種模型,Tomcat 中的 Connector 組件就是對這兩種的封裝實現。
BIO - 阻塞式
Tomcat 實現了一個一連接一線程的簡單服務器模型,內部采用線程池做了優化,設計如下:
- 當 Acceptor 接收到一個 TCP 連接時,線程池分配一個線程進行處理;
- 線程調用 read() 方法讀取 Socket 輸入流中的字節,此時線程阻塞(Block),直到收到客戶端發送的數據;
- 收到數據後,進行解碼、業務處理、編碼,最後把響應發送到客戶端,關閉連接。
由此可以看出,合理的分配線程池大小可以一定程度上提高系統的並發能力。
NIO - 非阻塞
BIO 的缺點在於不管當前連接有沒有數據傳輸,它始終阻塞占用線程池內的一個線程,而 NIO 的處理方式是若通道無數據可讀取,此時線程不阻塞直接返回,可用於處理其他連接,提高了線程利用率。那怎麽知道什麽時候處理數據的讀寫呢?當通道可讀或可寫時,內核會通知用戶程序進行處理。
NIO 的編程比較復雜,常用的是 Reactor 模式,它描述了一個利用多路復用 I/O,基於事件驅動的服務器處理模型,(這裏) 基於 Doug Lea 的 Scalable IO in Java 對 Reactor 進行了實現。Tomcat 的設計略有不同,其設計如下:
- Acceptor 以阻塞模式接收 TCP 連接,然後將連接註冊到 Poller 上;
- Poller 以非阻塞模式處理 SSL 握手和 HTTP 請求頭的讀取;
- BlockPoller 模擬阻塞處理 HTTP 請求體的讀取和發送響應。
值得註意的是,兩類 Poller 都只是負責事件的通知,I/O 操作都是由線程池中的線程完成。那麽,ServerSocketChannel 為什麽阻塞?為什麽要模擬阻塞處理請求體和 Servlet 響應?相關的討論可參考:
- Why Tomcat‘s Non-Blocking Connector is using a blocking socket?
- Getting my head around NIO ‘simulated‘ blocking (trying to)
Servlet API 的實現
Servlet 規範描述了容器如何加載和運行 Servlet,如和將請求映射到用戶配置的 Servlet, 如何處理請求和響應等相關問題。Tomcat 主要實現了以下 API:
- ServletConfig :Servlet 名字和初始化參數;
- ServletContext :定義了 Web 應用程序,Servlet 運行的上下文;
- ServletRequest :封裝客戶端請求;
- ServletResponse :封裝服務端響應;
- FilterChain :請求過濾調用鏈;
- FilterConfig :過濾配置對象;
- RequestDispatcher :轉發請求,將請求轉發給 JSP 或另一個 Servlet 處理。
其他如 Servlet、Filter、GenericServlet、HttpServlet 接口或類則由用戶程序來實現,更多詳細的介紹請參考規範內容。
小結
Tomcat 架構看著挺簡單但做起來難,難的就是把復雜的問題抽象化、簡單化,體現到代碼上就是如何設計和抽象出類並且優雅的組織在一起,它作為一個流行的中間件,其內部代碼的實現以及優化手段,也是值得我們去研究和模仿的。
Tomcat 架構概述