深入拆解Tomcat&Jetty(七)

回顧一下Tomcat的啟動步驟

  • 1.安裝JDK,配置環境變量
  • 2.下載Tomcat并解壓
  • 3.執行tomcat/bin目錄下的start.sh
    執行腳本后的流程
image
  • 1.Tomcat本質上還是一個Java程序,因此startup.sh腳本會啟動一個JVM來運行Tomcat的啟動類BootStrap

其實Tomcat和我們自己平時寫的代碼并沒有本質上的區別,只是Tomcat的啟動時通過腳本.我們常用的SpringBoot或簡單的Java類可以通過java命令啟動.

  • 2.BootStrap主要任務是初始化Tomcat的類加載器,創建Catalina.
  • 3.Catalina會解析server.xml,創建響應的組件,并調用Server.start
  • 4.Server負責管理Service,調用Service.start
  • 5.Service會管理頂層容器Engine,調用Engine.start

經過這幾步Tomcat啟動就算完成了.

Tomcat比作公司

  • Catalina:公司創始人,負責組件團隊,創建Server以及它的子組件
  • Server:公司的CEO,管理多個事業群,每個事業群是一個Service
  • Service:事業群總經理,管理兩個職能部門,對外市場部:連接器,對內的研發部:容器
  • Engine:研發部總經理,作為最頂層的容器組件

Catalina

Catalina主要任務就是創建Server,解析server.xml,將server.xml定義的各個組件創建出來,然后調用Server的init和start方法

public void start() {
        //1.獲取Server,如果為空進行創建
        if (getServer() == null) {
            load();
        }
        //2.創建失敗直接報錯退出
        if (getServer() == null) {
            log.fatal(sm.getString("catalina.noServer"));
            return;
        }

        long t1 = System.nanoTime();

        // 3.啟動Server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
        }

        // 創建Tomcat關閉的鉤子
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
        //監聽Tomcat停止請求
        if (await) {
            await();
            stop();
        }
    }

Hook:鉤子,Tomcat中的關閉鉤子是用于在JVM關閉時做一些清理工作,比如將緩存數據刷到磁盤,或者清理臨時文件呢.Hook本質上是一個線程,JVM在停止之前會嘗試執行這個線程的run方法.

Catalina的關閉鉤子

protected class CatalinaShutdownHook extends Thread {

        @Override
        public void run() {
            try {
                if (getServer() != null) {
                //其實只是調用了stop方法
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                //略...
                }
            }
        }
    }

Catalina的關閉Hook中,只是調用了內部的stop方法,最終也是通過Server的stop和destory方法進行資源釋放和清理.

Server

Server組件的實現類是StandardServer,繼承自LifeCycleBase,生命周期被統一管理,它的子組件是Service,因此需要對Service的生命周期進行管理.

  • 在啟動時調用Service組件的start方法
  • 停止是調用Service組件的stop方法

Server添加Service組件

public void addService(Service service) {

        service.setServer(this);

        synchronized (servicesLock) {
            Service results[] = new Service[services.length + 1];
            System.arraycopy(services, 0, results, 0, services.length);
            results[services.length] = service;
            services = results;

            if (getState().isAvailable()) {
                try {
                    service.start();
                } catch (LifecycleException e) {
                    // Ignore
                }
            }

            // Report this property change to interested listeners
            support.firePropertyChange("service", null, service);
        }

    }

可以看到Server通過一個數組持有所有Service的引用,同時這個數組默認長度是0,只有在每次新增Service組件時候會創建新的數組,長度為原數組長度+1,然后將原數組的數據復制到新數組中,并且使用的是System.arraycopy()的Native方法,避免數組的自動1.5倍擴容浪費內存空間.

除了管理Service組件外,Server還有一個重要功能,就是啟動一個Socket監聽停止端口,就是平常使用shutdown.sh腳本就能停止的原因.

其實在Catalina啟動的最后,有一個await方法,這個方法就是調用了Server#await,在Server#await方法中會創建一個Socket對關閉進行監聽,在一個死循環中監聽來自8005端口的數據(關閉端口模式就是8005),收到SHUTDOWN指令后就會退出循環,進入stop的流程.

Service

Service的具體實現是StandardService.StandardService繼承LifeCycleBase,并且會持有Server,Connector,Engine,Mapper等組件.

public class StandardService extends LifecycleBase implements Service {

//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);

其中MapperListener的作用是支持動態部署,監聽容器變化將信息更新到Mapper中.


在Service的啟動方法中,維護了子組件的生命周期,在各種組件啟動的時候,組件有個字的啟動順序

protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        //1.觸發啟動監聽
        setState(LifecycleState.STARTING);

        
        //2.啟動Engine,由Engine啟動其子容器
        if (engine != null) {
            synchronized (engine) {
                
                engine.start();
            }
        }
        //略...
        //啟動Mapper監聽
        mapperListener.start();

        //最后啟動連接器
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }

由組件啟動順序可以看出,Service先啟動了Engine,然后是Mapper監聽器,最后才啟動連接器.

因為只有對內的組件都啟動好了,才能啟動對外服務的組件,這樣才能保證連接后不會因為內部組件未初始化完成導致的問題.所以停止的順序就會和啟動時剛好相反

Engine

Engine具體實現類是StandardEngine本質是一個頂層容器,所以會繼承自ContainerBase,實現Engine接口.

但是由于Engine是頂層的容器,所以很多功能都抽象到ContainerBase中實現.
通過HashMap持有所有子容器Host的引用.

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

當Engine在啟動的時候,會通過專門的線程池啟動子容器

Engine啟動子容器

Engine容器最重要的功能其實就是將請求轉發給Host進行處理,具體是通過pipline-Valve實現的.

在Engine的構造函數中,就已經將Pipline-Valve的第一個基礎閥設置好了

 /**
     * Create a new StandardEngine component with the default basic Valve.
     */
    public StandardEngine() {

        super();
        //設置第一個基礎閥
        pipeline.setBasic(new StandardEngineValve());
       //..略
    }

StandardEngineValve

在創建Engine時,就會默認創建一個StandardEngineValve,用于連接Host的Pipline,并且在Mapper組件中已經對請求進行了路
由處理,通過URL定位了相應的容器,然后把容器對象保存在Request對象中,所以StandardEngineValve就能開始整個調用鏈路.

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            // HTTP 0.9 or HTTP 1.0 request without a host when no default host
            // is defined. This is handled by the CoyoteAdapter.
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);
    }

Server在啟動連接器和容器都進行了加鎖

為了保證多線程操作共享資源的正確性.

Server內部使用了線程不安全的數組進行Service的引用,Engine對Host的持有使用了hashMap也是線程不安全的.但是又因為存在資源的動態添加和刪除,所以需要加鎖.

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容