<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Tomcat 第五篇:請求處理流程(下)

          共 2844字,需瀏覽 6分鐘

           ·

          2020-10-01 21:44

          1. 請求處理流程 AprEndPoint

          順著上一篇接著聊,當一個請求發(fā)送到 Tomcat 以后,會由連接器 Connector 轉(zhuǎn)送至 AprEndPoint ,在 AprEndPoint 中調(diào)用了 startInternal() 方法,這個方法總共做了做了四件事兒:

          • LimitLatch 限制連接次數(shù)。
          • 創(chuàng)建了 poller 線程。
          • 創(chuàng)建了 sendfile 線程。
          • 創(chuàng)建了 acceptor 。

          其中, poller 、 sendfile 、 acceptor 都是 AprEndPoint 的內(nèi)部類,因為他們的父類都實現(xiàn)了 Runnable ,所以核心邏輯都在他們自己的 run() 方法中。

          其中的涉及到的源代碼太多了,我就是懶得往出列了,所以畫了下面這個圖給各位做個示意。

          • LimitLatch 是連接控制器,它負責控制最大連接數(shù)。
          • Acceptor 跑在一個單獨的線程中,它在一個死循環(huán)里面通過調(diào)用 accept() 方法來接收新連接,會返回一個 long 類型的 socket ,然后將這個 socket 封裝成 AprSocketWrapper 對象。
          • Poller 本身也跑在一個單獨的線程中,它早內(nèi)部維護了一個 SocketList 對象,這個對象中含有 socket 數(shù)組,它在一個死循環(huán)里不斷檢測 socket 的數(shù)據(jù)就緒狀態(tài),一旦有 socket 可讀,就生成一個 SocketProcessor 任務對象扔給 Executor 去處理。
          • Executor 就是一個線程池,負責運行 SocketProcessor 任務類, SocketProcessorrun() 方法會調(diào)用 Http11Processor 來讀取和解析請求數(shù)據(jù)。

          肯能有的朋友看完了,都不知道 AprEndPoint 或者說 Apr 這種連接模式是什么。

          稍微做下簡介:

          APR(Apache Portable Runtime Libraries)是 Apache 可移植運行時庫,它是用 C 語言實現(xiàn)的,其目的是向上層應用程序提供一個跨平臺的操作系統(tǒng)接口庫。Tomcat 可以用它來處理包括文件和網(wǎng)絡 I/O,從而提升性能。

          在 Tomcat8.5.x 中,默認的 I/O 模式使用的是 NIO ,使用的鏈接器是 org.apache.coyote.http11.Http11NioProtocol ,當然,由于是默認的,無需顯示配置,在 server.xml 中只需要這么寫就可以了:

          <Connector?port="8080"?protocol="HTTP/1.1"
          ????????????connectionTimeout="20000"
          ????????????redirectPort="8443"?/>

          但是如果要換成 APR ,就需要這么寫了:

          <Connector?port="8443"?protocol="org.apache.coyote.http11.Http11AprProtocol"
          ????????????maxThreads="150"?SSLEnabled="true"?>

          ????<UpgradeProtocol?className="org.apache.coyote.http2.Http2Protocol"?/>
          ????<SSLHostConfig>
          ????????<Certificate?certificateKeyFile="conf/localhost-rsa-key.pem"
          ????????????????????????certificateFile="conf/localhost-rsa-cert.pem"
          ????????????????????????certificateChainFile="conf/localhost-rsa-chain.pem"
          ????????????????????????type="RSA"?/>

          ????SSLHostConfig>
          Connector>

          接下來聊一個拷問靈魂的問題, 「APR 是如何提升性能的?」

          NioEndpoint 一樣, AprEndpoint 也實現(xiàn)了非阻塞 I/O,它們的區(qū)別是:NioEndpoint 通過調(diào)用 Java 的 NIO API 來實現(xiàn)非阻塞 I/O,而 AprEndpoint 是通過 JNI 調(diào)用 APR 本地庫而實現(xiàn)非阻塞 I/O 的。

          Tomcat 的 Endpoint 組件在接收網(wǎng)絡數(shù)據(jù)時需要預先分配好一塊 Buffer,所謂的 Buffer 就是字節(jié)數(shù)組 byte[] ,Java 通過 JNI 調(diào)用把這塊 Buffer 的地址傳給 C 代碼,C 代碼通過操作系統(tǒng) API 讀取 Socket 并把數(shù)據(jù)填充到這塊 Buffer。

          Java NIO API 提供了兩種 Buffer 來接收數(shù)據(jù):HeapByteBuffer 和 DirectByteBuffer 。

          HeapByteBuffer 對象本身在 JVM 堆上分配,并且它持有的字節(jié)數(shù)組 byte[] 也是在 JVM 堆上分配。但是如果用 HeapByteBuffer 來接收網(wǎng)絡數(shù)據(jù),需要把數(shù)據(jù)從內(nèi)核先拷貝到一個臨時的本地內(nèi)存,再從臨時本地內(nèi)存拷貝到 JVM 堆,而不是直接從內(nèi)核拷貝到 JVM 堆上。

          數(shù)據(jù)從內(nèi)核拷貝到 JVM 堆的過程中,JVM 可能會發(fā)生 GC , GC 過程中對象可能會被移動,也就是說 JVM 堆上的字節(jié)數(shù)組可能會被移動,這樣的話 Buffer 地址就失效了。如果這中間經(jīng)過本地內(nèi)存中轉(zhuǎn),從本地內(nèi)存到 JVM 堆的拷貝過程中 JVM 可以保證不做 GC。

          Tomcat 的 AprEndpoint 通過操作系統(tǒng)層面的 sendfile 特性解決了這個問題,sendfile 系統(tǒng)調(diào)用方式非常簡潔。

          2. 請求處理流程 NioEndPoint

          前面介紹了 AprEndpoint 的請求處理流程,我們在順便看下 Tomcat 默認的 NioEndPoint 處理流程。

          實際上這兩個處理流程非常的相似,區(qū)別基本上是因為非阻塞 I/O 的實現(xiàn)方式。

          • Acceptor 中的 accept() 方法返回一個 Channel 對象,接著把 Channel 對象交給 Poller 去處理。
          • Poller 在內(nèi)部維護一個 Channel 數(shù)組,它在一個死循環(huán)里不斷檢測 Channel 的數(shù)據(jù)就緒狀態(tài),一旦有 Channel 可讀,就生成一個 SocketProcessor 任務對象扔給 Executor 去處理。每個 Poller 線程都有自己的 Queue 。每個 Poller 線程可能同時被多個 Acceptor 線程調(diào)用來注冊 PollerEvent 。Poller 不斷的通過內(nèi)部的 Selector 對象向內(nèi)核查詢 Channel 的狀態(tài),一旦可讀就生成任務類 SocketProcessor 交給 Executor 去處理。Poller 的另一個重要任務是循環(huán)遍歷檢查自己所管理的 SocketChannel 是否已經(jīng)超時,如果有超時就關(guān)閉這個 SocketChannel 。
          • Executor 是線程池,負責運行 SocketProcessor 任務類, SocketProcessorrun() 方法會調(diào)用 Http11Processor 來讀取和解析請求數(shù)據(jù)。ServerSocketChannel 通過 accept() 接受新的連接, accept() 方法返回獲得 SocketChannel 對象,然后將 SocketChannel 對象封裝在一個 PollerEvent 對象中,并將 PollerEvent 對象壓入 PollerQueue 里,這是個典型的生產(chǎn)者 - 消費者模式, AcceptorPoller 線程之間通過 Queue 通信。

          參考

          https://jonhuster.blog.csdn.net/article/details/93297251




          感謝閱讀



          瀏覽 19
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼高清视频 | 大鳮芭久久久久久精高潮 | 大香蕉伊人在线视屏 | 操你逼逼| 翔田千里无遮挡全棵 |