[轉帖]WEB請求處理三:Servlet容器請求處理

https://www.jianshu.com/p/571c474279af

 

0 系列目錄#

本篇文章將給大家講述Servlet容器中請求處理的過程,在給本篇文章起標題時,一直在“應用服務器”與“Servlet容器”這兩者之間拿捏不定,主要是因爲要清晰的區分開這兩者的關係:Servlet容器可以說是應用服務器的一個子集。又由於本文的初衷是講述大家平常使用比較多的Servlet爲主,所以,給本篇就起了《Servlet容器請求處理》的名字。

先說下在整個WEB請求處理過程中,本篇文章講述的是哪個流程模塊。爲直觀明瞭,先上一張圖,紅色部分爲本章所述模塊:

 
紅色部分爲本章所述模塊

所講述的請求流程模塊,大家已經很清楚了。那怎麼給大家去講的更清晰,大家理解的更容易呢?當然是,帶着問題去學習,吸收或許會更快些啦。:)

開篇之前,給大家提以下幾個問題,這些問題是本文的主體思路(也是個人學習路線):

  1. WEB服務器那麼多,Apache、Tomcat、Nginx、Jetty、Resin,名詞那麼多,HTTP Server、Application Server、Web Server、Servlet Container,他們是什麼?之間關係是什麼?區別又在哪?

  2. CGI、WSGI、Servlet、JSP、FastCGI等等,他們是什麼?他們之間區別又在哪?和上面WEB服務器之間關係是什麼?

  3. Servlet生命週期及工作原理是什麼?

  4. HTTP Request進入到Tomcat中執行,請求處理流程如何?如何找到對應的Application並進行請求處理?

1 WEB服務器#

只要Web上的Server都叫Web Server,但是大家分工不同,解決的問題也不同,所以根據Web Server提供的功能,每個Web Server的名字也會不一樣。

按功能分類,Web Server可以分爲:

|- Web Server
        |- Http Server
        |- Application Server
            |- Servlet Container
            |- CGI Server
            |- ......

1.1 Http Server##

HTTP Server本質上也是一種應用程序——它通常運行在服務器之上,綁定服務器的IP地址並監聽某一個tcp端口來接收並處理HTTP請求,這樣客戶端(一般來說是IE, Firefox,Chrome這樣的瀏覽器)就能夠通過HTTP協議來獲取服務器上的網頁(HTML格式)、文檔(PDF格式)、音頻(MP4格式)、視頻(MOV格式)等等資源。下圖描述的就是這一過程:

 

一個HTTP Server關心的是HTTP協議層面的傳輸和訪問控制,所以在Apache/Nginx上你可以看到代理、負載均衡等功能。

  1. 客戶端通過HTTP Server訪問服務器上存儲的靜態資源(HTML文件、圖片文件等等)。
  2. 通過CGI/Servlet技術,也可以將處理過的動態內容通過HTTP Server分發,但是一個HTTP Server始終只是把服務器上的文件如實的通過HTTP協議傳輸給客戶端。

HTTP Server中經常使用的是Apache、Nginx兩種,HTTP Server主要用來做靜態內容服務、代理服務器、負載均衡等。直面外來請求轉發給後面的應用服務(Tomcat,django什麼的)。

|- Http Server
    |- Apache
    |- Nginx

1.1.1 Apache HTTP服務器###

Apache HTTP服務器是一個模塊化的服務器,可以運行在幾乎所有廣泛使用的計算機平臺上。Apache支持模塊多,性能穩定,Apache本身是靜態解析,適合靜態HTML、圖片等,但可以通過擴展腳本、模塊等支持動態頁面等。

Apache可以支持PHPcgiperl,但是要使用Java的話,你需要Tomcat在Apache後臺支撐,將Java請求由Apache轉發給Tomcat處理。

1.1.2 Nginx HTTP服務器###

Nginx是一個高性能的HTTP和反向代理服務器,同時也是一個IMAP/POP3/SMTP代理服務器。

其特點是佔有內存少,併發能力強。Nginx代碼完全用C語言從頭寫成。

具有很高的穩定性。其它HTTP服務器,當遇到訪問的峯值,或者有人惡意發起慢速連接時,也很可能會導致服務器物理內存耗盡頻繁交換,失去響應,只能重啓服務器。例如當前apache一旦上到200個以上進程,web響應速度就明顯非常緩慢了。

而Nginx採取了分階段資源分配技術,使得它的CPU與內存佔用率非常低。Nginx官方表示保持10000個沒有活動的連接,它只佔2.5M內存,所以類似DOS這樣的攻擊對nginx來說基本上是毫無用處的。就穩定性而言,Nginx比Lighthttpd更勝一籌。

1.1.3 Nginx與Apache比較###

Nginx相對於Apache的優點:

  1. 輕量級,同樣啓動WEB服務,比Apache佔用更少的內存以及資源;
  2. 抗併發性能高,核心區別在於Apache是同步多進程模型,一個連接對應一個進程。Nginx是異步的,多個連接(萬級別)可以對應一個進程;
  3. Nginx模塊較少,配置簡單,所以Nginx可以將資源用在數據處理以及進程上面,Apache模塊較多比較全,相對穩定,但在內存資源上消耗比較大;
  4. Nginx可以在不間斷的情況下進行軟件版本的升級;
  5. Nginx處理靜態頁面性能比apache高3倍多;

