日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

Tomcat 架構設計 25 年后依舊能打!我學到了什么?

來源: 責編: 時間:2024-01-02 09:30:31 194觀看
導讀 你好,我是碼哥,可以叫我靚仔。是一個擁抱硬核技術和對象,面向人民幣編程的男人。友情提示:閱讀本文前需要對 Tomcat 有一個全局架構認識,可先翻閱《Tomcat 架構深度解析》。Tomcat 是 Sun 公司在 1998 年開發的。當時開發

 你好,我是碼哥,可以叫我靚仔。是一個擁抱硬核技術和對象,面向人民幣編程的男人。友情提示:閱讀本文前需要對 Tomcat 有一個全局架構認識,可先翻閱《Tomcat 架構深度解析》。Pss28資訊網——每日最新資訊28at.com

Pss28資訊網——每日最新資訊28at.com

Tomcat 是 Sun 公司在 1998 年開發的。當時開發 Tomcat 的目標是成為 Sun 公司的 Java Servlet 和 JSP 規范的參考實現。Pss28資訊網——每日最新資訊28at.com

Pss28資訊網——每日最新資訊28at.com

如今已經成為業務開發首選的 Web 應用服務器,Spring Boot 直接將 Tomcat 內置作為 Web 應用啟動,二十五年寶刀未老。其中的架構設計思維值得我們深入學習和借鑒。Pss28資訊網——每日最新資訊28at.com

Pss28資訊網——每日最新資訊28at.com

碼哥今天帶你深入探究,學會借鑒 Tomcat 的設計思想在工作中做好架構設計。Pss28資訊網——每日最新資訊28at.com

Pss28資訊網——每日最新資訊28at.com

在Tomcat 架構解析到設計思想借鑒中我們學到 Tomcat 的總體架構,學會從宏觀上怎么去設計一個復雜系統,怎么設計頂層模塊,以及模塊之間的關系;Pss28資訊網——每日最新資訊28at.com

Pss28資訊網——每日最新資訊28at.com

Tomcat 實現的 2 個核心功能:Pss28資訊網——每日最新資訊28at.com

  • 處理 Socket 連接,負責網絡字節流與 Request 和 Response 對象的轉化。
  • 加載并管理 Servlet ,以及處理具體的 Request 請求。

所以 Tomcat 設計了兩個核心組件連接器(Connector)和容器(Container),連接器負責對外交流,容器負責內部處理。Pss28資訊網——每日最新資訊28at.com

Tomcat整體架構Tomcat整體架構Pss28資訊網——每日最新資訊28at.com

管理組件,運籌帷幄

本篇作為 Tomcat 系列的第三篇,帶大家體會 Tomcat 是如何構建的?每個組件如何管理組件的?連接器和容器是如何被啟動和管理的?Pss28資訊網——每日最新資訊28at.com

Tomcat 啟動流程:startup.sh -> catalina.sh start ->java -jar org.apache.catalina.startup.Bootstrap.main()Pss28資訊網——每日最新資訊28at.com

Tomcat 啟動流程Tomcat 啟動流程Pss28資訊網——每日最新資訊28at.com

Bootstrap、Catalina、Server、Service、 Engine 都承擔了什么責任?Pss28資訊網——每日最新資訊28at.com

圖片圖片Pss28資訊網——每日最新資訊28at.com

單獨寫一篇介紹他們是因為你可以看到這些啟動類或者組件不處理具體請求,它們的任務主要是管理,管理下層組件的生命周期,并且給下層組件分配任務,也就是把請求路由到負責干活兒的組件。Pss28資訊網——每日最新資訊28at.com

他們就像一個公司的高層,管理整個公司的運作,將任務分配給專業的人。Pss28資訊網——每日最新資訊28at.com

我們在設計軟件系統中,不可避免地會遇到需要一些管理作用的組件,就可以學習和借鑒 Tomcat 是如何抽象和管理這些組件的。Pss28資訊網——每日最新資訊28at.com

Bootstrap

當執行 startup.sh 腳本的時候,就會啟動一個 JVM 運行 Tomcat 的啟動類 Bootstrap 的 main 方法。Pss28資訊網——每日最新資訊28at.com

