深入理解 Tomcat (二) 從巨集觀上理解 Tomcat 元件及架構

莫那·魯道發表於2018-01-14

小姐姐鎮樓

這是我們自編譯原始碼以來第一次總結 tomcat, 雖然不知從何說起, 但這筆不能停下來, 看了很多的文章和原始碼, 腦子裡從最初的混混沌沌到現在的稍有頭緒, 樓主想說, 不容易.

tomcat 異常複雜, 元件巨多. 我認為, 作為初學者, 我們不能直接進入原始碼, 過多的實現細節會掩蓋抽象概念, 容易一葉障目不見泰山. 我個人認為<>這本書寫的很好, 而我也想和這本書的角度一樣, 盡力(注意:是盡力)去描述我所理解的 tomcat.

而我學習tomcat 的路線是:

  1. 巨集觀上理解 tomcat 元件及架構設計.
  2. 從一個簡單的例子來理解tomcat的底層設計, 到底是怎麼做的.
  3. 從 tomcat 的各個元件各個擊破, 理解每個元件的設計.
  4. 從2條路線去 debug, 驗證之前的理論是否正確, 並深刻理解程式碼. 第一條路線是啟動過程. 第二條路線是一個 http 請求到 Servlet 的 service() 方法的過程.
  5. 總結 tomcat 的設計.

大概是這個樣子, 所以按照我們的研究路線, 大概會有5到6篇文章來詳細講述.

我們的文章會配上原始碼, 所以, 如果同學們沒有下載原始碼, 可以去樓主關於 tomcat 的第一篇文章去下載原始碼, 最好將2份原始碼都下載, 因為我們會將2分原始碼混合的分析.

so, 這是第一篇(雖然標題是二).

這篇文章我們主要講什麼?
1. 什麼是 tomcat?
  1. tomcat 的歷史
  2. 和 tomcat 高度相關的 Servlet 是什麼?是如何工作的?
複製程式碼
2. tomcat 是如何設計的? 有哪些所謂的元件?
  1.  tomcat 原始碼目錄的解釋
  2.  tomcat 整體框架的層次結構和架構圖
  3. 分析每個元件, 包括 Connector, Container, Component
  4. 從介面和類的角度(UML 類圖)看架構.
複製程式碼
3. 總結

什麼是 tomcat ?

我們還是引用一下維基百科:

Tomcat是由Apache軟體基金會下屬的Jakarta專案開發的一個Servlet容器,按照Sun Microsystems提供的技術規範,實現了對ServletJavaServer PageJSP)的支援,並提供了作為Web伺服器的一些特有功能,如Tomcat管理和控制平臺、安全域管理和Tomcat閥等。由於Tomcat本身也內含了一個HTTP伺服器,它也可以被視作一個單獨的Web伺服器。但是,不能將Tomcat和Apache HTTP伺服器混淆,Apache HTTP伺服器是一個用C語言實現的HTTPWeb伺服器;這兩個HTTP web server不是捆綁在一起的。Apache Tomcat包含了一個配置管理工具,也可以通過編輯XML格式的配置檔案來進行配置。

簡而言之: tomcat 是一個接受 http 請求並解析 http 請求並反饋客戶端的一個應用程式.

tomcat 可以說是 sun Servlet 的一個官方參考實現, 因為 tomcat 剛開始就是 sun 開發的,後來捐獻給了 apache 基金會.

在我們理解 tomcat 之前, 我們得了解一下tomcat 所實現的 Servlet 是什麼東東?

那要從網際網路大潮興起開始講起, 我們知道, 網頁不止有靜態的還有動態的, 而在剛開始的時候, 常見的實現動態網頁的技術就是 CGI, 但是作為 Java 的發明人, SUN|肯定要搞一個超級大風頭, 讓整個世界一下子認識了 Java, 不過很快悲催的發現 Applet 其實用途不大, 眼看著網際網路開始流行, 一定要搭上千載難逢的快車啊.

於是, Servlet 就誕生了, Servlet 其實就是 SUN 為了讓 Java 能實現動態的可互動的網頁, 從而進入 web 程式設計的領域而定義的一套標準.

這套標準是這麼說的: 你想用 Java 開發動態網頁, 可以定義一個自己的" Servlet", 但一定要實現我的 HTTPServlet 介面, 然後過載 doGet(), doPost()方法. 使用者從流浪器 GET 的時候, 呼叫 doGet 方法, 從流浪器向伺服器傳送表單資料的時候, 呼叫 doPost 方法, 如果你想訪問使用者從瀏覽器傳遞過來的引數, 用 HttpServletRequest 物件就好了, 裡面有 getParameter, getQueryString 方法, 如果你處理完了, 想向瀏覽器返回資料, 用 HttpServletResponse 物件呼叫 getPrintWriter 方法就可以輸出資料了.

