天天看點

jetty的工作原理以及與tomcat 的比較

Jetty 的基本架構

Jetty

目前的是一個比較被看好的 Servlet

引擎,它的架構比較簡單,也是一個可擴充性和非常靈活的應用伺服器,它有一個基本資料模型,這個資料模型就是

Handler,所有可以被擴充的元件都可以作為一個 Handler,添加到 Server 中,Jetty 就是幫你管理這些 Handler。

下圖是

Jetty 的基本架構圖,整個 Jetty 的核心元件由 Server 和 Connector 兩個元件構成,整個 Server 元件是基于

Handler 容器工作的,它類似與 Tomcat 的 Container 容器,Jetty 與 Tomcat 的比較在後面詳細介紹。Jetty

中另外一個比不可少的元件是 Connector,它負責接受用戶端的連接配接請求,并将請求配置設定給一個處理隊列去執行。

jetty的工作原理以及與tomcat 的比較

Jetty 中還有一些可有可無的元件,我們可以在它上做擴充。如 JMX,我們可以定義一些 Mbean 把它加到 Server 中,當 Server 啟動的時候,這些 Bean 就會一起工作。

jetty的工作原理以及與tomcat 的比較

從上圖可以看出整個

Jetty 的核心是圍繞着 Server 類來建構,Server 類繼承了 Handler,關聯了 Connector 和

Container。Container 是管理 Mbean 的容器。Jetty 的 Server 的擴充主要是實作一個個 Handler 并将

Handler 加到 Server 中,Server 中提供了調用這些 Handler 的通路規則。

整個 Jetty 的所有元件的生命周期管理是基于觀察者模闆設計,它和 Tomcat 的管理是類似的。下面是 LifeCycle 的類關系圖

jetty的工作原理以及與tomcat 的比較

前面所述 Jetty 主要是基于 Handler 來設計的,Handler 的體系結構影響着整個 Jetty 的方方面面。下面總結了一下 Handler 的種類及作用:

jetty的工作原理以及與tomcat 的比較

主要提供了兩種 Handler 類型,一種是 HandlerWrapper,它可以将一個 Handler

委托給另外一個類去執行,如我們要将一個 Handler 加到 Jetty 中,那麼就必須将這個 Handler 委托給 Server

去調用。配合 ScopeHandler 類我們可以攔截 Handler 的執行,在調用 Handler 之前或之後,可以做一些另外的事情,類似于

Tomcat 中的 Valve;另外一個 Handler 類型是 HandlerCollection,這個 Handler 類可以将多個

Handler 組裝在一起,構成一個 Handler 鍊,友善我們做擴充。

的入口是 Server 類,Server 類啟動完成了,就代表 Jetty 能為你提供服務了。它到底能提供哪些服務,就要看 Server

類啟動時都調用了其它元件的 start 方法。從 Jetty 的配置檔案我們可以發現,配置 Jetty 的過程就是将那些類配置到 Server

的過程。下面是 Jetty 的啟動時序圖:

jetty的工作原理以及與tomcat 的比較

因為

Jetty 中所有的元件都會繼承 LifeCycle,是以 Server 的 start 方法調用就會調用所有已經注冊到 Server

的元件,Server 啟動其它元件的順序是:首先啟動設定到 Server 的 Handler,通常這個 Handler 會有很多子

Handler,這些 Handler 将組成一個 Handler 鍊。Server 會依次啟動這個鍊上的所有 Handler。接着會啟動注冊在

Server 上 JMX 的 Mbean,讓 Mbean 也一起工作起來,最後會啟動

Connector,打開端口,接受用戶端請求,啟動邏輯非常簡單。

作為一個獨立的 Servlet 引擎可以獨立提供 Web 服務,但是它也可以與其他 Web

應用伺服器內建,是以它可以提供基于兩種協定工作,一個是 HTTP,一個是 AJP 協定。如果将 Jetty 內建到 Jboss 或者

Apache,那麼就可以讓 Jetty 基于 AJP 模式工作。下面分别介紹 Jetty

如何基于這兩種協定工作,并且它們如何建立連接配接和接受請求的。

如果前端沒有其它 web 伺服器,那麼 Jetty 應該是基于 HTTP 協定工作。也就是當 Jetty 接收到一個請求時,必須要按照 HTTP 協定解析請求和封裝傳回的資料。那麼 Jetty 是如何接受一個連接配接又如何處理這個連接配接呢?

我們設定

Jetty 的 Connector 實作類為 org.eclipse.jetty.server.bi.SocketConnector 讓