選擇高併發高性能就選擇Nginx,如果要穩定,選擇Apache,主要根據服務器要面臨的需求而定。

當然,兩者也可以組合使用:

  1. Nginx放前端+apache放後端+MYSQL+PHP:可以提高服務器負載能力
  2. Nginx處理靜態頁面請求如MP3,GIF.JPG.JS,apache處理動態頁面請求,充分結合了二者的優勢;

1.2 Application Server##

Application Server 是一個應用執行的服務器。它首先需要支持開發語言的 Runtime(對於 Tomcat 來說,就是 Java),保證應用能夠在應用服務器上正常運行。其次,需要支持應用相關的規範,例如類庫、安全方面的特性。與HTTP Server相比,Application Server能夠動態的生成資源並返回到客戶端。

|- Application Server
    |- Tomcat
    |- Jetty

當初在Apache Server開發時還未出現Servlet的概念,所以Apache不能內置支持Servlet。實際上,除了Apache,其他許多HTTP Server軟件都不能直接支持Servlet。爲了支持Servlet,通常要單獨開發程序,這種程序一般稱爲服務器小程序容器(Servlet Container),有時也叫做服務器小程序引擎(Servlet Engine)。它是Web服務器或應用程序服務器的一部分,用於在發送的請求和響應之上提供網絡服務,解碼基於MIME的請求,格式化基於MIME的響應,它在Servlet的生命週期內包容和管理Servlet,是一個實時運行的外殼程序。運行時由Web服務器軟件處理一般請求,並把Servlet調用傳遞給“容器”來處理。

比如,對於 Tomcat 來說,就是需要提供 JSP/Sevlet 運行需要的標準類庫、Interface 等。爲了方便,應用服務器往往也會集成 HTTP Server 的功能,但是不如專業的 HTTP Server 那麼強大,所以Application Server往往是運行在 HTTP Server 的背後,執行應用,將動態的內容轉化爲靜態的內容之後,通過 HTTP Server 分發到客戶端。

 
HTTP Server 與 Application Server

Tomcat運行在JVM之上,它和HTTP服務器一樣,綁定IP地址並監聽TCP端口,同時還包含以下指責:

  1. 管理Servlet程序的生命週期;
  2. 將URL映射到指定的Servlet進行處理;
  3. 與Servlet程序合作處理HTTP請求——根據HTTP請求生成HttpServletRequest/Response對象並傳遞給Servlet進行處理,將Servlet中的HttpServletResponse對象生成的內容返回給瀏覽器;

所以 Tomcat 屬於是一個「Application Server」,但是更準確的來說,是一個「Servlet/JSP」應用的容器(Ruby/Python 等其他語言開發的應用也無法直接運行在 Tomcat 上)。

1.2.1 Servlet容器工作模式###

按照工作模式的不同,Servlet容器可以分爲以下3類:

  1. 獨立運行的Servlet容器

在這種模式下,Servlet容器作爲構成Web服務器的一部分而存在。當使用基於Java的Web服務器時,就屬於這種情況。這種方式是Tomcat的默認模式,然而大多數Web服務器並不是基於Java的,所以就產生了下面的兩種其他類型。

  1. 內置的Servlet容器

Servlet容器由Web服務器插件和Java容器兩部分組成。採用這種方式時,Web服務器插件需要在某個Web服務器內部地址空間中打開一個JVM(Java虛擬機),在此JVM上加載Java容器並運行Servlet。如果客戶端調用Servlet,Web服務器插件首先獲得此請求的控制並將它傳遞(使用JNI技術)給Java容器,然後Java容器把此請求交給Servlet來處理。這種方式運行速度較快,並且能夠提供良好的性能,適用於單進程、多線程服務器,但是在伸縮性方面存在不足。

  1. 外置的Servlet容器

採用這種方式時,Servlet容器運行在Web服務器外部地址空間。先由Web服務器插件在某個Web服務器外部地址空間打開一個JVM(Java虛擬機),然後加載Java容器來運行Servlet。Web服務器插件和JVM之間使用IPC(進程間通信)機制(通常是TCP/IPSockets)。如果客戶端調用Servlet,Web服務器插件首先獲得此請求的控制並將它傳遞(使用IPC技術)給Java容器,然後Java容器把此請求交給Servlet來處理。這種方式對客戶端請求的處理速度不如內置Servlet那樣快,但是在其他方面(如可伸縮性、穩定性等)具有優勢。

Tomcat屬於Servlet容器,其工作模式也分爲上述3種,所以Tomcat既可被用作獨立運行的Servlet引擎(便於開發和調試),又可作爲一個需要增強功能的Web服務器(如當前的Apache、IIS和Netscape服務器)插件。在配置Tomcat之前,就需要確定採用哪種工作模式,工作模式(1)比較簡單,直接安裝Tomcat即可,工作模式(2)和(3)有些複雜,除了安裝Tomcat、Web服務器之外,還需要安裝連接兩者的中間連接件。

