JVM菜鳥進階高手之路七(tomcat調優以及tomcat7、8性能對比)

来源:http://www.cnblogs.com/lirenzuo/archive/2017/09/23/7580104.html
-Advertisement-
Play Games

轉載請註明原創出處,謝謝! 因為每個鏈路都會對其性能造成影響,應該是 全鏈路的修改壓測 (ak大神經常說全鏈路!)。本次基本就是區域網,所以並沒有怎麼優化,其實也應該考慮進去的。 Linux系統參數層面的修改: 1. 修改可打開文件數和用戶最多可開發進程數 命令: 可以通過ulimit –a查看參數 ...


轉載請註明原創出處,謝謝!

因為每個鏈路都會對其性能造成影響,應該是全鏈路的修改壓測(ak大神經常說全鏈路!)。本次基本就是區域網,所以並沒有怎麼優化,其實也應該考慮進去的。

Linux系統參數層面的修改:

  1. 修改可打開文件數和用戶最多可開發進程數
    命令:

      ulimit -n 655350
      ulimit –u 655350
    可以通過ulimit –a查看參數設置,不設置時預設為1024,預設情況下,你會發現請求數到到一定數值後,再也上不去了。
  2. 操作系統內核優化

    net.ipv4.tcp_max_tw_buckets = 6000
    timewait 的數量,預設是180000。
    net.ipv4.ip_local_port_range = 1024 65000
    允許系統打開的埠範圍。
    net.ipv4.tcp_tw_recycle = 1
    啟用timewait 快速回收。
    net.ipv4.tcp_tw_reuse = 1
    開啟重用。允許將TIME-WAIT sockets 重新用於新的TCP 連接。
    net.ipv4.tcp_syncookies = 1
    開啟SYN Cookies,當出現SYN等待隊列溢出時,啟用cookies來處理。
    net.core.somaxconn = 262144
    web 應用中listen函數的backlog預設會給我們內核參數的net.core.somaxconn限制到128,而nginx定義的NGX_LISTEN_BACKLOG預設為511,所以有必要調整這個值。
    net.core.netdev_max_backlog = 262144
    每個網路介面接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目。
    net.ipv4.tcp_max_orphans = 262144
    系統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。如果超過這個數字,故而連接將即刻被覆位並列印出警告信息。這個限制僅僅是為了防止簡單的DoS攻擊,不能過分依靠它或者人為地減小這個值,更應該增加這個值(如果增加了記憶體之後)。
    net.ipv4.tcp_max_syn_backlog = 262144
    記錄的那些尚未收到客戶端確認信息的連接請求的最大值。對於有128M記憶體的系統而言,預設值是1024,小記憶體的系統則是128。
    net.ipv4.tcp_timestamps = 0
    時間戳可以避免序列號的卷繞。一個1Gbps的鏈路肯定會遇到以前用過的序列號。時間戳能夠讓內核接受這種“異常”的數據包。這裡需要將其關掉。
    net.ipv4.tcp_synack_retries = 1
    為了打開對端的連接,內核需要發送一個SYN 並附帶一個回應前面一個SYN的ACK。也就是所謂三次握手中的第二次握手。這個設置決定了內核放棄連接之前發送SYN+ACK包的數量。
    net.ipv4.tcp_syn_retries = 1
    在內核放棄建立連接之前發送SYN 包的數量。
    net.ipv4.tcp_fin_timeout = 1
    如果套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間。對端可以出錯並永遠不關閉連接,甚至意外當機。預設值是60秒。2.2內核的通常值是180秒,3你可以按這個設置,但要記住的是,即使你的機器是一個輕載的WEB伺服器,也有因為大量的死套接字而記憶體溢出的風險,FIN-WAIT-2的危險性比FIN-WAIT-1要小,因為它最多只能吃掉1.5K記憶體,但是它們的生存期長些。
    net.ipv4.tcp_keepalive_time = 30
    當keepalive 起用的時候,TCP發送keepalive消息的頻度。預設是2小時。
    內核參數優化設置在/etc/sysctl.conf文件中。

上面2個都調整一樣的情況下,開始準備測試tomcat7jdk7)與tomcat8jdk8)的一些性能測試了。
由於各各原因複雜服務沒法測試,先僅僅是測試靜態頁面。


jvm層面優化:

Jdk7:

-Xms2G
-Xmx2G
-Xmn512m
-XX:PermSize=512M 
-XX:MaxPermSize=512M 
-XX:+UseConcMarkSweepGC 
-XX:+CMSClassUnloadingEnabled 
-XX:+HeapDumpOnOutOfMemoryError 
-verbose:gc 
-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDateStamps 
-Xloggc:/appl/gc.log 
-XX:CMSInitiatingOccupancyFraction=75 
-XX:+UseCMSInitiatingOccupancyOnly  