先看下他的成員變量窺探核心功能:Pss28資訊網——每日最新資訊28at.com

public final class Bootstrap {    ClassLoader commonLoader = null;    ClassLoader catalinaLoader = null;    ClassLoader sharedLoader = null;}

它的主要任務就是初始化 Tomcat 定義的類加載器,同時創建 Catalina 對象。Pss28資訊網——每日最新資訊28at.com

Bootstrap 猶如女媧,女媧造人,它初始化了類加載器,加載萬物。Pss28資訊網——每日最新資訊28at.com

關于為何自定義各種類加載器詳情請查看碼哥的 Tomcat 架構設計解析 類加載器部分。Pss28資訊網——每日最新資訊28at.com

初始化類加載器

如何WebAppClassLoader

假如我們在 Tomcat 中運行了兩個 Web 應用程序,兩個 Web 應用中有同名的 Servlet,但是功能不同,Tomcat 需要同時加載和管理這兩個同名的 Servlet類,保證它們不會沖突,因此 Web 應用之間的類需要隔離。Pss28資訊網——每日最新資訊28at.com

Tomcat 的解決方案是自定義一個類加載器 WebAppClassLoader, 并且給每個 Web 應用創建一個類加載器實例。Pss28資訊網——每日最新資訊28at.com

我們知道,Context 容器組件對應一個 Web 應用。因此,每個 Context容器負責創建和維護一個 WebAppClassLoader加載器實例。Pss28資訊網——每日最新資訊28at.com

這背后的原理是,不同的加載器實例加載的類被認為是不同的類,即使它們的類名相同。Pss28資訊網——每日最新資訊28at.com

Tomcat 的自定義類加載器 WebAppClassLoader打破了雙親委托機制,它首先自己嘗試去加載某個類,如果找不到則通過 ExtClassLoader 加載 JRE 核心類防止黑客攻擊,無法加載再代理給 AppClassLoader 加載器,其目的是優先加載 Web 應用自己定義的類。Pss28資訊網——每日最新資訊28at.com

具體實現就是重寫 ClassLoader的兩個方法:findClass和 loadClass。Pss28資訊網——每日最新資訊28at.com

SharedClassLoader

假如兩個 Web 應用都依賴同一個第三方的 JAR 包,比如 Spring,那 Spring的 JAR 包被加載到內存后,Tomcat要保證這兩個 Web 應用能夠共享,也就是說 Spring的 JAR 包只被加載一次。Pss28資訊網——每日最新資訊28at.com

SharedClassLoader 就是 Web 應用共享的類庫的加載器,專門加載 Web 應用共享的類。Pss28資訊網——每日最新資訊28at.com

如果 WebAppClassLoader自己沒有加載到某個類,就會委托父加載器 SharedClassLoader去加載這個類,SharedClassLoader會在指定目錄下加載共享類,之后返回給 WebAppClassLoader,這樣共享的問題就解決了。Pss28資訊網——每日最新資訊28at.com

CatalinaClassloader

如何隔離 Tomcat 本身的類和 Web 應用的類?Pss28資訊網——每日最新資訊28at.com

要共享可以通過父子關系,要隔離那就需要兄弟關系了。Pss28資訊網——每日最新資訊28at.com

兄弟關系就是指兩個類加載器是平行的,它們可能擁有同一個父加載器,基于此 Tomcat 又設計一個類加載器 CatalinaClassloader,專門來加載 Tomcat 自身的類。Pss28資訊網——每日最新資訊28at.com

這樣設計有個問題,那 Tomcat 和各 Web 應用之間需要共享一些類時該怎么辦呢?Pss28資訊網——每日最新資訊28at.com

老辦法,還是再增加一個 CommonClassLoader,作為 CatalinaClassloader和 SharedClassLoader 的父加載器。Pss28資訊網——每日最新資訊28at.com

CommonClassLoader能加載的類都可以被 CatalinaClassLoader和 SharedClassLoader 使用。Pss28資訊網——每日最新資訊28at.com

Catalina

Catalina 就好像是一個帝王,管理天下。就是它創建 Server 以及所有子組件。Pss28資訊網——每日最新資訊28at.com