1.2.2 Apache與Tomcat整合使用###

雖然Tomcat也可以認爲是HTTP服務器,但通常它仍然會和Apache/Nginx配合在一起使用:

  1. 動靜態資源分離——運用Nginx的反向代理功能分發請求:所有動態資源的請求交給Tomcat,而靜態資源的請求(例如圖片、視頻、CSS、JavaScript文件等)則直接由Nginx返回到瀏覽器,這樣能大大減輕Tomcat的壓力;

  2. 負載均衡——當業務壓力增大時,可能一個Tomcat的實例不足以處理,那麼這時可以啓動多個Tomcat實例進行水平擴展,而Nginx的負載均衡功能可以把請求通過算法分發到各個不同的實例進行處理;

整合的好處:

  1. 如果客戶端請求的是靜態頁面,則只需要Apache服務器響應請求。
  2. 如果客戶端請求動態頁面,則是Tomcat服務器響應請求。
  3. 因爲JSP是服務器端解釋代碼的,這樣整合就可以減少Tomcat的服務開銷。

2 什麼是CGI#

如上文所述,HTTP服務器是一個很簡單的東西,並不負責動態網頁的構建,只能轉發靜態網頁。事物總是不斷髮展,網站也越來越複雜,所以出現動態技術。同時Apache也說,它能支持perl,生成動態網頁。這個支持perl,其實是Apache越位了,做了一件額外的事情。

既然HTTP Server自己不能做,外包給別人吧,但是要與第三做個約定,我給你什麼,然後你給我什麼,就是握把請求參數發送給你,然後我接收你的處理結果給客戶端。那這個約定就是Common Gateway Interface,簡稱CGI。

CGI全稱是“通用網關接口”(Common Gateway Interface),是HTTP服務器與你的或其它機器上的程序進行“交談”的一種工具,其程序須運行在網絡服務器上,是一種根據請求信息動態產生響應內容的接口協議。CGI可以用任何一種語言編寫,只要這種語言具有標準輸入、輸出和環境變量。如php,perl,tcl等。

通過CGI,HTTP Server可以將根據請求不同啓動不同的外部程序,並將請求內容轉發給該程序,在程序執行結束後,將執行結果作爲迴應返回給客戶端。也就是說,對於每個請求,都要產生一個新的進程進行處理。因爲每個進程都會佔有很多服務器的資源和時間,這就導致服務器無法同時處理很多的併發請求。另外CGI程序都是與操作系統平臺相關的,雖然在互聯網爆發的初期,CGI爲開發互聯網應用做出了很大的貢獻,但是隨着技術的發展,開始逐漸衰落。

所以,CGI的定義是:外部應用程序與HTTP 服務器之間的接口協議。

2.1 CGI工作原理##

HTTP Server與CGI程序請求處理流程:

 
HTTP Server與CGI程序請求處理流程

HTTP服務器將根據CGI程序的類型決定數據向CGI程序的傳送方式,一般來講是通過標準輸入/輸出流和環境變量來與CGI程序間傳遞數據。 如下圖所示:

 
CGI結構示意圖

CGI程序通過標準輸入(STDIN)和標準輸出(STDOUT)來進行輸入輸出。此外CGI程序還通過環境變量來得到輸入,操作系統提供了許多環境變量,它們定義了程序的執行環境,應用程序可以存取它們。HTTP服務器和CGI接口又另外設置了一些環境變量,用來向CGI程序傳遞一些重要的參數。CGI的GET方法還通過環境變量QUERY-STRING向CGI程序傳遞Form中的數據。

2.2 CGI環境變量##

下面是一些常用的CGI環境變量:

 
CGI環境變量

每當客戶請求CGI的時候,HTTP服務器就請求操作系統生成一個新的CGI解釋器進程(如php-cgi.exe),CGI的一個進程則處理完一個請求後退出,下一個請求來時再創建新進程。當然,這樣在訪問量很少沒有併發的情況也行。可是當訪問量增大,併發存在,這種方式就不適合了。於是就有了FastCGI。

3 什麼是FastCGI#

FastCGI像是一個常駐(long-live)型的CGI,它可以一直執行着,只要激活後,不會每次都要花費時間去fork一次(這是CGI最爲人詬病的fork-and-execute 模式)。它還支持分佈式的運算, 即 FastCGI 程序可以在網站服務器以外的主機上執行並且接受來自其它網站服務器來的請求。

FastCGI是語言無關的、可伸縮架構的CGI開放擴展,其主要行爲是將CGI解釋器進程保持在內存中並因此獲得較高的性能。衆所周知,CGI解釋器的反覆加載是CGI性能低下的主要原因,如果CGI解釋器保持在內存中並接受FastCGI進程管理器調度,則可以提供良好的性能、伸縮性、Fail- Over特性等等。