Jetty 以 BIO 的方式工作,Jetty 在啟動時将會建立 BIO 的工作環境,它會建立 HttpConnection 類用來解析和封裝

HTTP1.1 的協定,ConnectorEndPoint 類是以 BIO 的處理方式處理連接配接請求,ServerSocket 是建立

socket 連接配接接受和傳送資料,Executor 是處理連接配接的線程池,它負責處理每一個請求隊列中任務。acceptorThread

是監聽連接配接請求,一有 socket 連接配接,它将進入下面的處理流程。

當 socket 被真正執行時,HttpConnection 将被調用,這裡定義了如何将請求傳遞到 servlet 容器裡,有如何将請求最終路由到目的 servlet,關于這個細節可以參考《 servlet 工作原了解析》一文。

下圖是 Jetty 啟動建立建立連接配接的時序圖:

jetty的工作原理以及與tomcat 的比較

Jetty 建立接受連接配接環境需要三個步驟:

建立一個隊列線程池,用于處理每個建立連接配接産生的任務,這個線程池可以由使用者來指定,這個和 Tomcat 是類似的。

建立 ServerSocket,用于準備接受用戶端的 socket 請求,以及用戶端用來包裝這個 socket 的一些輔助類。

建立一個或多個監聽線程,用來監聽通路端口是否有連接配接進來。

相比 Tomcat 建立建立連接配接的環境,Jetty 的邏輯更加簡單,牽涉到的類更少,執行的代碼量也更少了。

當建立連接配接的環境已經準備好了,就可以接受 HTTP 請求了,當 Acceptor 接受到 socket 連接配接後将轉入下圖所示流程執行:

jetty的工作原理以及與tomcat 的比較

Accetptor

線程将會為這個請求建立 ConnectorEndPoint。HttpConnection 用來表示這個連接配接是一個 HTTP

協定的連接配接,它會建立 HttpParse 類解析 HTTP 協定,并且會建立符合 HTTP 協定的 Request 和 Response

對象。接下去就是将這個線程交給隊列線程池去執行了。

通常一個

web 服務站點的後端伺服器不是将 Java 的應用伺服器直接暴露給服務通路者,而是在應用伺服器,如 Jboss 的前面在加一個 web

伺服器,如 Apache 或者

nginx,我想這個原因大家應該很容易了解,如做日志分析、負載均衡、權限控制、防止惡意請求以及靜态資源預加載等等。

下圖是通常的 web 服務端的架構圖:

jetty的工作原理以及與tomcat 的比較

這種架構下 servlet 引擎就不需要解析和封裝傳回的 HTTP 協定,因為 HTTP 協定的解析工作已經在 Apache 或 Nginx 伺服器上完成了,Jboss 隻要基于更加簡單的 AJP 協定工作就行了,這樣能加快請求的響應速度。

對比 HTTP 協定的時序圖可以發現,它們的邏輯幾乎是相同的,不同的是替換了一個類 Ajp13Parserer 而不是 HttpParser,它定義了如何處理 AJP 協定以及需要哪些類來配合。

實際上在

AJP 處理請求相比較 HTTP 時唯一的不同就是在讀取到 socket 資料包時,如何來轉換這個資料包,是按照 HTTP

協定的包格式來解析就是 HttpParser,按照 AJP 協定來解析就是 Ajp13Parserer。封裝傳回的資料也是如此。

Jetty 工作在 AJP 協定下,需要配置 connector 的實作類為 Ajp13SocketConnector,這個類繼承了

SocketConnector 類,覆寫了父類的 newConnection 方法,為的是建立 Ajp13Connection 對象而不是

HttpConnection。如下圖表示的是 Jetty 建立連接配接環境時序圖:

jetty的工作原理以及與tomcat 的比較

HTTP 方式唯一不同的地方的就是将 SocketConnector 類替換成了 Ajp13SocketConnector。改成

Ajp13SocketConnector 的目的就是可以建立 Ajp13Connection 類,表示目前這個連接配接使用的是 AJP

協定,是以需要用 Ajp13Parser 類解析 AJP 協定,處理連接配接的邏輯都是一樣的。如下時序圖所示:

jetty的工作原理以及與tomcat 的比較

前面所描述的 Jetty 建立用戶端連接配接到處理用戶端的連接配接都是基于 BIO 的方式,它也支援另外一種 NIO 的處理方式,其中 Jetty 的預設 connector 就是 NIO 方式。

關于 NIO 的工作原理可以參考 developerworks 上關于 NIO 的文章,通常 NIO 的工作原型如下:

建立一個

Selector 相當于一個觀察者,打開一個 Server 端通道,把這個 server