如果你想實現一個購物車, 需要 session, 很簡單, 從 HttpServletRequest 呼叫 getSession 方法就可以了.

那麼..... Servlet 是如何工作的

servlet 是一個複雜的系統, 但是他有三個基本任務, 對每個請求, servlet 容器會為其完成以下3個操作:

  1. 建立一個 Request 物件, 用可能會在呼叫的 Servlet 中使用到的資訊填充該 Request 物件, 如引數, 頭, cookie, 查詢字串, URI 等, Request 物件是 javax.servlet.ServletRequest 介面或 javax.servlet.ServletRequest 介面的一個例項.
  2. 建立一個呼叫 Servlet 的 Response 物件, 用來向 Web 客戶端傳送響應. response 物件是 javax.servlet.http.ServletResponse 介面或 javax.servlet.ServletResponse 介面的一個例項;
  3. 呼叫 Servlet 的 service 方法, 將 request 物件和 response 物件作為引數傳入, Servlet 從 request 物件中讀取資訊, 並通過 response 物件傳送響應資訊.

你寫了一個" Servlet", 接下來要執行, 你就發現沒法通過 java 直接執行了, 你需要一個能夠執行 Servlet 的容器, 這個容器 Sun 最早實現了一個, 叫 Java Web Server, 1999年捐給了 Apache Software foundation, 就改名叫 Tomcat. 這就是 Tomcat 的由來.

所以, Tomcat 就是一個 Servlet 容器, 能接收使用者從瀏覽器發來的請求, 然後轉發給 Servlet 處理, 把處理完的響應資料發回瀏覽器.

但是 Servlet 輸出 Html, 還是採用了老的 CGI 方式 , 是一句一句輸出, 所以,編寫和修改 html 非常不方便, 於是, Java Server Pages(JSP) 就來救急了, JSP 並沒有增加任何本質上不能用 Servlet 實現的功能, 實際上 JSP 在執行之前, 需要先編譯成 Servlet, 然後才執行的.

但是, 在 JSP 中編寫靜態 HTML 更加方便, 不必再用 println 語句來輸出每一行HTML 程式碼, 更重要的是, 藉助內容和外觀的分離,頁面製作中不同性質的任務可以方便的分開: 比如, 有頁面設計者進行 HTML 設計, 同時流出供 Java 程式插入動態內容的空間.

Tomcat 能執行 Servlet, 當然能執行 JSP 了.

既然是 Web 伺服器, tomcat 除了能執行 Servlet 和 JSP 之外, 也能像 Apache/nginx 一樣, 支援靜態 html, 圖片, 文件的訪問, 只是效能差一點,在實際的應用中, 一般是 Apache/nginx 作為負載均衡伺服器和靜態資源伺服器放在最前端, 後面是 tomcat 組成的叢集.

如果使用者請求的是靜態資源, nginx 直接搞定, 如果是動態資源(如xxx.jsp), nginx 就會按照一定的演算法轉發的某個 tomcat 上, 達到負載均衡的目的.


Tomcat 是如何設計的? 有哪些所謂的元件?

tomcat 的主體是 Catalina, catalina 是一個成熟的軟體, 設計和開發的十分優雅, 功能結構也是模組化的. 我們之前說 Servlet 是如何工作的?中提到了 Servlet 容器的任務, 基於這些任務可以將 Catalina 劃分為兩個模組: 聯結器(connector)和容器(container).

注意: 聯結器和 Servlet 容器是一對多的關係.

聯結器負責將一個請求與容器相關聯, 他的工作為它接收到的每個 http 請求建立一個 Request 物件和一個 Response 物件, 然後, 他將處理過程交給容器. 容器從聯結器中接收到 Request 物件和 Response 物件, 並負責呼叫相應的 service 方法.

但是請記住, 上面所描述的處理過程只是 Catalina 容器處理請求的的整個過程的一小部分, 猶如冰山的一角, 在容器中還包括很多其他的事情要做, 例如, 在容器呼叫相應的 Servlet 的 service 方法之前, 他必須先載入該 Servlet 類, 對使用者進行身份驗證(如果有必要的話), 為使用者更新會話資訊等, 因此, 當你發現容器使用了很多不同的模組來處理這些事情時, 請不要它驚訝, 例如, 管理器模組用來處理使用者會話資訊, 類載入器模組用來載入所需的 Servlet 類.

那麼我們來看看 Tomcat 原始碼的目錄結構和每個目錄是起什麼作用的:

dir