3.1 FastCGI工作原理##

  1. HTTP Server啓動時載入FastCGI進程管理器(IIS ISAPI或Apache Module);
  2. FastCGI進程管理器自身初始化,啓動多個CGI解釋器進程(可見多個php-cgi)並等待來自HTTP Server的連接;
  3. 當客戶端請求到達HTTP Server時,FastCGI進程管理器選擇並連接到一個CGI解釋器。HTTP Server將CGI環境變量和標準輸入發送到FastCGI子進程php-cgi;
  4. FastCGI子進程完成處理後將標準輸出和錯誤信息從同一連接返回HTTP Server。當FastCGI子進程關閉連接時,請求便告處理完成。FastCGI子進程接着等待並處理來自FastCGI進程管理器(運行在HTTP Server中)的下一個連接。在CGI模式中,php-cgi在此便退出了。

在上述情況中,你可以想象CGI通常有多慢。每一個Web請求PHP都必須重新解析php.ini、重新載入全部擴展並重初始化全部數據結構。使用FastCGI,所有這些都只在進程啓動時發生一次。一個額外的好處是,持續數據庫連接(Persistent database connection)可以工作。

3.2 FastCGI與CGI特點##

  1. 如CGI,FastCGI也具有語言無關性;
  2. 如CGI,FastCGI在進程中的應用程序,獨立於核心web服務器運行,提供了一個比API更安全的環境。(API把應用程序的代碼與核心的web服務器鏈接在一起,這意味着在一個錯誤的API的應用程序可能會損壞其他應用程序或核心服務器; 惡意的API的應用程序代碼甚至可以竊取另一個應用程序或核心服務器的密鑰。)
  3. FastCGI技術目前支持語言有:C/C++、Java、Perl、Tcl、Python、SmallTalk、Ruby等。相關模塊在Apache, ISS, Lighttpd等流行的服務器上也是可用的。
  4. 如CGI,FastCGI的不依賴於任何Web服務器的內部架構,因此即使服務器技術的變化, FastCGI依然穩定不變。

4 什麼是PHP-CGI#

PHP-CGI是PHP自帶的FastCGI管理器。PHP-CGI的不足:

  1. PHP-CGI變更php.ini配置後,需重啓PHP-CGI才能讓新的php-ini生效,不可以平滑重啓;
  2. 直接殺死PHP-CGI進程,php就不能運行了。(PHP-FPM和Spawn-FCGI就沒有這個問題,守護進程會平滑從新生成新的子進程。)

5 什麼是PHP-FPM#

PHP-FPM是一個PHP FastCGI管理器,是隻用於PHP的,使用PHP-FPM來控制PHP-CGI的FastCGI進程,它負責管理一個進程池,來處理來自Web服務器的請求。可以在 http://php-fpm.org/download 下載得到。

相對Spawn-FCGI,PHP-FPM在CPU和內存方面的控制都更勝一籌,而且前者很容易崩潰,必須用crontab進行監控,而PHP-FPM則沒有這種煩惱。

PHP-FPM提供了更好的PHP進程管理方式,可以有效控制內存和進程、可以平滑重載PHP配置,比Spawn-FCGI具有更多優點,所以被PHP官方收錄了。在PHP 5.3.3中可以直接使用PHP-FPM了。

在./configure的時候帶 –enable-fpm參數即可開啓PHP-FPM。

5.1 PHP-FPM工作原理##

Apache+PHP配合使用,會在Apache配置下面一段:

LoadModule php5_module C:/php/php5apache2_2.dll

當PHP需要在Apache服務器下運行時,一般來說,它可以模塊的形式集成,此時模塊的作用是接收Apache傳遞過來的PHP文件請求,並處理這些請求,然後將處理後的結果返回給Apache。如果我們在Apache啓動前在其配置文件中配置好了PHP模塊,PHP模塊通過註冊apache2的ap_hook_post_config掛鉤,在Apache啓動的時候啓動此模塊以接受PHP文件的請求。

Apache的Hook機制是指:Apache允許模塊(包括內部模塊和外部模塊,例如mod_php5.so,mod_perl.so等)將自定義的函數注入到請求處理循環中。換句話說,模塊可以在Apache的任何一個處理階段中掛接(Hook)上自己的處理函數,從而參與Apache的請求處理過程。mod_php5.so/php5apache2.dll就是將所包含的自定義函數,通過Hook機制注入到Apache中,在Apache處理流程的各個階段負責處理php請求。

有人測試Nginx+PHP-FPM在高併發情況下可能會達到Apache+mod_php5的5~10倍,現在Nginx+PHP-FPM使用的人越來越多。

6 什麼是Spawn-FCGI#

Spawn-FCGI是一個通用的FastCGI管理服務器,它是lighttpd中的一部份,很多人都用Lighttpd的Spawn-FCGI進行FastCGI模式下的管理工作,不過有不少缺點。而PHP-FPM的出現多少緩解了一些問題,但PHP-FPM有個缺點就是要重新編譯,這對於一些已經運行的環境可能有不小的風險(refer)。

Spawn-FCGI目前已經獨成爲一個項目,更加穩定一些,也給很多Web 站點的配置帶來便利。已經有不少站點將它與nginx搭配來解決動態網頁。

6.1 PHP-FPM與Spawn-CGI對比##

PHP-FPM、Spawn-FCGI都是守護PHP-CGI的進程管理器。

