<strike id="ca4is"><em id="ca4is"></em></strike>
  • <sup id="ca4is"></sup>
    • <s id="ca4is"><em id="ca4is"></em></s>
      <option id="ca4is"><cite id="ca4is"></cite></option>
    • 二維碼
      企資網

      掃一掃關注

      當前位置: 首頁 » 企資快報 » 服務 » 正文

      Tomcat性能優化(三)

      放大字體  縮小字體 發布日期:2021-08-21 18:09:38    作者:媒體小英    瀏覽次數:29
      導讀

      九、Tomcat中NIO2通道原理及性能從Tomcat8開始出現了NIO2通道,這個通道利用了NIO2中的最重要的特性,異步IO的java API。從性能角度上來說,從紙面上看該IO模型是非常優秀的,這也是很多書籍推崇的最優秀的IO模型,

      九、Tomcat中NIO2通道原理及性能


      從Tomcat8開始出現了NIO2通道,這個通道利用了NIO2中的最重要的特性,異步IO的java API。

      從性能角度上來說,從紙面上看該IO模型是非常優秀的,這也是很多書籍推崇的最優秀的IO模型,例如《Unix網絡編程》這本圣經,但取決于目前操作系統的支持程度和環境,還有業務邏輯代碼的編寫,NIO2的程序調用并不一定比NIO,甚至比BIO的效率要高。

      我們在沒有實測的情況之下,本文從源碼的角度去分析一下Tomcat8中的這個NIO2通道,后續在相應的章節中,我們會進一步的分析一下Tomcat的4個通道的性能差異。

      1、NIO2的框圖源碼解讀(源碼詳細分析解讀見視頻)

      前面我們已經了解了Tomcat的BIO,NIO,APR這三個通道,對于NIO2的通道框圖大體上和這些沒有太大的區別,如下圖所示,少了一個poller線程,多了一個CompletionHandler。

      和其他通道一樣,Tomcat最前端工作的依然是Endpoint類中的Acceptor線程,該線程主要任務是接收socket包,簡單解析并封裝socket,對其進行包裝為SocketWrapper后,交給工作線程。

      在NIO2的通道下,Acceptor線程結束之后,并不會直接調用工作線程也就是SocketProcessor,而是利用NIO2的機制,利用CompleteHandler完成處理器去異步處理任務。

      這正是CompleteHandler完成處理器的一個特性。

      再對比NIO,BIO兩個通道:

      我們不用像BIO通道那樣去拿著SockerWrapper在工作線程進行阻塞讀,這樣工作線程中的時間會占據網絡IO讀取的時間,導致大并發模式下工作線程暴漲,這也就是經常我們看到很多cpu為什么被占到99%的原因,再怎么設置工作線程無濟于事,因為大量的cpu線程切換太耗時間了;

      而NIO通道采用Reactor的模式去做這個事,Selector承擔了多路分離器這個角色,對于BIO是一大改進,其次java NIO的牛B之處就是操作系統內核緩沖區的就緒通知;

      2、異步IO的運用(具體源碼分析見視頻)


      經過以上分析我們得知三件事:

      1)NIO2這種純異步IO,必須要有操作系統支持,并且性能和這個內核態的事件分離器有著非常大的關系。

      2)對于內核分離器通知CompleteHandler的時機是什么,對比NIO的緩沖區,實質是當內核態緩沖區的數據已經復制到用戶態緩沖區時候,這個時候觸發CompleteHandler,這相當于比NIO的模式更進一步,如下圖:

      NIO只是內核緩沖區就緒才告訴客戶端去讀,這個時候用戶態緩沖區是空的,你得執行完socketChannel.read之后,用戶態緩沖區才會填滿;

      3).因為NIO2的優勢,事件分離器分離器實際是在操作系統內核態的功能,所以不需要用戶態搞一個Selector做事件分發。因此,對比NIO的通道框圖,可以看到缺少了Poller線程這一個環節。

      以下是部分源碼解析(詳細解析見視頻)

      從代碼的角度來看看,Tomcat的NIO2的通道,主要集中在NIO2Endpoint這個類的bind方法。

      關注兩個點:

      1).AsynchronousChannelGroup是異步通道線程組,通過這個類可以給AsynchronousChannel定義線程池的環境,而ExecutorService就是Tomcat中的特有的線程池。

      TaskQueue是隊列,Thread工廠針對于創建的線程名稱進行了一下修改,并且對于線程池的最大,最小,時間都進行了限定,這個線程池在BIO,NIO通道中也是這個,都是一樣的。

      定義完AsynchronousChannelGroup的通道線程組,AsynchronousChannel的read就是運行在通道組中的線程組中,包括從操作系統的內核態多路分離器響應的CompleteHandler,也是從該線程池中取出線程進行運行,這個是很重要的,如果每一次都new Thread的話,會有很大的消耗,所以不如都放在一個線程組中隨取隨用,用完再還;

      2).隨即開啟 AsynchronousChannel通道,并綁定到對應的端口中,這個API使用的就是JAVA NIO2的API。

      之后,Acceptor線程獲得socket包,直接進行包裝為SocketWrapper,之后的流程如第一節中的源碼分析一樣,隨著讀取的執行,異步操作就執行完了,轉而Acceptor線程進行下一個循環,讀取新socket包;

      這時候需要注意的是,在NIO模式下,這個時刻是將SocketWrapper扔給Poller線程,Poller線程中的Selector去輪詢key值,而不是NIO2這種的直接就不管不問了,從這一點上也可以看出,NIO2的異步優勢就在這,事件觸發的機制直接由內核通知,我搞一個CompleteHandler就行,無需在用戶態輪詢。

      3、總結

      由下圖可見,bio,nio都是由用戶態發起數據拷貝(read操作),而nio2(aio)則是由操作系統發起數據拷貝,所有的io操作都是由操作系統主動完成。所以io操作和用戶業務邏輯的執行都是異步化的。

      所以從賬面上來講,NIO2通道相比NIO效率高,因為proactor模式本來就比reactor模式要好,另外還省去了Poller線程,但由于多路事件分離器是內核提供的,不同內核提供的多路事件分離器的事件處理效率不一,對NIO2的通道需要基于實際環境和場景壓測才能得出最終的結論。

      在后續的章節中,會對Tomcat各通道進行壓力實際測試對比,并基于各個通道的實測結果進行詳細的對比和分析。

      十、APR通道到底是個怎么回事?

      APR通道是Tomcat比較有特色的通道,在早期的JDK的NIO框架不成熟的時候,因為java的網絡包的低效,Tomcat使用APR開源項目做網絡IO,這樣有效的緩解了java語言的不足,提供了一個高性能的直接通過jni接口進行底層IO通信內存使用的這么一個通道。

      但是,當JDK的后續版本推出之后,JDK的網絡底層庫的性能也上來了,各種先進的IO模型,線程模型和APR開源項目幾乎不相上下,這個時候,經常會出現一種測試場景是,加上APR通道之后并沒有太多的實質提升,這是可以理解的,但是JDK中的SSL信道的性能至少從目前的角度來看,和APR通道基于openssl的引擎信道實現,還有不小的差距,因為SSL協議中定義的握手協議,交互次數比較多,而openssl項目經歷多年,性能極為高效,因此從目前的Tomcat的APR通道來看,主推的就是這個SSL/TLS協議的高效支持。

      1、TomcatAPR通道的架構圖


      APR通道底層最終是通過tomcat-native實現的,具體的源碼分析講解請觀看視頻

      2、APR通道詳解

      從上圖中可以看到,對于Connector通道總共有這么幾種通道:BIO是阻塞式的通道,NIO是利用高性能的linux(windows也有)的poll或者epoll模型,APR通道就是本文中講的內容,對于目前的JDK還支持NIO2的通道,對于APR來講,SSL Support區別最大,使用的是openssl作為SSL的信道支持,另外從IO模型角度來看,對于Http請求頭的讀取,SSL握手因為調用的JNI也是阻塞的,這個是與NIO和NIO2的差距,但是從SSL信道的支持上用的是高效的openssl。APR通道中依然有Acceptor接收線程池,Poller輪詢,Worker工作線程池,這些和其她通道的架構區別不大,重要的是其關于socket調用和SSL的握手等內容。這部分的源碼分析見視頻

      總之一句話

      APR通道的Socket全部來自c語言實現的socket,非jdk的socket,直接在tomcat層級調用native方法。

      APR通道的SSL信道上下文直接來自于native底層

      3、Tomcat-Native子項目

      tomcat中對于這些jni的調用部分,做出了一個tomcat的子項目,叫做Tomcat-native,在這個調用層級中,一部分是java部分,也就是AprEndpoint類中看到的native方法,這些native方法有很多,這些java的包,對應調用的就是jni的native的C的代碼,是一一對應的,如下圖所示:

      對于tomcat-native最好的教程應該是在example目錄中,這個目錄使用一個例子完整的復現了Tomcat前端APREndpoint的幾個線程組件的工作模式;對于test目錄也可以從這個點切入進去,是一個好的調試tomcat-native代碼的過程。

      4、APR高性能網絡庫(Apache Portable Runtime (APR) project)

      下載:https://mirrors.cnnic.cn/apache/apr/apr-1.6.5.tar.gz

      tomcat-native項目,可以說是作為一個集成包,有點類似于TomEE對于JAVA EE規范的集成,她集成的內容一個是openssl,這個是ssl信道的實現,另外一個就是高性能的apr網絡庫。

      Apache Portable Runtime (APR) project,這個庫定位于在操作系統的底層封裝出一層抽象的高性能庫,在于屏蔽掉操作系統的差異。可以分析出來,APR相當于JDK的一個角色了,只不過她關注的大多在網絡IO相關的這塊,有原子類,編解碼,文件IO,鎖,內存申請與釋放,內存映射,網絡IO,IO多路復用,線程池等等。APR庫對眾多操作系統都有支持。

      總結一下就是,APR提供了對于底層高性能的網絡IO的處理,可以解決Tomcat早期網絡IO低效的問題。

      5、Openssl庫

      tomcat-native除了調用APR網絡庫保證高性能的網絡傳輸以外,對于SSL/TLS的支持還調用了openssl。對于OpenSSL項目來說,市面上大多數的SSL信道實現都是用OpenSSL做的,這也就是說,如果要OpenSSL暴露出一個漏洞出來,那破壞性都是驚人的。

      6、總結

      APR通道只有很小的一部分是java,大部分的源碼都是C的,而且和操作系統的環境有著密切的關系,不同操作系統定制的接口不同,性能特色也不同。

      如下圖所示,java這一層調用的是jni,相當于是一個接口,然后底層tomcat-native,相當于是實現,只不過是用c實現的,然后apr和openssl又是獨立的c組件。

      十一、Tomcat中各通道的sendfile支持

      sendfile實質是linux系統中一項優化技術,用以發送文件和網絡通信時,減少用戶態空間與磁盤倒換數據,而直接在內核級做數據拷貝,這項技術是linux2.4之后就有的,現在已經很普遍的用在了C的網絡端服務器上了,而對于java而言,因為java是高級語言中的高級語言,至少在C語言的層面上可以提供sendfile級別的接口,舉個例子,java中可以通過jni的方式調用c的庫,而這種在tomcat中其實就是APR通道,通過tomcat-native去調用類似于APR庫,這種調用思路雖然增大了java調用鏈條,但可以在java層級中獲得如sendfile的這種linux系統級優化的支持,可謂是一舉多得。

      上述的內容,實際就是本章的背景,本文就從系統調用的層級,逐步講解tomcat中的sendfile是怎么實現的。

      1、傳統的網絡傳輸機制

      大家可以在linux上執行 man sendfile 這個命令,查看sendfile的定義

      上述定義可以看出,sendfile()實際是作用于數據拷貝在兩個文件描述符之間的操作函數.這個拷貝操作是在內核中完成的,所以稱為"零拷貝".sendfile函數比起read和write函數高效得多,因為read和write是要把數據拷貝到用戶應用層操作,多了一個步驟,如下圖所示:

      那么經過sendfile優化過的拷貝機制如下圖所示,直接在內核態拷貝,不用經過用戶態了,這大大提高了執行效率。

      2、linux的sendfile機制(零拷貝)

      3、DefaultServlet的sendfile邏輯

      對于Tomcat中的靜態資源處理,直接對應的就是DefaultServlet了,這個類是嵌入在Tomcat源碼中,專門處理靜態資源的類,靜態資源一般不需要經過處理(也就是不需要拿到用戶態內存中去)直接從服務器返回,所以此類文件最適合走sendfile方式,以下是DefaultServlet中和sendfile相關的源碼邏輯。

      值得注意的一點是,一般http響應的數據包都會進行壓縮,這樣的好處是能極大的減小帶寬占用,而響應頭中發現了compression壓縮屬性,瀏覽器會自動首先進行解壓縮,從而正確的將response響應主體刷到頁面中。

      但是,當sendfile屬性開啟后,這個compression壓縮屬性就不生效了(后面一章會講解sendfile和compression的互斥性),因此,當需要傳輸的文件非常大的時候,而網絡帶寬又是瓶頸的時候,sendfile顯然并不是合適之舉。

      4、sendfile在BIO通道中的實現(不支持)

      以Tomcat9為例,不同的Tomcat前端通道中的sendfile的java包裝是不同的,但實際上都是在調用系統調用sendfile。

      對于BIO(從tomcat8開始已經拋棄BIO通道了,下面源碼截圖來自于tomcat7)來說,JIOEndpoint是不支持sendfile的,這個可以通過代碼中看出來:

      5、sendfile在NIO通道中的實現

      在NIO通道中,有一個useSendfile屬性,這個useSendfile屬性是做什么的呢?

      這個是可以設置在Connector中的,以NIO通道為例,這個useSendfile屬性是允許request進行sendfile的總體開關(前面講的org.apache.tomcat.sendfile.support 屬性是針對于每一個request的),這個useSendfile屬性在NIO通道中默認就是打開的,當reqeust設置org.apache.tomcat.sendfile.support 屬性為true的時候,response就會準備一個SendFileData的數據結構,這個數據結構就是NIO通道下的sendfile的媒介。

      因此,NIO的sendfile實現可以分為三個階段(具體的源碼解析請查看視頻):

      第一階段,實際上就是前面的XXXDefaultServlet中(不僅僅是DefaultServlet,其她的Servlet只要設置這個屬性也可以調用sendfile)對Request的sendfile屬性的設置,當該請求設置上述的屬性后,證明該請求為sendfile請求。

      第二階段,servlet處理完之后,業務邏輯完成,對應的Response該commit了,而在Response的準備階段,會初始化這個SendFileData的數據結構,這塊的代碼邏輯都在Http11NioProcessor類中,下圖中的prepareSendfile方法就是從前面DefaultServlet中設置的reqeust屬性中拿到file名稱,字符位置的start,end,然后將這些屬性作為傳入的參數,初始化SendFileData實例。

      第三階段,我們記得NIO前端通道的Acceptor,Poller線程,Worker線程的三個線程,當Worker線程干完活之后,返回給客戶端,依然要通過Poller線程,也就是會重新注冊KeyEvent,讀取KeyAttachment,這個時候當為sendfile的時候,前面初始化的SendFileData實例是會注冊在KeyAttachment上的,上圖的processSendfile就是Poller線程的run中的一個判斷分支,當為sendfile的時候,Poller線程就對SendFileData數據結構中的file名字取出,通過FileChannel的transferTo方法,這個transferTo方法本質上就是sendfile在tomcat源碼中的具體體現,如下圖所示

      6、sendfile在APR通道中的實現(具體源碼跟蹤分析見視頻)

      在NIO通道中sendfile實現算是比較復雜的了,在APR通道中更加的復雜,我們可以回過頭先看看NIO通道中的sendfile,實際是通過每一個Poller線程中的FileChannel的transferTo方法來實現的,對于transferTo方法是阻塞的,這也就意味著,當文件進行sendfile的時候,Poller線程是阻塞的,而我們前面研究過Tomcat前端,Poller線程是很珍貴的,不僅僅是為某幾個sendfile服務的,這樣會導致Poller線程產生瓶頸,從而拖慢了整個Tomcat前端的效率。

      APR通道是開辟一個獨立的線程來處理sendfile的,如下圖所示,這樣做的好處不言自明,Poller就干Poller的事,而遇到Sendfile的需求的時候,sendfile線程就挺身而出,把活兒給接了。

      最后,對于APR通道是通過JNI調用的APR庫,sendfile自然就不是java的API了

      7、總結
      SendFile實際上是操作系統的優化,Tomcat中基于在不同的通道中有不同的實現,配置也不盡相同,但實際上都是底層操作系統的SendFile的系統調用!

      十、調整和tomcat相關的JVM參數進行優化

      1、設置串行垃圾回收器(nio模式,最大線程1000)


      壓測步驟:

      1)在tomcat啟動腳本catalina.sh里設置以下腳本:

      年輕代、老年代均使用串行收集器,初始堆內存64M,最大堆內存512M,打印gc時間戳等信息,生成gc日志文件

      JAVA_OPTS="-XX:+UseSerialGC -Xms64m -Xmx512m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX: +PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:../logs/gc.log"

      2)設置后啟動tomcat,使用jmeter進行壓測(jmeter設置線程為1000,每個線程循環10次),訪問test_web

      3)查看吞吐量

      壓測結果:平均時間1.585s,吞吐量378.6/s,異常1.12%

      將gc.log拷貝出來,改名gc1.log。預備比較

      2、設置并行垃圾回收器(nio模式,最大線程1000)

      壓測步驟:

      1)、在tomcat啟動腳本catalina.sh里設置以下腳本:

      年輕代、老年代均改成并行垃圾收集器,初始堆內存64M,最大堆內存512M,打印gc時間戳等信息,生成gc日志文件。

      #JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms64m -Xmx512m -XX:+PrintGCDetails -XX :+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:../logs/gc.log"

      2)、刪除gc.log

      rm -rf gc.log

      3)、設置后重啟tomcat,使用jmeter進行壓測(jmeter設置線程為1000,每個線程循環10次),訪問test_web,查看吞吐量

      壓測結果:平均時間1.161s,吞吐量407.7/s,異常0.40%

      將gc.log拷貝出來,改名gc2.log。預備比較

      分析結論:

      可以看出設置成并行垃圾收集器之后平均執行時間減少了,吞吐量增加了,異常率也減少了,總體性能有了很大的提高。

      3、查看gc日志文件

      將gc1.log和gc2.log文件分別上傳到gceasy.io進行在線分析,分析結果如下:

      gc1.log中的gc總次數是13次

      gc2.log中gc總次數12次,比串行時少了1次,性能是有所提升的。

      4、調整年輕代大小

      再次重新設置啟動參數,依然是并行垃圾收集器,不過我們增加了初始化堆內存和最大堆內存,分別設置為128m和1024m。

      JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms128m -Xmx1024m -XX:NewSize=64m -XX:M axNewSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHe apAtGC -Xloggc:../logs/gc.log"

      設置完后再次重啟,用jmeter進行壓測(壓測參數不變),結果如下:

      壓測結果:平均時間0.943s,吞吐量433.5/s,異常0.29%

      性能再一次的得到了提升。再次分析gc.log 如下圖:

      gc收集總次數減少為8次,從gc的收集次數也再次證明了調整參數后性能的確得到了極大的提升。

      5、設置G1垃圾回收器(jdk9之后默認G1,測試用的jdk8)

      再次重新設置啟動參數,修改垃圾收集器為G1收集器,參數如下:

      JAVA_OPTS="-XX:+UseG1GC -Xms128m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+Pr intGCDateStamps -XX:+PrintHeapAtGC -Xloggc:../logs/gc.log"

      重啟tomcat后使用jmeter再次壓測(壓測參數不變),壓測結果如圖:

      壓測結果:平均時間0.897s,吞吐量431.2/s,異常0.14%

      總體性能再一次得到了提升。

      6、總結

      通過不斷的調優,我們得出4次壓測結果如下:

      第1次壓測結果:平均時間1.585s,吞吐量378.6/s,異常1.12%

      第2次壓測結果:平均時間1.161s,吞吐量407.7/s,異常0.40%

      第3次壓測結果:平均時間0.943s,吞吐量433.5/s,異常0.29%

      第4次壓測結果:平均時間0.897s,吞吐量431.2/s,異常0.14%

      平均時間一次比一次短,吞吐量一次比一次大,異常率一次比一次少,所以總體性能一次比一次優越。

      結論:對tomcat性能優化需要不斷的進行參數調整,然后測試結果,可能每次調優結果都有差異,這就需要借助于gc的可視化工具來看gc的情況,再幫我我們做出決策應該調整哪些參數,從而達到一個相對理想的優化效果。

       
      (文/媒體小英)
      免責聲明
      本文僅代表作發布者:媒體小英個人觀點,本站未對其內容進行核實,請讀者僅做參考,如若文中涉及有違公德、觸犯法律的內容,一經發現,立即刪除,需自行承擔相應責任。涉及到版權或其他問題,請及時聯系我們刪除處理郵件:weilaitui@qq.com。
       

      Copyright ? 2016 - 2025 - 企資網 48903.COM All Rights Reserved 粵公網安備 44030702000589號

      粵ICP備16078936號

      微信

      關注
      微信

      微信二維碼

      WAP二維碼

      客服

      聯系
      客服

      聯系客服:

      在線QQ: 303377504

      客服電話: 020-82301567

      E_mail郵箱: weilaitui@qq.com

      微信公眾號: weishitui

      客服001 客服002 客服003

      工作時間:

      周一至周五: 09:00 - 18:00

      反饋

      用戶
      反饋

      午夜久久久久久网站,99久久www免费,欧美日本日韩aⅴ在线视频,东京干手机福利视频
        <strike id="ca4is"><em id="ca4is"></em></strike>
      • <sup id="ca4is"></sup>
        • <s id="ca4is"><em id="ca4is"></em></s>
          <option id="ca4is"><cite id="ca4is"></cite></option>
        • 主站蜘蛛池模板: 绝美女神抬臀娇吟| 日韩字幕一中文在线综合| 日本免费精品一区二区三区| 波霸影院一区二区| 日本特黄特黄刺激大片免费| 图片区小说校园综合| 国产午夜精品一二区理论影院| 人妻无码久久一区二区三区免费 | 国产乱了真实在线观看| 亚洲第一成年免费网站| 亚洲精品无码mv在线观看网站| 久久91精品国产一区二区| 12345国产精品高清在线| 精品无码一区在线观看| 日韩精品一区在线| 国产麻豆成av人片在线观看| 四虎影视884a精品国产四虎| 亚洲人成网亚洲欧洲无码| xyx性爽欧美| 耻辱の女潜入搜查官正在播放| 欧美a级片在线观看| 天堂网www中文在线| 国产99久久九九精品无码| 久操免费在线观看| 69国产成人综合久久精品91| 精品无码av无码免费专区| 女王厕便器vk| 四虎1515hh丶com| 久久亚洲综合色| 亚洲另类专区欧美制服| 毛片网在线观看| 女人张腿让男桶免费视频网站| 国产亚洲精品bt天堂精选| 亚洲三级黄色片| 67194久久| 日韩精品第一区| 国产一级做a爰片久久毛片| 久久亚洲国产精品| 精品韩国亚洲av无码不卡区| 日本午夜精品一区二区三区电影 | 疯狂做受xxxx高潮视频免费|