PHP如何解決網站大流量與高并發的問題(二)
827
2022-05-30
Tomcat是什么
開源的 Java Web 應用服務器,實現了 Java EE(Java Platform Enterprise Edition)的部分技術規范,比如 Java Servlet、Java Server Page、JSTL、Java WebSocket 。Java EE 是 Sun 公 司為企業級應用推出的標準平臺,定義了一系列用于企業級開發的技術規范,除了上述的之外,還有 EJB、Java Mail、JPA、JTA、JMS 等,而這些都依賴具體容器的實現。
上圖對比了 Java EE 容器的實現情況,Tomcat 和 Jetty 都只提供了 Java Web 容器必需的 Servlet 和 JSP 規范,開發者要想實現其他的功能,需要自己依賴其他開源實現。
Glassfish 是由 sun 公司推出,Java EE 最新規范出來之后,首先會在 Glassfish 上進行實 現,所以是研究 Java EE 最新技術的首選。
最常見的情況是使用 Tomcat 作為 Java Web 服務器,使用 Spring 提供的開箱即用的強大 的功能,并依賴其他開源庫來完成負責的業務功能實現。
Servlet容器
Tomcat 組成如下圖 :
主要有 Container 和 Connector 以及相關組件構成。
Server :指的就是整個 Tomcat 服務器,包含多組服務,負責管理和啟動各個Service,同時** 8005 端口發過來的 shutdown 命令,用于關閉整個容器 ;
Service :Tomcat 封裝的、對外提供完整的、基于組件的 web 服務, 包含 Connectors、Container 兩個核心組件,以及多個功能組件,各 個 Service 之間是獨立的,但是共享同一 JVM 的資源 ;
Connector :Tomcat 與外部世界的連接器,**固定端口接收外部請求,傳遞給 Container,并將 Container 處理的結果返回給外部;
Container :Catalina,Servlet 容器,內部有多層容器組成,用于管理 Servlet 生命周期,調用 servlet 相關方法。
Loader :封裝了 Java ClassLoader ,用于 Container 加載類文件;
Realm: Tomcat 中為 web 應用程序提供訪問認證和角色管理的機制;
JMX :Java SE 中定義技術規范,是一個為應用程序、設備、系統等植入管理功能的框架,通過 JMX 可以遠程監控 Tomcat 的運行狀態;
Jasper :Tomcat 的 Jsp 解析引擎,用于將 Jsp 轉換成 Java 文件,并編譯成 class 文件。
Session: 負責管理和創建 session,以及 Session 的持久化(可自定義),支持 session 的集群。
Pipeline :在容器中充當管道的作用,管道中可以設置各種 valve(閥門),請求和響應在經由管 道中各個閥門處理,提供了一種 靈活可 配置的處理請求和響應的機制。
Naming :命名服務,JNDI, Java 命名和目錄接口,是一組在 Java 應用中訪問命名和目錄服務的 API。命名服務將名稱和對象聯系起來,使得我們可以用名 稱訪問對象,目錄服務也是一種命名 服務,對象不但有名稱,還有屬性。Tomcat 中可以使用 JNDI 定義數據源、配置信息,用于開發 與部署的分離。
Container組成
Engine: Servlet 的頂層容器,包含一個或多個 Host 子容器;
Host: 虛擬主機,負責 web 應用的部署和 Context 的創建;
Context: Web 應用上下文,包含多個 Wrapper,負責 web 配置的解析、管理所有的 Web 資源;
Wrapper: 最底層的容器,是對 Servlet 的封裝,負責 Servlet 實例的創建、執行和銷毀。
生命周期管理
Tomcat 為了方便管理組件和容器的生命周期,定義了從創建、啟動、到停止、銷毀共 12 中狀態,tomcat 生命周期管理了內部狀態變化的規則控制,組件和容器只需實現相應的生命周期 方法即可完成各生命周期內的操作( initInternal 、 startInternal 、 stopInternal 、 destroyInternal );
比如執行初始化操作時,會判斷當前狀態是否 New,如果不是則拋出生命周期異常;是的 話則設置當前狀態為 Initializing,并執行 initInternal 方法,由子類實現,方法執行成功則設置當 前狀態為 Initialized,執行失敗則設置為 Failed 狀態;
Tomcat 的生命周期管理引入了事件機制,在組件或容器的生命周期狀態發生變化時會通 知事件 **器,**器通過判斷事件的類型來進行相應的操作。
事件**器的添加可以在 server.xml 文件中進行配置;
Tomcat 各類容器的配置過程就是通過添加 listener 的方式來進行的,從而達到配置邏輯與 容器的解耦。如 EngineConfig 、 HostConfig 、 ContextConfig 。
EngineConfig :主要打印啟動和停止日志
HostConfig :主要處理部署應用,解析應用 META-INF/context.xml 并創建應用的 Context ContextConfig :主要解析并合并 web.xml,掃描應用的各類 web 資源 (filter、servlet、listener)
Tomcat 的啟動過程
啟動從 Tomcat 提供的 start.sh 腳本開始,shell 腳本會調用 Bootstrap 的 main 方法,實際 調用了 Catalina 相應的 load、start 方法。
load 方法會通過 Digester 進行 config /server.xml 的解析,在解析的過程中會根據 xml 中的關系 和配置信息來創建容器,并設置相關的屬性。接著 Catalina 會調用 StandardServer 的 init 和 start 方法進行容器的初始化和啟動。
按照 xml 的配置關系,server 的子元素是 service,service 的子元素是頂層容器 Engine,每層容器有持有自己的子容器,而這些元素都實現了生命周期管理 的各個方法,因此就很容易的完成整個容器的啟動、關閉等生命周期的管理。
StandardServer 完成 init 和 start 方法調用后,會一直**來自 8005 端口(可配置),如果接收 到 shutdown 命令,則會退出循環**,執行后續的 stop 和 destroy 方法,完成 Tomcat 容器的 關閉。同時也會調用 JVM 的 Runtime.getRuntime () ? . addShutdownHook 方法,在虛擬機意外退 出的時候來關閉容器。
所有容器都是繼承自 ContainerBase ,基類中封裝了容器中的重復工作,負責啟動容器相關的組 件 Loader、Logger、Manager、Cluster、Pipeline,啟動子容器(線程池并發啟動子容器,通過 線程池 submit 多個線程,調用后返回 Future 對象,線程內部啟動子容器,接著調用 Future 對象 的 get 方法來等待執行結果)。
-
Java 代碼
01
List
02
for ? ( int ? i =?0; i < children.length ; i ++) {
03
results.add(startStopExecutor.submit(new ? StartChild(children[i])));
04
}
05
boolean ? fail =?false;
06
for ? (Future
07
try ? {
08
result.get ();
09
}?catch ? (Exception e) {
10
log.error(sm.getString("containerBase.threadedStartFailed"), e);
11
fail =?true;
12
}
13
}
Web 應用的部署方式
注: catalina.home :安裝目錄; catalina.base :工作目錄;默認值 user. dir
? Server.xml 配置 Host 元素,指定 appBase 屬性,默認\$ catalina.base / webapps /
? Server.xml 配置 Context 元素,指定 docBase ,元素,指定 web 應用的路徑
? 自定義配置:在\$ catalina.base / EngineName / HostName /XXX.xml 配置 Context 元素
HostConfig **了 StandardHost 容器的事件,在 start 方法中解析上述配置文件:
? 掃描 appbase 路徑下的所有文件夾和 war 包,解析各個應用的 META-INF/context.xml,并 創建 StandardContext ,并將 Context 加入到 Host 的子容器中。
? 解析$ catalina.base / EngineName / HostName /下的所有 Context 配置,找到相應 web 應 用的位置,解析各個應用的 META-INF/context.xml,并創建 StandardContext ,并將 Context 加入到 Host 的子容器中。
注:
? HostConfig 并沒有實際解析 Context.xml,而是在 ContextConfig 中進行的。
? HostConfig 中會定期檢查 watched 資源文件(context.xml 配置文件)
ContextConfig 解析 context.xml 順序:
? 先解析全局的配置 config /context.xml
? 然后解析 Host 的默認配置 EngineName / HostName / context.xml.default
? 最后解析應用的 META-INF/context.xml
ContextConfig 解析 web.xml 順序:
? 先解析全局的配置 config /web.xml
? 然后解析 Host 的默認配置 EngineName / HostName / web.xml.default 接著解析應用的 MEB-INF/web.xml
? 掃描應用 WEB-INF/lib/下的 jar 文件,解析其中的 META-INF/web-fragment.xml 最后合并 xml 封裝成 WebXml ,并設置 Context
注:
? 掃描 web 應用和 jar 中的注解(Filter、Listener、Servlet)就是上述步驟中進行的。
? 容器的定期執行: backgroundProcess ,由 ContainerBase 來實現的,并且只有在頂層容器 中才會開啟線程。( backgroundProcessorDelay =10 標志位來控制)
Servlet 生命周期
Servlet 是用 Java 編寫的服務器端程序。其主要功能在于交互式地瀏覽和修改數據,生成動態 Web 內容。
(1)請求到達 server 端,server 根據 url 映射到相應的 Servlet
(2)判斷 Servlet 實例是否存在,不存在則加載和實例化 Servlet 并調用 init 方法
()30Server 分別創建 Request 和 Response 對象,調用 Servlet 實例的 service 方法(service 方法 內部會根據 http 請求方法類型調用相應的 doXXX 方法)
(4) doXXX 方法內為業務邏輯實現,從 Request 對象獲取請求參數,處理完畢之后將結果通過 response 對象返回給調用方
(5)當 Server 不再需要 Servlet 時(一般當 Server 關閉時),Server 調用 Servlet 的 destroy() 方 法。
load on startup
當值為 0 或者大于 0 時,表示容器在應用啟動時就加載這個 servlet; 當是一個負數時或者沒有指定時,則指示容器在該 servlet 被選擇時才加載; 正數的值越小,啟動該 servlet 的優先級越高;
single thread model
每次訪問 servlet,新建 servlet 實體對象,但并不能保證線程安全,同時 tomcat 會限制 servlet 的實例數目
最佳實踐:不要使用該模型,servlet 中不要有全局變量
請求處理過程
(1)根據 server.xml 配置的指定的 connector 以及端口** http、或者 ajp 請求
(2)請求到來時建立連接,解析請求參數,創建 Request 和 Response 對象,調用頂層容器 pipeline 的 invoke 方法
(3)容器之間層層調用,最終調用業務 servlet 的 service 方法
(4)Connector 將 response 流中的數據寫到 socket 中
Pipeline 與 Valve
Pipeline 可以理解為現實中的管道,Valve 為管道中的閥門,Request 和 Response 對象在管道中 經過各個閥門的處理和控制。
每個容器的管道中都有一個必不可少的 basic valve,其他的都是可選的,basic valve 在管道中最 后調用,同時負責調用子容器的第一個 valve。
Valve 中主要的三個方法: setNext 、 getNext 、 invoke;valve 之間的關系是單向鏈式結構,本身 invoke 方法中會調用下一個 valve 的 invoke 方法。
各層容器對應的 basic valve 分別是 StandardEngineValve 、 StandardHostValve 、 StandardContextValve 、 StandardWrapperValve 。
JSP引擎
JSP 生命周期
? 編譯階段:servlet 容器編譯 servlet 源文
件,生成 servlet 類
? 初始化階段:加載與 JSP 對應的 servlet 類, 創建其實例,并調用它的初始化方法
? 執行階段:調用與 JSP 對應的 servlet 實例的 服務方法
? 銷毀階段:調用與 JSP 對應的 servlet 實例的 銷毀方法,然后銷毀 servlet 實例
JSP元素
代碼片段: <% 代碼片段 %>
JSP聲明: <%! declaration ; [ declaration; ]+ ... %>
JSP表達式:<%= 表達式 %>
JSP注釋: <%-- 注釋 --%>
JSP指令: <%@ directive attribute=“value” %>
JSP行為:
HTML元素: html/head/body/div/p/…
JSP隱式對象:request、response、out、session、application、 config 、
pageContext 、page、Exception
JSP 元素說明
代碼片段:包含任意量的 Java 語句、變量、方法或表達式;
JSP 聲明:一個聲明語句可以聲明一個或多個變量、方法,供后面的 Java 代碼使用;
JSP 表達式:輸出 Java 表達式的值,String 形式;
JSP 注釋:為代碼作注釋以及將某段代碼注釋掉
JSP 指令:用來設置與整個 JSP 頁面相關的屬性,
<%@ page ... %>定義頁面的依賴屬性,比如 language、 contentType 、 errorPage 、 isErrorPage 、import、 isThreadSafe 、session 等等
<%@ include ... %>包含其他的 JSP 文件、HTML 文件或文本文件,是該 JSP 文件的一部分,會 被同時編譯執行
<%@ taglib ... %>引入標簽庫的定義,可以是自定義標簽
JSP 行為: jsp:include 、 jsp:useBean 、 jsp:setProperty 、 jsp:getProperty 、 jsp: forward
Jsp 解析過程
? 代碼片段:在_ jspService ()方法內直接輸出
? JSP 聲明: 在 servlet 類中進行輸出
? JSP 表達式:在_ jspService ()方法內直接輸出
? JSP 注釋:直接忽略,不輸出
? JSP 指令:根據不同指令進行區分,include:對引入的文件進行解析;page 相關的屬性會 做為 JSP 的屬性,影響的是解析和請求處理時的行為
? JSP 行為:不同的行為有不同的處理方式, jsp:useBean 為例,會從 pageContext 根據 scope 的 類別獲取 bean 對象,如果沒有會創建 bean,同時存到相應 scope 的 pageContext 中
? HTML:在_ jspService ()方法內直接輸出
? JSP 隱式對象:在_ jspService ()方法會進行聲明,只能在方法中使用;
Connector
Http:HTTP 是超文本傳輸協議,是客戶端瀏覽器或其他程序與 Web 服務器之間的應用層通信協 議
AJP:Apache JServ 協議(AJP)是一種二進制協議,專門代理從 Web 服務器到位于后端的應用 程序服務器的入站請求
阻塞 IO
非阻塞 IO
IO多路復用
阻塞與非阻塞的區別在于進行讀操作和寫操作的系統調用時,如果此時內核態沒有數據可讀或者沒有緩沖空間可寫時,是否阻塞。
IO多路復用的好處在于可同時**多個socket的可讀和可寫事件,這樣就能使得應用可以同時**多個socket,釋放了應用線程資源。
Tomcat各類Connector對比
Connector的實現模式有三種,分別是BIO、NIO、APR,可以在server.xml中指定。
? JIO:用java.io編寫的TCP模塊,阻塞IO
? NIO:用 java.nio 編寫的TCP模塊,非阻塞IO,(IO多路復用)
? APR:全稱Apache Portable Runtime,使用JNI的方式來進行讀取文件以及進行網絡傳輸
Apache Portable Runtime是一個高度可移植的庫,它是Apache HTTP Server 2.x的核心。 APR具有許多用途,包括訪問高級IO功能(如 sendfile , epoll 和 OpenSSL ),操作系統級功能(隨機數生成,系統狀態等)和本地進程處理(共享內存,NT管道和Unix套接字)。
表格中字段含義說明:
? Support Polling:是否支持基于IO多路復用的socket事件輪詢
? Polling Size:輪詢的最大連接數
? Wait for next Request:在等待下一個請求時,處理線程是否釋放,BIO是沒有釋放的,所以在keep-alive=true的情況下處理的并發連接數有限
? Read Request Headers:由于request header數據較少,可以由容器提前解析完畢,不需要阻塞
? Read Request Body:讀取request body的數據是應用業務邏輯的事情,同時Servlet的限制,是需要阻塞讀取的
? Write Response:跟讀取request body的邏輯類似,同樣需要阻塞寫
NIO處理相關類
Acceptor線程負責接收連接,調用accept方法阻塞接收建立的連接,并對socket進行封裝成 PollerEvent ,指定注冊的事件為 op_read ,并放入到 EventQueue 隊列中, PollerEvent 的run方法邏輯的是將Selector注冊到socket的指定事件;
Poller 線程從 EventQueue 獲取 PollerEvent ,并執行 PollerEvent 的run方法,調用Selector的select方法,如果有可讀的Socket則創建Http11NioProcessor,放入到線程池中執行;
CoyoteAdapter 是Connector到Container的適配器,Http11NioProcessor調用其提供的service方法,內部創建Request和Response對象,并調用最頂層容器的Pipeline中的第一個Valve的invoke方法
Mapper主要處理http url 到servlet的映射規則的解析,對外提供map方法
NIO Connector主要參數
Comet
Comet是一種用于web的推送技術,能使服務器實時地將更新的信息傳送到客戶端,而無須客戶端發出請求
在 WebSocket 出來之前,如果不適用comet,只能通過瀏覽器端輪詢Server來模擬實現服務器端推送。
Comet支持servlet異步處理IO,當連接上數據可讀時觸發事件,并異步寫數據(阻塞)
Tomcat要實現Comet,只需繼承 HttpServlet 同時,實現 CometProcessor 接口
? Begin:新的請求連接接入調用,可進行與Request和Response相關的對象初始化操作,并保存response對象,用于后續寫入數據
? Read:請求連接有數據可讀時調用
? End:當數據可用時,如果讀取到文件結束或者response被關閉時則被調用
? Error:在連接上發生異常時調用,數據讀取異常、連接斷開、處理異常、socket超時
Note:
? Read:在post請求有數據,但在begin事件中沒有處理,則會調用read,如果read沒有讀取數據,在會觸發Error回調,關閉socket
? End:當socket超時,并且response被關閉時也會調用;server被關閉時調用
? Error:除了socket超時不會關閉socket,其他都會關閉socket
? End和Error時間觸發時應關閉當前comet會話,即調用 CometEvent 的close方法
Note:在事件觸發時要做好線程安全的操作
異步Servlet
傳統流程:
? 首先,Servlet 接收到請求之后,request數據解析;
? 接著,調用業務接口的某些方法,以完成業務處理;
? 最后,根據處理的結果提交響應,Servlet 線程結束
異步處理流程:
? 客戶端發送一個請求
? Servlet容器分配一個線程來處理容器中的一個servlet
? servlet調用 request.startAsync (),保存 AsyncContext , 然后返回
? 任何方式存在的容器線程都將退出,但是response仍然保持開放
? 業務線程使用保存的 AsyncContext 來完成響應(線程池)
? 客戶端收到響應
Servlet 線程將請求轉交給一個異步線程來執行業務處理,線程本身返回至容器,此時 Servlet 還沒有生成響應數據,異步線程處理完業務以后,可以直接生成響應數據(異步線程擁有 ServletRequest 和 ServletResponse 對象的引用)
為什么web應用中支持異步?
推出異步,主要是針對那些比較耗時的請求:比如一次緩慢的數據庫查詢,一次外部REST API調用, 或者是其他一些I/O密集型操作。這種耗時的請求會很快的耗光Servlet容器的線程池,繼而影響可擴展性。
Note:從客戶端的角度來看,request仍然像任何其他的HTTP的request-response交互一樣,只是耗費了更長的時間而已
異步事件**
? onStartAsync :Request調用 startAsync 方法時觸發
? onComplete : syncContext 調用complete方法時觸發
? onError :處理請求的過程出現異常時觸發
? onTimeout :socket超時觸發
Note :
onError / onTimeout 觸發后,會緊接著回調 onComplete
onComplete 執行后,就不可 再操作 request和response
Java Servlet 容器
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。