Catalina 的主要任務就是創建 Server,解析 server.xml 把里面配置的各個組件創建出來,并調用每個組件的 init和 start方法,將整個 Tomcat 啟動,這樣整個帝國就在正常運作了。Pss28資訊網——每日最新資訊28at.com

我們可以根據 Tomcat 配置文件來直觀感受下:Pss28資訊網——每日最新資訊28at.com

<Server port="8005" shutdown="SHUTDOWN"> // 頂層組件,可包含多個 Service,代表一個 Tomcat 實例  <Service name="Catalina">  // 頂層組件,包含一個 Engine ,多個連接器    <Connector port="8080" protocol="HTTP/1.1"               connectionTimeout="20000"               redirectPort="8443" />    <!-- Define an AJP 1.3 Connector on port 8009 -->    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />  // 連接器 // 容器組件:一個 Engine 處理 Service 所有請求,包含多個 Host    <Engine name="Catalina" defaultHost="localhost">   // 容器組件:處理指定Host下的客戶端請求, 可包含多個 Context      <Host name="localhost"  appBase="webapps"            unpackWARs="true" autoDeploy="true">   // 容器組件:處理特定 Context Web應用的所有客戶端請求   <Context></Context>      </Host>    </Engine>  </Service></Server>

作為帝王,Catalina 還需要處理國家的各種異常情況,比如有人搶公章(執行了 Ctrl + C 關閉 Tomcat)。Pss28資訊網——每日最新資訊28at.com

Tomcat 要如何清理資源呢?Pss28資訊網——每日最新資訊28at.com

通過向 JVM 注冊一個「關閉鉤子」,具體關鍵邏輯詳見Pss28資訊網——每日最新資訊28at.com

org.apache.catalina.startup.Catalina#start 源碼:Pss28資訊網——每日最新資訊28at.com

  1. Server 不存在則解析 server.xml 創建;
  2. 創建失敗則報錯;
  3. 啟動 Server;
  4. 創建并注冊「關閉鉤子」;
  5. await 方法監聽停止請求。