Jdk8:

-Xms2G
-Xmx2G
-Xmn512m 
-XX:MetaspaceSize=512M 
-XX:MaxMetaspaceSize=512M 
-XX:+UseConcMarkSweepGC 
-XX:+CMSClassUnloadingEnabled 
-XX:+HeapDumpOnOutOfMemoryError 
-verbose:gc 
-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDateStamps 
-Xloggc:/appl/gc.log 
-XX:CMSInitiatingOccupancyFraction=75 
-XX:+UseCMSInitiatingOccupancyOnly   

需要特別說明下:

元數據空間,專門用來存元數據的,它是jdk8里特有的數據結構用來替代perm。

Jdk7:
出現了多次Full GC了。其中,CMS-initial-mark和CMS-remark會stop-the-world。
所以選擇cms垃圾回收器,用jstat 相關命令看到的FGC每次都是加2的變化情況。
Jdk8:
一次Full GC也沒有發生。從這裡也可以看出tomcat8的實現機制比tomcat7的要好些(相同條件沒有產生多餘對象從而導致Full GC問題)。

需要特別說明下:
年輕代的gc日誌7和8略有不同
jdk8把日誌打得更全了 ,jdk8的gc日誌與jdk7的有所不同,聽大佬們說各各jdk的日誌都有所不同,其實這裡8的這個和7的意思一樣,只是7沒有表達出來而已。

通過壓力測試結果來看,jdk7每隔一段時間會出現tps大的下降,就是俗話說的卡頓。
而jdk8沒有啥卡頓現象

而jdk7的波動就特別明顯

該效果8比7的效果請求要好。

由於jdk7 gc日誌,
CMS開始回收tenured generation collection。這階段是CMS初始化標記的階段,從垃圾回收的“根對象”開始,且只掃描直接與“根對象”直接關聯的對象,並做標記,在此期間,其他線程都會停止。

tenured generation的空間是1572864K,在容量為1205558K時開始執行初始標記。
說明-XX:CMSInitiatingOccupancyFraction=75已經達到觸發(Background )CMS GC的條件。

應該擴大堆空間大小,在此修改僅僅是修改了堆其他不變,其他參數還是原來上面的參數
Jdk7,jdk8:

-Xms4G -Xmx4G -Xmn1365m  

查看gc日誌,發現的確都沒有FGC了,但是ygc差距很大,在此表示tomcat8(jdk8)比tomcat7(jdk7)好好像。
Jdk7 ygc時間過長:

Jdk8 ygc非常好:

其實對於ygc的分享特別複雜,jvm的參數調整算是小調,最關鍵的應該在產生對象的地方,即應用本事,採用合理的架構,合理的數據結構結合一些技巧來達到等。
由於測試的是靜態頁面,那麼只有tomcat代碼了,表示8的實現比7的實現方面的確要好(有空去準備去讀讀tomcat源碼到時候在分享分享)。

通過日誌查看jdk7的老年代使用率很低,準備在此進行調整,在堆大小不變的情況下調整年輕代的大小。
Jdk7,jdk8都進行調整其他參數還保持上面不變。

-Xms4G -Xmx4G -Xmn3g   

效果有所改善(tps也張了200多),但是還是不如jdk8的,可能是tomcat內部實現8就是比7好。
次中間還嘗試過更大堆以及年輕代的調整 如6G 8G 10G等都沒有太大變化有些還不如4G點這個好,所以並不是堆空間設置越大越好。
Jvm目前只能調到這塊了,後續如果有啥發現或者大佬們的建議在調整。


Tomcat本身這塊的調優

Tomcat 7/8 的優化參數有點不一樣,最好按下麵的方式看一下官網這個文檔是否還保留著這個參數
啟動tomcat,訪問該地址,下麵要講解的一些配置信息,在該文檔下都有說明的:
文檔:http://127.0.0.1:8080/docs/config
你也可以直接看網路版本:
Tomcat 7 文檔:https://tomcat.apache.org/tomcat-7.0-doc/config/
Tomcat 8 文檔:https://tomcat.apache.org/tomcat-8.0-doc/config/
如果你需要查看 Tomcat 的運行狀態可以配置tomcat管理員賬戶,然後登陸Tomcat後臺進行查看。

在修改jvm參數之後tps怎麼都上不去的情況下麵通過查看線程dump

Tomcat7、tomcat8情況一樣,發現很多都堵塞在這塊了。
這塊涉及到代碼這塊了(tomcat的源碼這塊了)
通過查看源碼發現這塊是涉及到了tomcat線程池這塊了,稍微會詳細說明下,先看看一些簡單配置。

預設配置,可以配置寫那些值呢?
Tomcat8多了一個nio2
這個也比較重要
這個就是關於池的配置了,有那些參數和怎麼實現的呢?