通道注冊到觀察者上并且指定監聽的事件。然後周遊這個觀察者觀察到事件,取出感興趣的事件再處理。這裡有個最核心的地方就是,我們不需要為每個被觀察者創

建一個線程來監控它随時發生的事件。而是把這些被觀察者都注冊一個地方統一管理,然後由它把觸發的事件統一發送給感興趣的程式子產品。這裡的核心是能夠統一

的管理每個被觀察者的事件,是以我們就可以把服務端上每個建立的連接配接傳送和接受資料作為一個事件統一管理,這樣就不必要每個連接配接需要一個線程來維護了。

這裡需要注意的地方時,很多人認為監聽

SelectionKey.OP_ACCEPT 事件就已經是非阻塞方式了,其實 Jetty

仍然是用一個線程來監聽用戶端的連接配接請求,當接受到請求後,把這個請求再注冊到 Selector

上,然後才是非阻塞方式執行。這個地方還有一個容易引起誤解的地方是:認為 Jetty 以 NIO

方式工作隻會有一個線程來處理所有的請求,甚至會認為不同使用者會在服務端共享一個線程進而會導緻基于 ThreadLocal 的程式會出現問題,其實從

的源碼中能夠發現,真正共享一個線程的處理隻是在監聽不同連接配接的資料傳送事件上,比如有多個連接配接已經建立,傳統方式是當沒有資料傳輸時,線程是阻塞的也就

是一直在等待下一個資料的到來,而 NIO 的處理方式是隻有一個線程在等待所有連接配接的資料的到來,而當某個連接配接資料到來時 Jetty

會把它配置設定給這個連接配接對應的處理線程去處理,是以不同連接配接的處理線程仍然是獨立的。

Jetty 的 NIO 處理方式和 Tomcat 的幾乎一樣,唯一不同的地方是在如何把監聽到事件配置設定給對應的連接配接的處理方式。從測試效果來看 Jetty 的 NIO 處理方式更加高效。下面是 Jetty 的 NIO 處理時序圖:

jetty的工作原理以及與tomcat 的比較

下面看一下 Jetty 是如何處理一個 HTTP 請求的。

實際上

Jetty 的工作方式非常簡單,當 Jetty 接受到一個請求時,Jetty 就把這個請求交給在 Server 中注冊的代理 Handler

去執行,如何執行你注冊的 Handler,同樣由你去規定,Jetty 要做的就是調用你注冊的第一個 Handler 的

handle(String target, Request baseRequest, HttpServletRequest request,

HttpServletResponse response) 方法,接下去要怎麼做,完全由你決定。

要能接受一個 web 請求通路,首先要建立一個 ContextHandler,如下代碼所示:

當我們在浏覽器裡敲入

http://localhost:8080 時的請求将會代理到 Server 類的 handle 方法,Server 的 handle

的方法将請求代理給 ContextHandler 的 handle 方法,ContextHandler 又調用 HelloHandler 的

handle 方法。這個調用方式是不是和 Servlet 的工作方式類似,在啟動之前初始化,然後建立對象後調用 Servlet 的

service 方法。在 Servlet 的 API 中我通常也隻實作它的一個包裝好的類,在 Jetty 中也是如此,雖然

ContextHandler 也隻是一個 Handler,但是這個 Handler 通常是由 Jetty

幫你實作了,我們一般隻要實作一些我們具體要做的業務邏輯有關的 Handler 就好了,而一些流程性的或某些規範的

Handler,我們直接用就好了,如下面的關于 Jetty 支援 Servlet 的規範的 Handler 就有多種實作,下面是一個簡單的

HTTP 請求的流程。

通路一個 Servlet 的代碼:

ServletContextHandler 并給這個 Handler 添加一個 Servlet,這裡的 ServletHolder 是

Servlet 的一個裝飾類,它十分類似于 Tomcat 中的 StandardWrapper。下面是請求這個 Servlet 的時序圖:

jetty的工作原理以及與tomcat 的比較

上圖可以看出

Jetty 處理請求的過程就是 Handler 鍊上 handle 方法的執行過程,在這裡需要解釋的一點是 ScopeHandler

的處理規則,ServletContextHandler、SessionHandler 和 ServletHandler 都繼承了

ScopeHandler,那麼這三個類組成一個 Handler

鍊,它們的執行規則是:ServletContextHandler.handleServletContextHandler.doScope

SessionHandler. doScopeServletHandler. doScopeServletContextHandler.

doHandleSessionHandler. doHandleServletHandler. doHandle,它這種機制使得我們可以在

doScope 做一些額外工作。

前面介紹了