PHP-FPM的使用非常方便,配置都是在PHP-FPM.ini的文件內,而啓動、重啓都可以從php/sbin/PHP-FPM中進行。更方便的是修改php.ini後可以直接使用PHP-FPM reload進行加載,無需殺掉進程就可以完成php.ini的修改加載。使用PHP-FPM可以使PHP有不小的性能提升。PHP-FPM控制的進程CPU回收的速度比較慢,內存分配的很均勻。

Spawn-FCGI控制的進程CPU下降的很快,而內存分配的比較不均勻。有很多進程似乎未分配到,而另外一些卻佔用很高。可能是由於進程任務分配的不均勻導致的。而這也導致了總體響應速度的下降。而PHP-FPM合理的分配,導致總體響應的提到以及任務的平均。

7 什麼是Servlet#

Servlet最初是在1995年由James Gosling提出的,因爲使用該技術需要複雜的Web服務器支持,所以當時並沒有得到重視,也就放棄了。後來隨着Web應用複雜度的提升,並要求提供更高的併發處理能力,Servlet被重新撿起,並在Java平臺上得到實現,現在提起Servlet,指的都是Java Servlet。Java Servlet要求必須運行在Web服務器當中,與Web服務器之間屬於分工和互補關係。確切的說,在實際運行的時候Java Servlet與Web服務器會融爲一體,如同一個程序一樣運行在同一個Java虛擬機(JVM)當中。與CGI不同的是,Servlet對每個請求都是單獨啓動一個線程,而不是進程。這種處理方式大幅度地降低了系統裏的進程數量,提高了系統的併發處理能力。另外因爲Java Servlet是運行在虛擬機之上的,也就解決了跨平臺問題。如果沒有Servlet的出現,也就沒有互聯網的今天。

在Servlet出現之後,隨着使用範圍的擴大,人們發現了它的一個很大的一個弊端。那就是爲了能夠輸出HTML格式內容,需要編寫大量重複代碼,造成不必要的重複勞動。爲了解決這個問題,基於Servlet技術產生了JavaServet Pages技術,也就是JSP。Servlet和JSP兩者分工協作,Servlet側重於解決運算和業務邏輯問題,JSP則側重於解決展示問題。Servlet與JSP一起爲Web應用開發帶來了巨大的貢獻,後來出現的衆多Java Web應用開發框架都是基於這兩種技術的,更確切的說,都是基於Servlet技術的。

7.1 Servlet生命週期##

作爲一名專業編程人員,您碰到的大多數 Java servlet 都是爲響應 Web 應用程序上下文中的 HTTP 請求而設計的。因此,javax.servlet 和 javax.servlet.http 包中特定於 HTTP 的類是您應該關心的。對於Servlet容器(Tomcat)與HttpServlet是怎樣進行交互的呢,看下類圖:

 
Java Servlet 類圖

Servlet的框架是由兩個Java包組成的:javax.servlet與javax.servlet.http。在javax.servlet包中定義了所有的Servlet類都必須實現或者擴展的通用接口和類。在javax.servlet.http包中定義了採用Http協議通信的HttpServlet類。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必須實現這個接口。在Servlet接口中定義了5個方法,其中3個方法代表了Servlet的生命週期:

  1. init(ServletConfig)方法:負責初始化Servlet對象,在Servlet的生命週期中,該方法執行一次;該方法執行在單線程的環境下,因此開發者不用考慮線程安全的問題;
  2. service(ServletRequest req,ServletResponse res)方法:負責響應客戶的請求;爲了提高效率,Servlet規範要求一個Servlet實例必須能夠同時服務於多個客戶端請求,即service()方法運行在多線程的環境下,Servlet開發者必須保證該方法的線程安全性;
  3. destroy()方法:當Servlet對象退出生命週期時,負責釋放佔用的資源;

編程注意事項說明:

  1. 當Server Thread線程執行Servlet實例的init()方法時,所有的Client Service Thread線程都不能執行該實例的service()方法,更沒有線程能夠執行該實例的destroy()方法,因此Servlet的init()方法是工作在單線程的環境下,開發者不必考慮任何線程安全的問題。
  2. 當服務器接收到來自客戶端的多個請求時,服務器會在單獨的Client Service Thread線程中執行Servlet實例的service()方法服務於每個客戶端。此時會有多個線程同時執行同一個Servlet實例的service()方法,因此必須考慮線程安全的問題。
  3. 請大家注意,雖然service()方法運行在多線程的環境下,並不一定要同步該方法。而是要看這個方法在執行過程中訪問的資源類型及對資源的訪問方式。分析如下:

如果service()方法沒有訪問Servlet的成員變量也沒有訪問全局的資源比如靜態變量、文件、數據庫連接等,而是隻使用了當前線程自己的資源,比如非指向全局資源的臨時變量、request和response對象等。該方法本身就是線程安全的,不必進行任何的同步控制。

如果service()方法訪問了Servlet的成員變量,但是對該變量的操作是隻讀操作,該方法本身就是線程安全的,不必進行任何的同步控制。