Tomcat 整體的框架層次:4個層次, 其中 Connector 和 Container 是最重要的.

  1. Server 和 Service
  2. Connector
    • HTTP
    • AJP (apache 私有協議,用於tomcat和apache靜態伺服器通訊)
  3. Container
    • Engine
    • Host
    • Context
    • Wrapper
  4. Component
    • Manager (管理器)
    • logger (日誌管理)
    • loader (載入器)
    • pipeline (管道)
    • valve (管道中的閥)
下面我們來看一張有名的關於 tomcat 的結構圖:

tomcat 結構圖

注意: Server是整個Tomcat元件的容器,包含一個或多個Service。 Service:Service是包含Connector和Container的集合,Service用適當的Connector接收使用者的請求,再發給相應的Container來處理。

從這張圖中可以看到,Tomcat的核心元件就兩個Connector和Container(後面還有詳細說明),多個Connector+一個Container構成一個Service,Service就是對外提供服務的元件,有了Service元件Tomcat就可以對外提供服務了,但是光有服務還不行,還得有環境讓你提供服務才行,所以最外層的Server就為Service提供了生存的土壤。那麼這些個元件到底是幹嘛用的呢?Connector是一個聯結器,主要負責接收請求並把請求交給Container,Container就是一個容器,主要裝的是具體處理請求的元件。Service主要是為了關聯Container與Connector,一個單獨的Container或者一個單獨的Connector都不能完整處理一個請求,只有兩個結合在一起才能完成一個請求的處理。Server這是負責管理Service集合,從圖中我們看到一個Tomcat可以提供多種服務,那麼這些Serice就是由Server來管理的,具體的工作包括:對外提供一個介面訪問Service,對內維護Service集合,維護Service集合又包括管理Service的生命週期、尋找一個請求的Service、結束一個Service等


Connector 元件:

Tomcat都是在容器裡面處理問題的, 而容器又到哪裡去取得輸入資訊呢? Connector就是專幹這個的。 他會把從socket傳遞過來的資料, 封裝成Request, 傳遞給容器來處理。 通常我們會用到兩種Connector,一種叫http connectoer, 用來傳遞http需求的。 另一種叫AJP, 在我們整合apache與tomcat工作的時候,apache與tomcat之間就是通過這個協議來互動的。 (說到apache與tomcat的整合工作, 通常我們的目的是為了讓apache 獲取靜態資源, 而讓tomcat來解析動態的jsp或者servlet。)

總的來說,Connector就是解析Http或Ajp請求的。


Container 元件:

我們剛剛看到, 容器從大到小分別是 Engine, Host, Context, Wrapper, 從左到右每個容器都是一對多關係, 也就是說, Engine 容器可以有多個 Host 容器, Host 容器可以有多個 Context 容器. Context 容器可以有多個 Wrapper 容器. 我們來看看每個元件的解釋:

  1. Container:可以理解為處理某型別請求的容器,處理的方式一般為把處理請求的處理器包裝為Valve(閥門)物件,並按一定順序放入型別為Pipeline(管道)的管道里。Container有多種子型別:Engine、Host、Context和Wrapper,這幾種子型別Container依次包含,處理不同粒度的請求。另外Container裡包含一些基礎服務,如Loader、Manager和Realm。
  2. Engine:Engine包含Host和Context,接到請求後仍給相應的Host在相應的Context裡處理。
  3. Host:就是我們所理解的虛擬主機。
  4. Context:就是我們所部屬的具體Web應用的上下文,每個請求都在是相應的上下文裡處理的。
  5. Wrapper:Wrapper是針對每個Servlet的Container,每個Servlet都有相應的Wrapper來管理。 可以看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper這些核心元件的作用範圍是逐層遞減,並逐層包含。 下面就是些被Container所用的基礎元件:
    • Loader:是被Container用來載入各種所需的Class。
    • Manager:是被Container用來管理Session池。
    • Realm:是用來處理安全裡授權與認證。

Component 元件:

需求被傳遞到了容器裡面, 在合適的時候, 會傳遞給下一個容器處理。而容器裡面又盛裝著各種各樣的元件, 我們可以理解為提供各種各樣的增值服務。比如:

  1. manager: 當一個容器裡面裝了manager元件後,這個容器就支援session管理了, 事實上在tomcat裡面的session管理, 就是靠的在context裡面裝的manager component.

  2. logger: 當一個容器裡面裝了logger元件後, 這個容器裡所發生的事情, 就被該元件記錄下來, 我們通常會在logs/ 這個目錄下看見catalina_log.time.txt 以及localhost.time.txt和localhost_examples_log.time.txt。 這就是因為我們分別為:engin, host以及context(examples)這三個容器安裝了logger元件, 這也是預設安裝, 又叫做標配 .

  3. loader: loader這個元件通常只會給我們的context容器使用,loader是用來啟動context以及管理這個context的classloader用的。

  4. pipline: pipeline是這樣一個東西,使用的責任鏈模式. 當一個容器決定了要把從上級傳遞過來的需求交給子容器的時候, 他就把這個需求放進容器的管道(pipeline)裡面去。 而需求傻呼呼得在管道里面流動的時候, 就會被管道里面的各個閥門攔截下來。 比如管道里面放了兩個閥門。 第一個閥門叫做“access_allow_vavle”, 也就是說需求流過來的時候,它會看這個需求是哪個IP過來的, 如果這個IP已經在黑名單裡面了,sure, 殺! 第二個閥門叫做“defaul_access_valve”它會做例行的檢查, 如果通過的話,OK, 把需求傳遞給當前容器的子容器。 就是通過這種方式, 需求就在各個容器裡面傳遞,流動, 最後抵達目的地的了。

  5. valve: 就是上面所說的閥門。

