Tomcat 啟動及請求處理過程

本文使用Spring Boot提供的嵌入式Tomcat為例來分析Tomcat的啟動過程吩案、線程池管理及請求過程典徘。

org.apache.tomcat.util.net.NioEndpoint#startInternal開始說起:

    @Override
    public void startInternal() throws Exception {

        if (!running) {
            running = true;
            paused = false;

            processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getProcessorCache());
            eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                            socketProperties.getEventCache());
            nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getBufferPool());

            // Create worker collection
            if ( getExecutor() == null ) {
                createExecutor();
            }

            initializeConnectionLatch();

            // Start poller threads
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
            }

            startAcceptorThreads();
        }
    }

這里我們關(guān)注下創(chuàng)建Executor的代碼奢方,通過條件判斷是否存在Executor,不存在則執(zhí)行createExecutor()方法。

    public void createExecutor() {
        internalExecutor = true;
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }

TaskThreadFactory是一個ThreadFactory的實現(xiàn)欲诺,里面定義了工作線程的線程名前綴和線程優(yōu)先級腮郊,然后實例化ThreadPoolExecutor摹蘑,這里的ThreadPoolExecutor其實是Tomcat自己的實現(xiàn),全稱為org.apache.tomcat.util.threads.ThreadPoolExecutor轧飞,其繼承自juc的java.util.concurrent.ThreadPoolExecutor衅鹿,我們可以理解為tomcat的ThreadPoolExecutor對juc提供的ThreadPoolExecutor進(jìn)行了一層包裝,最終返回一個Executor實例过咬,這里創(chuàng)建Executor的工作就結(jié)束了大渤。
值得注意的是在new ThreadPoolExecutor的時候就會創(chuàng)建核心線程,因為這個是通過TaskThreadFactory來創(chuàng)建線程的掸绞,所以我們可以稍微關(guān)注下它里面創(chuàng)建線程的方法:

    @Override
    public Thread newThread(Runnable r) {
        TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(daemon);
        t.setPriority(threadPriority);

        // Set the context class loader of newly created threads to be the class
        // loader that loaded this factory. This avoids retaining references to
        // web application class loaders and similar.
        if (Constants.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    t, getClass().getClassLoader());
            AccessController.doPrivileged(pa);
        } else {
            t.setContextClassLoader(getClass().getClassLoader());
        }

        return t;
    }

可以看到這里已經(jīng)指定了線程的名稱泵三,形如http-nio-8080-exec-3
回到startInternal方法衔掸,接著便是創(chuàng)建pollers并啟動(線程)烫幕,pollers的大小默認(rèn)為2和處理器核數(shù)的最小值,創(chuàng)建Poller的代碼如下:

        public Poller() throws IOException {
            this.selector = Selector.open();
        }

這里說明下Poller實現(xiàn)了Runnable接口敞映,且是NioEndpoint的一個內(nèi)部類较曼。

    /**
     * Poller class.
     */
    public class Poller implements Runnable {
        ...
    }

startInternal方法的介紹就告一段落,接著來看看Pollerrun方法:

        @Override
        public void run() {
            // Loop until destroy() is called
            while (true) {

                boolean hasEvents = false;

                try {
                    if (!close) {
                        hasEvents = events();
                        if (wakeupCounter.getAndSet(-1) > 0) {
                            //if we are here, means we have other stuff to do
                            //do a non blocking select
                            keyCount = selector.selectNow();
                        } else {
                            keyCount = selector.select(selectorTimeout);
                        }
                        wakeupCounter.set(0);
                    }
                    if (close) {
                        events();
                        timeout(0, false);
                        try {
                            selector.close();
                        } catch (IOException ioe) {
                            log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
                        }
                        break;
                    }
                } catch (Throwable x) {
                    ExceptionUtils.handleThrowable(x);
                    log.error("",x);
                    continue;
                }
                //either we timed out or we woke up, process events first
                if ( keyCount == 0 ) hasEvents = (hasEvents | events());

                Iterator<SelectionKey> iterator =
                    keyCount > 0 ? selector.selectedKeys().iterator() : null;
                // Walk through the collection of ready keys and dispatch
                // any active event.
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
                    // Attachment may be null if another thread has called
                    // cancelledKey()
                    if (attachment == null) {
                        iterator.remove();
                    } else {
                        iterator.remove();
                        processKey(sk, attachment);
                    }
                }//while

                //process timeouts
                timeout(keyCount,hasEvents);
            }//while

            getStopLatch().countDown();
        }