Tomcat的實現在org.apache.catalina.core.StandardThreadExecutor
裡面的參數有:

簡單理解就是:

maxThreads - Tomcat線程池最多能起的線程數
maxConnections - Tomcat最多能併發處理的請求(連接)
acceptCount - Tomcat維護最大的對列數
minSpareThreads - Tomcat初始化的線程池大小或者說Tomcat線程池最少會有這麼多線程。
比較容易弄混的是maxThreads和maxConnections這兩個參數:

maxThreads是指Tomcat線程池做多能起的線程數
maxConnections則是Tomcat一瞬間做多能夠處理的併發連接數。比如maxThreads=1000,maxConnections=800,假設某一瞬間的併發時1000,那麼最終Tomcat的線程數將會是800,即同時處理800個請求,剩餘200進入隊列“排隊”,如果acceptCount=100,那麼有100個請求會被拒掉。

註意:根據前面所說,只是併發那一瞬間Tomcat會起800個線程處理請求,但是穩定後,某一瞬間可能只有很少的線程處於RUNNABLE狀態,大部分線程是TIMED_WAITING,如果你的應用處理時間夠快的話。所以真正決定Tomcat最大可能達到的線程數是maxConnections這個參數和併發數,當併發數超過這個參數則請求會排隊,這時響應的快慢就看你的程式性能了。

這些僅僅是告訴我們,如果需要瞭解細節還需要閱讀下源碼。有些讀了源碼可能參數的理解更清楚了。

public class StandardThreadExecutor extends LifecycleMBeanBase  
        implements Executor, ResizableExecutor {  
    //預設線程的優先順序  
    protected int threadPriority = Thread.NORM_PRIORITY;  
    //守護線程  
    protected boolean daemon = true;  
    //線程名稱的首碼  
    protected String namePrefix = "tomcat-exec-";  
    //最大線程數預設200個  
    protected int maxThreads = 200;  
    //最小空閑線程25個  
    protected int minSpareThreads = 25;  
    //超時時間為6000  
    protected int maxIdleTime = 60000;  
    //線程池容器  
    protected ThreadPoolExecutor executor = null;  
    //線程池的名稱  
    protected String name;  
     //是否提前啟動線程  
    protected boolean prestartminSpareThreads = false;  
    //隊列最大大小  
    protected int maxQueueSize = Integer.MAX_VALUE;  
    //為了避免在上下文停止之後,所有的線程在同一時間段被更新,所以進行線程的延遲操作  
    protected long threadRenewalDelay = 1000L;  
    //任務隊列  
    private TaskQueue taskqueue = null;  
  
    //容器啟動時進行,具體可參考org.apache.catalina.util.LifecycleBase#startInternal()  
    @Override  
    protected void startInternal() throws LifecycleException {  
        //實例化任務隊列  
        taskqueue = new TaskQueue(maxQueueSize);  
        //自定義的線程工廠類,實現了JDK的ThreadFactory介面  
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());  
        //這裡的ThreadPoolExecutor是tomcat自定義的,不是JDK的ThreadPoolExecutor  
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);  
        executor.setThreadRenewalDelay(threadRenewalDelay);  
        //是否提前啟動線程,如果為true,則提前初始化minSpareThreads個的線程,放入線程池內  
        if (prestartminSpareThreads) {  
            executor.prestartAllCoreThreads();  
        }  
        //設置任務容器的父級線程池對象  
        taskqueue.setParent(executor);  
        //設置容器啟動狀態  
        setState(LifecycleState.STARTING);  
    }  
  
  //容器停止時的生命周期方法,進行關閉線程池和資源清理  
    @Override  
    protected void stopInternal() throws LifecycleException {  
  
        setState(LifecycleState.STOPPING);  
        if ( executor != null ) executor.shutdownNow();  
        executor = null;  
        taskqueue = null;  
    }  
  
    //這個執行線程方法有超時的操作,參考org.apache.catalina.Executor介面  
    @Override  
    public void execute(Runnable command, long timeout, TimeUnit unit) {  
        if ( executor != null ) {  
            executor.execute(command,timeout,unit);  
        } else {   
            throw new IllegalStateException("StandardThreadExecutor not started.");  
        }  
    }  
  
    //JDK預設操作線程的方法,參考java.util.concurrent.Executor介面  
    @Override  
    public void execute(Runnable command) {  
        if ( executor != null ) {  
            try {  
                executor.execute(command);  
            } catch (RejectedExecutionException rx) {  
                //there could have been contention around the queue  
                if ( !( (TaskQueue) executor.getQueue()).force(command) ) throw new RejectedExecutionException("Work queue full.");  
            }  
        } else throw new IllegalStateException("StandardThreadPool not started.");  
    }  
  
    //由於繼承了org.apache.tomcat.util.threads.ResizableExecutor介面,所以可以重新定義線程池的大小  
    @Override  
    public boolean resizePool(int corePoolSize, int maximumPoolSize) {  
        if (executor == null)  
            return false;  
  
        executor.setCorePoolSize(corePoolSize);  
        executor.setMaximumPoolSize(maximumPoolSize);  
        return true;  
    }  
}  