如果service()方法訪問了Servlet的成員變量,並且對該變量的操作既有讀又有寫,通常需要加上同步控制語句。

如果service()方法訪問了全局的靜態變量,如果同一時刻系統中也可能有其它線程訪問該靜態變量,如果既有讀也有寫的操作,通常需要加上同步控制語句。

如果service()方法訪問了全局的資源,比如文件、數據庫連接等,通常需要加上同步控制語句。

在創建一個 Java servlet 時,一般需要子類 HttpServlet。該類中的方法允許您訪問請求和響應包裝器(wrapper),您可以用這個包裝器來處理請求和創建響應。大多數程序員都知道Servlet的生命週期,簡單的概括這就分爲四步:

Servlet類加載--->實例化--->服務--->銷燬;

 
Servlet生命週期

創建Servlet對象的時機:

  1. 默認情況下,在Servlet容器啓動後:客戶首次向Servlet發出請求,Servlet容器會判斷內存中是否存在指定的Servlet對象,如果沒有則創建它,然後根據客戶的請求創建HttpRequest、HttpResponse對象,從而調用Servlet對象的service方法;
  2. Servlet容器啓動時:當web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素時,Servlet容器在啓動web服務器時,將按照順序創建並初始化Servlet對象;
  3. Servlet的類文件被更新後,重新創建Servlet。Servlet容器在啓動時自動創建Servlet,這是由在web.xml文件中爲Servlet設置的<load-on-startup>屬性決定的。從中我們也能看到同一個類型的Servlet對象在Servlet容器中以單例的形式存在;

注意:在web.xml文件中,某些Servlet只有<serlvet>元素,沒有<servlet-mapping>元素,這樣我們無法通過url的方式訪問這些Servlet,這種Servlet通常會在<servlet>元素中配置一個<load-on-startup>子元素,讓容器在啓動的時候自動加載這些Servlet並調用init(ServletConfig config)方法來初始化該Servlet。其中方法參數config中包含了Servlet的配置信息,比如初始化參數,該對象由服務器創建。

銷燬Servlet對象的時機:

Servlet容器停止或者重新啓動:Servlet容器調用Servlet對象的destroy方法來釋放資源。以上所講的就是Servlet對象的生命週期。那麼Servlet容器如何知道創建哪一個Servlet對象?Servlet對象如何配置?實際上這些信息是通過讀取web.xml配置文件來實現的。

<servlet>
    <!-- Servlet對象的名稱 -->
    <servlet-name>action<servlet-name>
    <!-- 創建Servlet對象所要調用的類 -->
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
        <!-- 參數名稱 -->
        <param-name>config</param-name>
        <!-- 參數值 -->
        <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
        <param-name>detail</param-name>
        <param-value>2</param-value>
    </init-param>
    <init-param>
        <param-name>debug</param-name>
        <param-value>2</param-value>
    </init-param>
    <!-- Servlet容器啓動時加載Servlet對象的順序 -->
    <load-on-startup>2</load-on-startup>
</servlet>
<!-- 要與servlet中的servlet-name配置節內容對應 -->
<servlet-mapping>
    <servlet-name>action</servlet-name>
    <!-- 客戶訪問的Servlet的相對URL路徑 -->
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

當Servlet容器啓動的時候讀取<servlet>配置節信息,根據<servlet-class>配置節信息創建Servlet對象,同時根據<init-param>配置節信息創建HttpServletConfig對象,然後執行Servlet對象的init方法,並且根據<load-on-startup>配置節信息來決定創建Servlet對象的順序,如果此配置節信息爲負數或者沒有配置,那麼在Servlet容器啓動時,將不加載此Servlet對象。當客戶訪問Servlet容器時,Servlet容器根據客戶訪問的URL地址,通過<servlet-mapping>配置節中的<url-pattern>配置節信息找到指定的Servlet對象,並調用此Servlet對象的service方法。

在整個Servlet的生命週期過程中,創建Servlet實例、調用實例的init()和destroy()方法都只進行一次,當初始化完成後,Servlet容器會將該實例保存在內存中,通過調用它的service()方法,爲接收到的請求服務。下面給出Servlet整個生命週期過程的UML序列圖,如圖所示:

 
Servlet生命週期UML序列圖

如果需要讓Servlet容器在啓動時即加載Servlet,可以在web.xml文件中配置<load-on-startup>元素。

7.2 Servlet工作原理##

上面描述了Servlet的生命週期,接着我們描述一下Tomcat與Servlet是如何工作的,首先看下面的時序圖:

 
Servlet工作原理時序圖
  1. Web Client 向Servlet容器(Tomcat)發出Http請求;
  2. Servlet容器接收Web Client的請求;
  3. Servlet容器創建一個HttpRequest對象,將Web Client請求的信息封裝到這個對象中;
  4. Servlet容器創建一個HttpResponse對象;
  5. Servlet容器調用HttpServlet對象的service方法,把HttpRequest對象與HttpResponse對象作爲參數傳給 HttpServlet對象;
  6. HttpServlet調用HttpRequest對象的有關方法,獲取Http請求信息;
  7. HttpServlet調用HttpResponse對象的有關方法,生成響應數據;
  8. Servlet容器把HttpServlet的響應結果傳給Web Client;

