我們直到Servlet是處理每個HTTP請求的最小單元,而這些Servlet又被web容器所管理,不同的web容器有不同的特性和應用特點,比如常見的web 容器Tomcat和Jetty。
(另外我們還知道一些高性能web框架比如netty,和tomcat的區別是什么呢?
- tomcat是基於HTTP協議的web容器,常用來做前端請求的服務器,也支持NIO模式;
- 而netty是一個網絡框架,可以基於HTTP/TCP/UTP,也可以自定義協議,自主開發不同的web服務器,以滿足不同的場景需求;)
本文旨在探究web容器組織管理servlet的機制,和web容器怎么去處理不同的線程請求。
從Tomcat啟動流程說起
Tomcat的系統結構:
Tomcat是一系列層級容器組成的web容器,其中最頂層的容器是Server,代表着整個服務器,從上圖中可以看出,一個Server可以包含至少一個Service,用於具體提供服務;
Service容器包含的來個主要組件是Connetctor和Container;這是Tomcat最重要的兩個組件:
- Connector用於處理鏈接相關的事情,並提供Socket與Request和Response相關的轉化;
- Container用於封裝和管理Servlet,以及具體處理Request請求;
這種設計模式是網絡I/O常見的設計,為了能夠並發處理大量連接,一般會采取一個線程方式監聽客戶端請求;另一個線程采用NIO的形式select已經接收到數據的channel信道,處理請求,這樣的形式;
Tomcat7提供了三種Connector:Java Blocking Connector,Java Nio Blocking Connector,APR/native Connector。
- Java Blocking Connector是Tomcat7默認的網絡連接處理方式,其處理模型為一個或多個線程(Acceptor)接收TCP連接,每個連接的請求都會交由單獨的線程(Worker)處理。
- Java Nio Blocking Connector是Tomcat8默認的處理方式。為了避免連接過多(尤其是keepalive的連接會阻塞worker線程)造成的線程資源浪費,它在接收網絡連接的線程(Acceptor)和請求處理線程(Worker)之間增加了Poller事件隊列,每當有IO就緒事件都會放入Poller隊列,Poller線程會不斷消費隊列里的事件,交由Worker線程處理。
- APR/native Connector,APR即Apache Portable Runtime Libraries,使用了native代碼來優化網絡請求的處理。
在容器等級中的最底層就是Context容器,負責直接管理Servlet,一個Context對應一個Web工程;
Tomcat的啟動邏輯是基於觀察者設計的,先來看看啟動的時序圖:
容器的配置屬性由web.xml制定;
Tomcat處理一次http請求的時序圖:
有了一個整體的了解后,讓我們再來看看Tomcat是如何分發請求,以及處理多用戶同時請求和管理多級容器的;
Tomcat是如何分發請求
Tomcat中使用Connector處理多線程的連接請求:
先看看Connector的主要類圖:其中處理Socket的是HttpProcessor
容器的總體設計: Container是容器的父接口,所有子容器都必須實現這個接口,其中用到的設計模式是責任鏈模式
Engine -> Host -> Context -> Wrapper 這四個容器是個分別包含的父子關系(參見上幾個圖)
四個容器的關系類圖:
來看看Jetty
Jetty相較於Tomcat更加輕便,雖然架構更加簡單,但是看起來可並不輕松。Spring是設計初衷是用來管理應用中的實例Bean,因而是基於Bean的架構;Jetty則更傾向於流程和組件的管理,采用了基於handler的架構。handler的嵌套和鏈式結構,LifeCycle和doStart、doHandler模式無不印證了這點
Jetty基礎架構:
Jetty與Tomcat的區別
Jetty 的架構從前面的分析可知,它的所有組件都是基於 Handler 來實現,當然它也支持 JMX。但是主要的功能擴展都可以用 Handler 來實現。可以說 Jetty 是面向 Handler 的架構,就像 Spring 是面向 Bean 的架構,iBATIS 是面向 statement 一樣,而 Tomcat 是以多級容器構建起來的,它們的架構設計必然都有一個“元神”,所有以這個“元神“構建的其它組件都是肉身。
從設計模板角度來看 Handler 的設計實際上就是一個責任鏈模式,接口類 HandlerCollection 可以幫助開發者構建一個鏈,而另一個接口類 ScopeHandler 可以幫助你控制這個鏈的訪問順序。另外一個用到的設計模板就是觀察者模式,用這個設計模式控制了整個 Jetty 的生命周期,只要繼承了 LifeCycle 接口,你的對象就可以交給 Jetty 來統一管理了。所以擴展 Jetty 非常簡單,也很容易讓人理解,整體架構上的簡單也帶來了無比的好處,Jetty 可以很容易被擴展和裁剪。
相比之下,Tomcat 要臃腫很多,Tomcat 的整體設計上很復雜,前面說了 Tomcat 的核心是它的容器的設計,從 Server 到 Service 再到 engine 等 container 容器。作為一個應用服務器這樣設計無口厚非,容器的分層設計也是為了更好的擴展,這是這種擴展的方式是將應用服務器的內部結構暴露給外部使用者,使得如果想擴展 Tomcat,開發人員必須要首先了解 Tomcat 的整體設計結構,然后才能知道如何按照它的規范來做擴展。這樣無形就增加了對 Tomcat 的學習成本。不僅僅是容器,實際上 Tomcat 也有基於責任鏈的設計方式,像串聯 Pipeline 的 Vavle 設計也是與 Jetty 的 Handler 類似的方式。要自己實現一個 Vavle 與寫一個 Handler 的難度不相上下。表面上看,Tomcat 的功能要比 Jetty 強大,因為 Tomcat 已經幫你做了很多工作了,而 Jetty 只告訴,你能怎么做,如何做,有你去實現。
單純比較 Tomcat 與 Jetty 的性能意義不是很大,只能說在某種使用場景下,它表現的各有差異。因為它們面向的使用場景不盡相同。從架構上來看 Tomcat 在處理少數非常繁忙的連接上更有優勢,也就是說連接的生命周期如果短的話,Tomcat 的總體性能更高。
而 Jetty 剛好相反,Jetty 可以同時處理大量連接而且可以長時間保持這些連接。例如像一些 web 聊天應用非常適合用 Jetty 做服務器,像淘寶的 web 旺旺就是用 Jetty 作為 Servlet 引擎。
另外由於 Jetty 的架構非常簡單,作為服務器它可以按需加載組件,這樣不需要的組件可以去掉,這樣無形可以減少服務器本身的內存開銷,處理一次請求也是可以減少產生的臨時對象,這樣性能也會提高。另外 Jetty 默認使用的是 NIO 技術在處理 I/O 請求上更占優勢,Tomcat 默認使用的是 BIO,在處理靜態資源時,Tomcat 的性能不如 Jetty。