/**     * Start a new server instance.     */    public void start() {        // 如果 Catalina 持有的 Server 為空則解析 server.xml 創建        if (getServer() == null) {            load();        }        if (getServer() == null) {            log.fatal("Cannot start server. Server instance is not configured.");            return;        }        // Start the new server        try {            getServer().start();        } catch (LifecycleException e) {            // 省略部分代碼        }        // 創建鉤子并注冊        if (useShutdownHook) {            if (shutdownHook == null) {                shutdownHook = new CatalinaShutdownHook();            }            Runtime.getRuntime().addShutdownHook(shutdownHook);        }        // 監聽停止請求,內部調用 Server 的 stop        if (await) {            await();            stop();        }    }

當我們需要在 JVM 關閉做一些清理工作,比如將緩存數據刷到磁盤或者清理一些文件,就可以向 JVM 注冊一個「關閉鉤子」。Pss28資訊網——每日最新資訊28at.com

它其實就是一個線程,當 JVM 停止前嘗試執行這個線程的 run 方法。Pss28資訊網——每日最新資訊28at.com

org.apache.catalina.startup.Catalina.CatalinaShutdownHookPss28資訊網——每日最新資訊28at.com

protected class CatalinaShutdownHook extends Thread {        @Override        public void run() {            try {                if (getServer() != null) {                    Catalina.this.stop();                }            } catch (Throwable ex) {              // 省略部分代碼....            }        }    }

其實就是執行了 Catalina 的 stop 方法,通過它將整個 Tomcat 停止。Pss28資訊網——每日最新資訊28at.com

Server

Server 組件的職責就是管理 Service 組件,負責調用持有的 Service 的 start 方法。Pss28資訊網——每日最新資訊28at.com

他就像是帝國的丞相,負責管理多個事業部,每個事業部就是一個 Service。Pss28資訊網——每日最新資訊28at.com

它管理兩個部門:Pss28資訊網——每日最新資訊28at.com

  • Connector 連接器:對外市場營銷部,推廣吹牛寫 PPT 的。
  • Container 容器:研發部門,沒有性生活的 996 社畜 。

實現類是 org.apache.catalina.core.StandardServer,Server 繼承 org.apache.catalina.util.LifecycleMBeanBase,所以他的生命周期也被統一管理,Server 的子組件是 Service,所以還需要管理 Service 的生命周期。Pss28資訊網——每日最新資訊28at.com

也就是說在啟動和關閉 Server 的時候會分別先調用 Service 的 啟動和停止方法。Pss28資訊網——每日最新資訊28at.com

這就是設計思想呀,抽象出生命周期 Lifecycle 接口,體現出接口隔離原則,將生命周期的相關功能內聚。Pss28資訊網——每日最新資訊28at.com

我們接著看 Server 如何管理 Service 的,核心源碼如下 org.apache.catalina.core.StandardServer#addService:Pss28資訊網——每日最新資訊28at.com

public void addService(Service service) {        service.setServer(this);        synchronized (servicesLock) {            // 創建 長度 +1 的數組            Service results[] = new Service[services.length + 1];            // 將舊的數據復制到新數組            System.arraycopy(services, 0, results, 0, services.length);            results[services.length] = service;            services = results;            // 啟動 Service 組件            if (getState().isAvailable()) {                try {                    service.start();                } catch (LifecycleException e) {                    // Ignore                }            }            // 發送事件            support.firePropertyChange("service", null, service);        }    }

在添加 Service 過程中動態拓展數組長度,為了節省內存。Pss28資訊網——每日最新資訊28at.com

除此之外,Server 組件還有一個重要的任務是啟動一個 Socket 來監聽停止端口,這就是為什么你能通過 shutdown 命令來關閉 Tomcat。Pss28資訊網——每日最新資訊28at.com

不知道你留意到沒有,上面 Caralina 的啟動方法的最后一行代碼就是調用了 Server 的 await 方法。Pss28資訊網——每日最新資訊28at.com

在 await 方法里會創建一個 Socket 監聽 8005 端口,并在一個死循環里接收 Socket 上的連接請求,如果有新的連接到來就建立連接,然后從 Socket 中讀取數據;如果讀到的數據是停止命令“SHUTDOWN”,就退出循環,進入 stop 流程。Pss28資訊網——每日最新資訊28at.com

Service

他的職責就是管理 Connector 連接器 和 頂層容器 Engine,會分別調用他們的 start 方法。至此,整個 Tomcat 就算啟動完成了。Pss28資訊網——每日最新資訊28at.com

Service 就是事業部的話事人,管理兩個職能部門對外推廣部(連接器),對內研發部(容器)。Pss28資訊網——每日最新資訊28at.com

Service 組件的實現類是org.apache.catalina.core.StandardService,直接看關鍵的成員變量。Pss28資訊網——每日最新資訊28at.com

public class StandardService extends LifecycleMBeanBase implements Service {    // 名字    private String name = null;    // 所屬的 Server 實例    private Server server = null;    // 連接器數組    protected Connector connectors[] = new Connector[0];    private final Object connectorsLock = new Object();    // 對應的 Engine 容器    private Engine engine = null;    // 映射器及其監聽器    protected final Mapper mapper = new Mapper();    protected final MapperListener mapperListener = new MapperListener(this);}

繼承 LifecycleMBeanBase 而 LifecycleMBeanBase 又繼承 LifecycleBase,這里實際上是模板方法模式的運用,org.apache.catalina.util.LifecycleBase#init,org.apache.catalina.util.LifecycleBase#start,org.apache.catalina.util.LifecycleBase#stop 分別是對應的模板方法,內部定義了整個算法流程,子類去實現自己內部具體變化部分,將變與不變抽象出來實現開閉原則設計思路。Pss28資訊網——每日最新資訊28at.com

那為什么還有一個 MapperListener?這是因為 Tomcat 支持熱部署,當 Web 應用的部署發生變化時,Mapper 中的映射信息也要跟著變化,MapperListener 就是一個監聽器,它監聽容器的變化,并把信息更新到 Mapper 中,這是典型的觀察者模式。Pss28資訊網——每日最新資訊28at.com

作為“管理”角色的組件,最重要的是維護其他組件的生命周期。Pss28資訊網——每日最新資訊28at.com

此外在啟動各種組件時,要注意它們的依賴關系,也就是說,要注意啟動的順序。我們來看看 Service 啟動方法:Pss28資訊網——每日最新資訊28at.com

protected void startInternal() throws LifecycleException {    //1. 觸發啟動監聽器    setState(LifecycleState.STARTING);    //2. 先啟動 Engine,Engine 會啟動它子容器    if (engine != null) {        synchronized (engine) {            engine.start();        }    }    //3. 再啟動 Mapper 監聽器    mapperListener.start();    //4. 最后啟動連接器,連接器會啟動它子組件,比如 Endpoint    synchronized (connectorsLock) {        for (Connector connector: connectors) {            if (connector.getState() != LifecycleState.FAILED) {                connector.start();            }        }    }}

這里啟動順序也很講究,Service 先啟動了 Engine 組件,再啟動 Mapper 監聽器,最后才是啟動連接器。Pss28資訊網——每日最新資訊28at.com

這很好理解,因為內層組件啟動好了才能對外提供服務,產品沒做出來,市場部也不能瞎忽悠,研發好了才能啟動外層的連接器組件。Pss28資訊網——每日最新資訊28at.com

而 Mapper 也依賴容器組件,容器組件啟動好了才能監聽它們的變化,因此 Mapper 和 MapperListener 在容器組件之后啟動。Pss28資訊網——每日最新資訊28at.com

組件停止的順序跟啟動順序正好相反的,也是基于它們的依賴關系。Pss28資訊網——每日最新資訊28at.com

Engine

他是最頂層的容器組件。繼承 Container,所有的容器組件都繼承 Container,這里實際上運用了組合模式統一管理。Pss28資訊網——每日最新資訊28at.com

他的實現類是 org.apache.catalina.core.StandardEngine,繼承 ContainerBase。Pss28資訊網——每日最新資訊28at.com

public class StandardEngine extends ContainerBase implements Engine {}

他的子容器是 Host,所以持有 Host 容器數組,這個屬性每個容器都會存在,所以放在抽象類中Pss28資訊網——每日最新資訊28at.com

protected final HashMap<String, Container> children = new HashMap<>();

ContainerBase 用 HashMap 保存了它的子容器,并且 ContainerBase 還實現了子容器的“增刪改查”,甚至連子組件的啟動和停止都提供了默認實現,比如 ContainerBase 會用專門的線程池來啟動子容器。Pss28資訊網——每日最新資訊28at.com

org.apache.catalina.core.ContainerBase#startInternalPss28資訊網——每日最新資訊28at.com

// Start our child containers, if anyContainer children[] = findChildren();List<Future<Void>> results = new ArrayList<>();for (Container child : children) {  results.add(startStopExecutor.submit(new StartChild(child)));}

Engine 在啟動 Host 子容器時就直接重用了這個方法。Pss28資訊網——每日最新資訊28at.com

容器組件最重要的功能是處理請求,而 Engine 容器對請求的“處理”,其實就是把請求轉發給某一個 Host 子容器來處理,具體是通過 Valve 來實現的。Pss28資訊網——每日最新資訊28at.com

每一個容器組件都有一個 Pipeline,而 Pipeline 中有一個基礎閥(Basic Valve),透過構造方法創建 Pipeline。Pss28資訊網——每日最新資訊28at.com

public StandardEngine() {    super();    pipeline.setBasic(new StandardEngineValve());    // 省略部分代碼}

Engine 容器的基礎閥定義如下:Pss28資訊網——每日最新資訊28at.com

final class StandardEngineValve extends ValveBase {    public final void invoke(Request request, Response response)      throws IOException, ServletException {      // 拿到請求中的 Host 容器      Host host = request.getHost();      if (host == null) {          return;      }      // 調用 Host 容器中的 Pipeline 中的第一個 Valve      host.getPipeline().getFirst().invoke(request, response);  }}

這個基礎閥實現非常簡單,就是把請求轉發到 Host 容器。Pss28資訊網——每日最新資訊28at.com

從代碼中可以看到,處理請求的 Host 容器對象是從請求中拿到的,請求對象中怎么會有 Host 容器呢?Pss28資訊網——每日最新資訊28at.com

這是因為請求到達 Engine 容器中之前,Mapper 組件已經對請求進行了路由處理,Mapper 組件通過請求的 URL 定位了相應的容器,并且把容器對象保存到了請求對象中。Pss28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-55082-0.htmlTomcat 架構設計 25 年后依舊能打!我學到了什么?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 在Go中使用Goroutines和Channels發送電子郵件

下一篇: 掌握這些Kubernetes Pod技巧,成為企業必備技能人才

標簽:
  • 熱門焦點
  • 2023年Q2用戶偏好榜:12+256G版本成新主流

    3月份的性能榜、性價比榜和好評榜之后,就要輪到2023年的第二季度偏好榜了,上半年的新機潮已經過去,最明顯的肯定就是大內存和存儲的機型了,另外部分中端機也取消了屏幕塑料支架
  • 5月iOS設備性能榜:M1 M2依舊是榜單前五

    和上個月一樣,沒有新品發布的iOS設備性能榜的上榜設備并沒有什么更替,僅僅只有跑分變化而產生的排名變動,剛剛開始的蘋果WWDC2023,推出的產品也依舊是新款Mac Pro、新款Mac Stu
  • 28個SpringBoot項目中常用注解,日常開發、求職面試不再懵圈

    前言在使用SpringBoot開發中或者在求職面試中都會使用到很多注解或者問到注解相關的知識。本文主要對一些常用的注解進行了總結,同時也會舉出具體例子,供大家學習和參考。注解
  • Rust中的高吞吐量流處理

    作者 | Noz編譯 | 王瑞平本篇文章主要介紹了Rust中流處理的概念、方法和優化。作者不僅介紹了流處理的基本概念以及Rust中常用的流處理庫,還使用這些庫實現了一個流處理程序
  • 一篇聊聊Go錯誤封裝機制

    %w 是用于錯誤包裝(Error Wrapping)的格式化動詞。它是用于 fmt.Errorf 和 fmt.Sprintf 函數中的一個特殊格式化動詞,用于將一個錯誤(或其他可打印的值)包裝在一個新的錯誤中。使
  • 使用LLM插件從命令行訪問Llama 2

    最近的一個大新聞是Meta AI推出了新的開源授權的大型語言模型Llama 2。這是一項非常重要的進展:Llama 2可免費用于研究和商業用途。(幾小時前,swyy發現它已從LLaMA 2更名為Lla
  • 2023年,我眼中的字節跳動

    此時此刻(2023年7月),字節跳動從未上市,也從未公布過任何官方的上市計劃;但是這并不妨礙它成為中國最受關注的互聯網公司之一。從2016-17年的抖音強勢崛起,到2018年的&ldquo;頭騰
  • 騰訊VS網易,最卷游戲暑期檔,誰能笑到最后?

    作者:無銹缽來源:財經無忌7月16日晚,上海1862時尚藝術中心。伴隨著幻象的精準命中,碩大的熒幕之上,比分被定格在了14:12,被寄予厚望的EDG戰隊以絕對的優勢戰勝了BLG戰隊,拿下了總決
  • iQOO 11S評測:行業唯一的200W標準版旗艦

    【Techweb評測】去年底,iQOO推出了“電競旗艦”iQOO 11系列,作為一款性能強機,該機不僅全球首發2K 144Hz E6全感屏,搭載了第二代驍龍8平臺及144Hz電競
Top 主站蜘蛛池模板: 林甸县| 安龙县| 买车| 普兰县| 汉中市| 武强县| 定边县| 浑源县| 娄底市| 连南| 红原县| 博野县| 利川市| 德昌县| 昆山市| 双桥区| 陵川县| 鹤壁市| 双峰县| 西充县| 上林县| 泸水县| 沙洋县| 开封市| 慈利县| 泾阳县| 丰都县| 信宜市| 来宾市| 商丘市| 卢龙县| 石门县| 右玉县| 商水县| 库车县| 宣城市| 循化| 如皋市| 高雄市| 烟台市| 绥宁县|