Jetty 可以基于 AJP 協定工作,在正常的企業級應用中,Jetty 作為一個 Servlet 引擎都是基于 AJP

協定工作的,是以它前面必然有一個伺服器,通常情況下與 Jboss 內建的可能性非常大,這裡介紹一下如何與 Jboss 內建。

Jboss 是基于 JMX 的架構,那麼隻要符合 JMX 規範的系統或架構都可以作為一個元件加到 Jboss 中,擴充 Jboss 的功能。Jetty 作為主要的 Servlet 引擎當然支援與 Jboss 內建。具體內建方式如下:

作為一個獨立的 Servlet 引擎內建到 Jboss 需要繼承 Jboss 的 AbstractWebContainer

類,這個類實作的是模闆模式,其中有一個抽象方法需要子類去實作,它是 getDeployer,可以指定建立 web 服務的

Deployer。Jetty 工程中有個 jetty-jboss 子產品,編譯這個子產品就會産生一個 SAR 包,或者可以直接從官網下載下傳一個 SAR

包。解壓後如下圖:

jetty的工作原理以及與tomcat 的比較

jboss-jetty-6.1.9 目錄下有一個 webdefault.xml 配置檔案,這個檔案是 Jetty 的預設 web.xml

配置,在 META-INF 目錄發下發現 jboss-service.xml 檔案,這個檔案配置了 MBean,如下:

同樣這個

org.jboss.jetty.JettyService 類也是繼成 org.jboss.web.AbstractWebContainer

類,覆寫了父類的 startService 方法,這個方法直接調用 jetty.start 啟動 Jetty。

Tomcat

和 Jetty 都是作為一個 Servlet 引擎應用的比較廣泛,可以将它們比作為中國與美國的關系,雖然 Jetty 正常成長為一個優秀的

Servlet 引擎,但是目前的 Tomcat 的地位仍然難以撼動。相比較來看,它們都有各自的優點與缺點。

經過長時間的發展,它已經廣泛的被市場接受和認可,相對 Jetty 來說 Tomcat 還是比較穩定和成熟,尤其在企業級應用方面,Tomcat

仍然是第一選擇。但是随着 Jetty 的發展,Jetty 的市場佔有率也在不斷提高,至于原因就要歸功與 Jetty

的很多優點了,而這些優點也是因為 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

告訴你 1+1=2,1+2=3,2+2=4 這個結果,然後你可以根據這個方式得出

1+1+2=4,你要計算其它數必須根據它給你的公式才能計算,而 Jetty

是告訴你加減乘除的算法規則,然後你就可以根據這個規則自己做運算了。是以你一旦掌握了 Jetty,Jetty 将變得異常強大。

單純比較

Tomcat 與 Jetty 的性能意義不是很大,隻能說在某種使用場景下,它表現的各有差異。因為它們面向的使用場景不盡相同。從架構上來看

Tomcat 在處理少數非常繁忙的連接配接上更有優勢,也就是說連接配接的生命周期如果短的話,Tomcat 的總體性能更高。

而 Jetty 剛好相反,Jetty 可以同時處理大量連接配接而且可以長時間保持這些連接配接。例如像一些 web 聊天應用非常适合用 Jetty 做伺服器,像淘寶的 web 旺旺就是用 Jetty 作為 Servlet 引擎。

另外由于

的架構非常簡單,作為伺服器它可以按需加載元件,這樣不需要的元件可以去掉,這樣無形可以減少伺服器本身的記憶體開銷,處理一次請求也是可以減少産生的臨時

對象,這樣性能也會提高。另外 Jetty 預設使用的是 NIO 技術在處理 I/O 請求上更占優勢,Tomcat 預設使用的是

BIO,在處理靜态資源時,Tomcat 的性能不如 Jetty。

作為一個标準的

Servlet 引擎,它們都支援标準的 Servlet 規範,還有 Java EE 的規範也都支援,由于 Tomcat

的使用的更加廣泛,它對這些支援的更加全面一些,有很多特性 Tomcat 都直接內建進來了。但是 Jetty 的應變更加快速,這一方面是因為

Jetty 的開發社群更加活躍,另一方面也是因為 Jetty 的修改更加簡單,它隻要把相應的元件替換就好了,而 Tomcat

的整體結構上要複雜很多,修改功能比較緩慢。是以 Tomcat 對最新的 Servlet 規範的支援總是要比人們預期的要晚。

本文介紹了目前 Java 服務端中一個比較流行應用伺服器 Jetty,介紹了它的基本架構和工作原理以及如何和 Jboss 工作,最後與 Tomcat 做了比較。

原文連結:[http://wely.iteye.com/blog/2360503]