Tomcat的線程池的名字也叫作ThreadPoolExecutor,剛開始看源代碼的時候還以為是使用了JDK的ThreadPoolExecutor了呢,後面仔細查看才知道是Tomcat自己實現的一個ThreadPoolExecutor,不過基本上都差不多。
看到這裡以為tomcat線程池的原理和jdk的線程池原理一樣了,其實不是的。
問題的關鍵在這裡

TaskQueue這個任務隊列是專門為線程池而設計的。優化任務隊列以適當地利用線程池執行器內的線程。
Jdk的execute執行策略: 優先offer到queue,queue滿後再擴充線程到maxThread,如果已經到了maxThread就reject
Tomcat的execute執行策略: 優先擴充線程到maxThread,再offer到queue,如果滿了就reject比較適合於業務處理需要遠程資源的場景

修改為:

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"  
       maxThreads="350" minSpareThreads="20" prestartminSpareThreads="true"/>  
<Connector executor="tomcatThreadPool" acceptCount="300000"  
               port="8080" protocol="HTTP/1.1"  
               connectionTimeout="20000"  
               redirectPort="8443" />  

由於可能是靜態頁面返回很快,設置500 800 1000線程效果都不怎麼明顯,如果是加項目應該會有所區別,所以線程池也並不是越多越好。
Tomcat7和tomcat8性能都有所提升,所以池很重要,但是8和7的tps在都提高了2000左右。在修改線程池之後,查看jvm gc情況都良好,所以並沒有在此調整jvm參數了。
經過這麼多分析也瞭解到了tomcat該如何調優了,以及tomcat7、tomcat8的一些性能區別了。
由於測試的是靜態頁面,很多有些問題還沒有涉及到,後續如果測試服務估計需要修改,調試排查的問題更多,到時候繼續查看後續文章!!


個人公眾號

匠心零度公眾號.jpg


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 回到目錄 對於Dapper是一個輕量級的數據訪問框架,而需要使用者去自己做SQL,它,只是一個數據訪問者! 對些,Dapper推出了Contrib擴展包,它可以友好的讓開發人員使用linq,而不需要寫SQL,但在使用時要註意,你的增,刪,改,單表查詢是可以用它的,但對於多表的join操作就不要用了, ...
  • 目前我用的vs2017的版本是15.3.5。生成解決方案有時會提示如下: 開始以為是許可權的問題,找到相應的目錄設置everyone許可權,再次生成還是不行。重啟VS試了下,還是不行。 最後無奈重啟下電腦,再重新生成,終於沒有這個問題了。 可是好景不長,改了代碼,再次重新生成,又出現了這個問題,都快被逼 ...
  • dynamic是Framework4.0的新特性,dynamic的出現讓C#具有了弱語言類型的特性,編譯器在編譯的時候,不再對類型進行檢查,不會報錯,但是運行時如果執行的是不存在的屬性或者方法,運行程式還是會拋出RuntimeBinderException異常。 var 與 dynamic 的區別 ...
  • 最近.NET Core升級到2.0後開始慢慢搗鼓的多了起來,但遇到了不少坑,所以特來記錄下。 第一個坑 條件編譯符 我們在編寫一些方法的時候通常會為Debug模式增加一些輸出日誌等以便我們檢查,也會為Release模式增加或修改一些特定的參數,但今天我在寫這些的時候就遇到了這個坑#if !DEBUG ...
  • 背水一戰 Windows 10 之 控制項(WebView): 對 WebView 中的內容截圖, 通過 Share Contract 分享 WebView 中的被選中的內容 ...
  • lintcode :First Unique Number In Stream ...
  • 近期,DataCamp發佈了jupyter notebook的 cheat sheet,【Python數據之道】第一時間與大家一起來分享下該cheat sheet的內容。 以下是該cheat sheet的部分內容: 各位小伙伴可以從DataCamp的網站獲取該cheat sheet的pdf版,當然, ...
  • 題目描述 如圖所示為某生態系統的食物網示意圖,據圖回答第1小題現在給你n個物種和m條能量流動關係,求其中的食物鏈條數。物種的名稱為從1到n編號M條能量流動關係形如a1 b1a2 b2a3 b3......am-1 bm-1am bm其中ai bi表示能量從物種ai流向物種bi,註意單獨的一種孤立生物 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...