7.3 CGI與Servlet比較##

CGI應用開發比較困難,因爲它要求程序員有處理參數傳遞的知識,這不是一種通用的技能。CGI不可移植,爲某一特定平臺編寫的CGI應用只能運行於這一環境中。每一個CGI應用存在於一個由客戶端請求激活的進程中,並且在請求被服務後被卸載。這種模式將引起很高的內存、CPU開銷,而且在同一進程中不能服務多個客戶。

Servlet對CGI的最主要優勢在於一個Servlet被客戶端發送的第一個請求激活,然後它將繼續運行於後臺,等待以後的請求。每個請求將生成一個新的線程,而不是一個完整的進程。多個客戶能夠在同一個進程中同時得到服務。一般來說,Servlet進程只是在Web Server卸載時被卸載。

Servlet提供了Java應用程序的所有優勢——可移植、穩健、易開發。使用Servlet Tag技術,Servlet能夠生成嵌於靜態HTML頁面中的動態內容。

綜上,Servlet處於服務器進程中,它通過多線程方式運行其service方法,一個實例可以服務於多個請求,並且其實例一般不會銷燬。 而CGI對每個請求都產生新的進程,服務完成後就銷燬,所以效率上低於Servlet。

CGI與Servlet的對比:

對比一:當用戶瀏覽器發出一個Http/CGI的請求,或者說調用一個CGI程序的時候,服務器端就要新啓用一個進程(而且是每次都要調用),調用CGI程序越多(特別是訪問量高的時候),就要消耗系統越多的處理時間,只剩下越來越少的系統資源,對於用戶來說,只能是漫長的等待服務器端的返回頁面了,這對於電子商務激烈發展的今天來說,不能不說是一種技術上的遺憾。

而Servlet充分發揮了服務器端的資源並高效的利用。每次調用Servlet時並不是新啓用一個進程,而是在一個Web服務器的進程中共享和分離線程,而線程最大的好處在於可以共享一個數據源,使系統資源被有效利用。

對比二:傳統的CGI程序,不具備平臺無關性特徵,系統環境發生變化,CGI程序就要癱瘓,而Servlet具備Java的平臺無關性,在系統開發過程中保持了系統的可擴展性、高效性。

對比三:傳統技術中,一般大都爲二層的系統架構,即Web服務器+數據庫服務器,導致網站訪問量大的時候,無法克服CGI程序與數據庫建立連接時速度慢的瓶頸,從而死機、數據庫死鎖現象頻繁發生。而Servlet有連接池的概念,它可以利用多線程的優點,在系統緩存中事先建立好若干與數據庫的連接,到時候若想和數據庫打交道可以隨時跟系統"要"一個連接即可,反應速度可想而知。

8 Tomcat工作原理#

Tomcat 的結構很複雜,但是 Tomcat 也非常的模塊化,找到了 Tomcat 最核心的模塊,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的總體結構圖:

 
Tomcat的總體結構圖

從上圖可以看出Tomcat的核心是兩個組件:連接器(Connector)和容器(Container)。Connector組件是負責生成請求對象和響應對象的,Tomcat默認的是HttpConnector,負責根據收到的Http請求報文生成Request對象和Response對象,並把這兩個對象傳遞給Container,然後根據Response中的內容生成相應的HTTP報文。

Container是容器的父接口,所有子容器都必須實現這個接口,簡單來說就是服務器部署的項目是運行在Container中的。Container裏面的項目獲取到Connector傳遞過來對應的的Request對象和Response對象進行相應的操作。

Connector可以根據不同的設計和應用場景進行替換。一個Container可以選擇對應多個Connector。多個Connector和一個Container就形成了一個Service,有了Service就可以對外提供服務了。

Tomcat要爲一個Servlet的請求提供服務,需要做三件事:

  1. 創建一個request對象並填充那些有可能被所引用的Servlet使用的信息,如參數,頭部、cookies、查詢字符串等。一個request對象就是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一個實例。
  2. 創建一個response對象,所引用的servlet使用它來給客戶端發送響應。一個response對象是javax.servlet.ServletResponse或javax.servlet.http.ServletResponse接口的一個實例。
  3. 調用servlet的service方法,並傳入request和response對象。這裏servlet會從request對象取值,給response寫值。
  4. 根據servlet返回的response生成相應的HTTP響應報文。

既然我們已經抓到Tomcat的“七寸”,兩個核心組件:連接器(Connector)和容器(Container),那這樣從連接器(Connector)入手,來看下Tomcat處理HTTP請求的流程。

很多開源應用服務器都是集成tomcat作爲web container的,而且對於tomcat的servlet container這部分代碼很少改動。這樣,這些應用服務器的性能基本上就取決於Tomcat處理HTTP請求的connector模塊的性能。

8.1 Connector種類##

Tomcat源碼中與connector相關的類位於org.apache.coyote包中,Connector分爲以下幾類:

Http Connector,基於HTTP協議,負責建立HTTP連接。它又分爲BIO Http Connector與NIO Http Connector兩種,後者提供非阻塞IO與長連接Comet支持。