Tomcat裡面大概就是這麼些東西, 我們可以簡單地這麼理解tomcat的框架,它是一種自上而下, 容器裡又包含子容器的這樣一種結構。

換個角度再來看看 Tomcat 的總體架構:

  • 面向元件架構
  • 基於JMX
  • 事件偵聽
  1. 面向元件架構 tomcat程式碼看似很龐大,但從結構上看卻很清晰和簡單,它主要由一堆元件組成,如Server、Service、Connector等,並基於JMX管理這些元件,另外實現以上介面的元件也實現了代表生存期的介面Lifecycle,使其元件履行固定的生存期,在其整個生存期的過程中通過事件偵聽LifecycleEvent實現擴充套件。Tomcat的核心類圖如下所示:
    核心類圖

上面我們已經對每個類的功能進行了一些描述.包括 Catalina 類和 Server 類的作用, Service 類是如何關聯 Connector 和 Container 的. 還有各個容器是上面關係. 包括容器中資料是如何從從管道( pipeline) 中傳遞, 還有一些元件, 比如類載入器, 管理器, 安全主體( realm);

  1. 基於JMX Tomcat會為每個元件進行註冊過程,通過Registry管理起來,而Registry是基於JMX來實現的,因此在看元件的init和start過程實際上就是初始化MBean和觸發MBean的start方法,會大量看到形如: Registry.getRegistry(null, null).invoke(mbeans, "init", false); Registry.getRegistry(null, null).invoke(mbeans, "start", false); 這樣的程式碼,這實際上就是通過JMX管理各種元件的行為和生命期。

那麼, 什麼是 JMX 呢?

JMX 即 Java Management Extensions(JMX 規範), 是用來對 tomcat 進行管理的. tomcat 中的實現是 commons modeler 庫, Catalina 使用這個庫來見哈編寫託管 Bean 的工作. 託管 Bean 就是用來管理 Catalina 中其他物件的 Bean.

  1. 事件偵聽(觀察者模式/事件驅動) 各個元件在其生命期中會有各種各樣行為,而這些行為都有觸發相應的事件,Tomcat就是通過偵聽這些時間達到對這些行為進行擴充套件的目的。在看元件的init和start過程中會看到大量如: lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);這樣的程式碼,這就是對某一型別事件的觸發,如果你想在其中加入自己的行為,就只用註冊相應型別的事件即可。

最後:

我們已經從巨集觀上詳細分析了 Tomcat 的架構和元件等一些抽象的概念, 可能大家還比較懵逼, 因為我們完全是紙上談兵, 沒有真刀真槍實戰原始碼, 所以, 接下來, 讓我們帶著我們準備好的原始碼一起撕開 Tomcat 的衣服, 看看她到底是什麼樣子的.

還是像剛開始說的:

  1. 巨集觀上理解 tomcat 元件及架構設計.
  2. 從一個簡單的例子來理解tomcat的底層設計, 到底是怎麼做的.
  3. 從 tomcat 的各個元件各個擊破, 理解每個元件的設計.
  4. 從2條路線去 debug, 驗證之前的理論是否正確, 並深刻理解程式碼. 第一條路線是啟動過程. 第二條路線是一個 http 請求到 Servlet 的 service() 方法的過程.
  5. 總結 tomcat 的設計.

現在我們已經完成了第一步, 巨集觀上理解了有哪些元件和他的架構設計. 後面的學習我們按照計劃來執行, 不要操之過急, 看原始碼最重要的就是沉的下心. 我們會 先從元件和介面分析, 比如 Connector 和 Container 介面, 還有4大容器介面和管道閥門, 還有 生命週期介面.他們分別使用了什麼設計模式,還有tomcat 鉤子的使用, 包括tomcat著名的類載入機制, 為什麼違背雙親委任模型等等. 這些內容都將是什麼重點關注的.

好了, 今天就到這裡, good luck !!!!!

相關文章