主要的邏輯是通過while循環(huán)去遍歷SelectionKey驱显,如果有請求過來诗芜,則會交給processKey方法處理瞳抓。

        protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
            try {
                if ( close ) {
                    cancelledKey(sk);
                } else if ( sk.isValid() && attachment != null ) {
                    if (sk.isReadable() || sk.isWritable() ) {
                        if ( attachment.getSendfileData() != null ) {
                            processSendfile(sk,attachment, false);
                        } else {
                            unreg(sk, attachment, sk.readyOps());
                            boolean closeSocket = false;
                            // Read goes before write
                            if (sk.isReadable()) {
                                if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
                                    closeSocket = true;
                                }
                            }
                            if (!closeSocket && sk.isWritable()) {
                                if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
                                    closeSocket = true;
                                }
                            }
                            if (closeSocket) {
                                cancelledKey(sk);
                            }
                        }
                    }
                } else {
                    //invalid key
                    cancelledKey(sk);
                }
            } catch ( CancelledKeyException ckx ) {
                cancelledKey(sk);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error("",t);
            }
        }

這里對nio的SelectionKey實例進(jìn)行判斷,發(fā)現(xiàn)sk.isReadable()true伏恐,最后交給processSocket方法孩哑。

    public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = processorCache.pop();
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }

這里通過getExecutor方法獲取之前創(chuàng)建的Executor實例,然后執(zhí)行execute方法提交請求任務(wù)翠桦。至此横蜒,一個完整的請求便會經(jīng)過Tomcat到Servlet,并最終進(jìn)入對應(yīng)的handler對請求進(jìn)行業(yè)務(wù)處理销凑。

以上丛晌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斗幼,隨后出現(xiàn)的幾起案子澎蛛,更是在濱河造成了極大的恐慌,老刑警劉巖蜕窿,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谋逻,死亡現(xiàn)場離奇詭異,居然都是意外死亡桐经,警方通過查閱死者的電腦和手機(jī)毁兆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阴挣,“玉大人气堕,你說我怎么就攤上這事∨线郑” “怎么了茎芭?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盒卸。 經(jīng)常有香客問我骗爆,道長,這世上最難降的妖魔是什么蔽介? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任摘投,我火速辦了婚禮,結(jié)果婚禮上虹蓄,老公的妹妹穿的比我還像新娘犀呼。我一直安慰自己,他們只是感情好薇组,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布外臂。 她就那樣靜靜地躺著,像睡著了一般律胀。 火紅的嫁衣襯著肌膚如雪宋光。 梳的紋絲不亂的頭發(fā)上貌矿,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音罪佳,去河邊找鬼逛漫。 笑死,一個胖子當(dāng)著我的面吹牛赘艳,可吹牛的內(nèi)容都是我干的酌毡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蕾管,長吁一口氣:“原來是場噩夢啊……” “哼枷踏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掰曾,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旭蠕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后婴梧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體下梢,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡客蹋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年塞蹭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讶坯。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡番电,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辆琅,到底是詐尸還是另有隱情漱办,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布婉烟,位于F島的核電站娩井,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏似袁。R本人自食惡果不足惜洞辣,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望昙衅。 院中可真熱鬧扬霜,春花似錦、人聲如沸而涉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啼县。三九已至材原,卻和暖如春沸久,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背余蟹。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工麦向, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人客叉。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓诵竭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兼搏。 傳聞我的和親對象是個殘疾皇子卵慰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355