AJP Connector,基於AJP協議,AJP是專門設計用來爲tomcat與http服務器之間通信專門定製的協議,能提供較高的通信速度和效率。如與Apache服務器集成時,採用這個協議。

APR HTTP Connector,用C實現,通過JNI調用的。主要提升對靜態資源(如HTML、圖片、CSS、JS等)的訪問性能。現在這個庫已獨立出來可用在任何項目中。Tomcat在配置APR之後性能非常強勁。

8.2 Connector配置##

對Connector的配置位於conf/server.xml文件中。

8.2.1 BIO HTTP/1.1 Connector配置###

<Connector port=”8080” protocol=”HTTP/1.1” maxThreads=”150” 
    connectionTimeout=”20000” redirectPort=”8443” />

其它一些重要屬性如下:

acceptCount : 接受連接request的最大連接數目,默認值是10;

address : 綁定IP地址,如果不綁定,默認將綁定任何IP地址;

allowTrace : 如果是true,將允許TRACE HTTP方法;

compressibleMimeTypes : 各個mimeType, 以逗號分隔,如text/html,text/xml;

compression : 如果帶寬有限的話,可以用GZIP壓縮;

connectionTimeout : 超時時間,默認爲60000ms (60s);

maxKeepAliveRequest : 默認值是100;

maxThreads : 處理請求的Connector的線程數目,默認值爲200;

如果是SSL配置,如下:

<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true" 
    maxThreads="150" scheme="https" secure="true" 
    clientAuth="false" sslProtocol = "TLS" 
    address="0.0.0.0" 
    keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks" 
    keystorePass="changeit" />

其中,keystoreFile爲證書位置,keystorePass爲證書密碼。

8.2.2 NIO HTTP/1.1 Connector配置###

<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11NioProtocol” 
    maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443” />

8.2.3 Native APR Connector配置###

  1. ARP是用C/C++寫的,對靜態資源(HTML,圖片等)進行了優化。所以要下載本地庫tcnative-1.dll與openssl.exe,將其放在%tomcat%\bin目錄下。

下載地址是:http://tomcat.heanet.ie/native/1.1.10/binaries/win32/

  1. 在server.xml中要配置一個Listener,如下圖。這個配置tomcat是默認配好的。
<!--APR library loader. Documentation at /docs/apr.html --> 
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  1. 配置使用APR connector
<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11AprProtocol” 
    maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443” />
  1. 如果配置成功,啓動tomcat,會看到如下信息:

org.apache.coyote.http11.Http11AprProtocol init

8.3 Tomcat架構模塊##

 
Tomcat架構模塊
  1. Server(服務器)是Tomcat構成的頂級構成元素,所有一切均包含在Server中,Server的實現類StandardServer可以包含一個到多個Services;
  2. 次頂級元素Service的實現類爲StandardService調用了容器(Container)接口,其實是調用了Servlet Engine(引擎),而且StandardService類中也指明瞭該Service歸屬的Server;
  3. 接下來次級的構成元素就是容器(Container):主機(Host)、上下文(Context)和引擎(Engine)均繼承自Container接口,所以它們都是容器。但是,它們是有父子關係的,在主機(Host)、上下文(Context)和引擎(Engine)這三類容器中,引擎是頂級容器,直接包含是主機容器,而主機容器又包含上下文容器,所以引擎、主機和上下文從大小上來說又構成父子關係,雖然它們都繼承自Container接口。
  4. 連接器(Connector)將Service和Container連接起來,首先它需要註冊到一個Service,它的作用就是把來自客戶端的請求轉發到Container(容器),這就是它爲什麼稱作連接器的原因。

8.4 Tomcat運行流程##

 
Tomcat運行流程

假設來自客戶的請求爲:http://localhost:8080/test/index.jsp

  1. 請求被髮送到本機端口8080,被在那裏偵聽的Coyote HTTP/1.1 Connector獲得;
  2. Connector把該請求交給它所在的Service的Engine來處理,並等待Engine的迴應;
  3. Engine獲得請求localhost:8080/test/index.jsp,匹配它所有虛擬主機Host;
  4. Engine匹配到名爲localhost的Host(即使匹配不到也把請求交給該Host處理,因爲該Host被定義爲該Engine的默認主機);
  5. localhost Host獲得請求/test/index.jsp,匹配它所擁有的所有Context;
  6. Host匹配到路徑爲/test的Context(如果匹配不到就把該請求交給路徑名爲""的Context去處理);
  7. path="/test"的Context獲得請求/index.jsp,在它的mapping table中尋找對應的servlet;
  8. Context匹配到URL PATTERN爲*.jsp的servlet,對應於JspServlet類;
  9. 構造HttpServletRequest對象和HttpServletResponse對象,作爲參數調用JspServlet的doGet或doPost方法;
  10. Context把執行完了之後的HttpServletResponse對象返回給Host;
  11. Host把HttpServletResponse對象返回給Engine;
  12. Engine把HttpServletResponse對象返回給Connector;
  13. Connector把HttpServletResponse對象返回給客戶browser;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章