JVM高手之路七(tomcat調優以及tomcat七、8性能對比)

 

 版權聲明:本文爲博主原創文章,未經博主容許不得轉載。 https://blog.csdn.net/lirenzuo/article/details/77164033

由於每一個鏈路都會對其性能形成影響,應該是全鏈路的修改壓測(ak大神常常說全鏈路大笑)。本次基本就是局域網,因此並無怎麼優化,其實也應該考慮進去的。html

 

Linux系統參數層面的修改:java

一、修改可打開文件數和用戶最多可開發進程數nginx

命令:ulimit -n 655350web

      ulimit –u 655350apache

能夠經過ulimit –a查看參數設置,不設置時默認爲1024,默認狀況下,你會發現請求數到到必定數值後,再也上不去了。tomcat

二、操做系統內核優化服務器

net.ipv4.tcp_max_tw_buckets = 6000cookie

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個都調整同樣的狀況下,開始準備測試tomcat7(jdk7)與tomcat8(jdk8)的一些性能測試了。

因爲各各緣由複雜服務無法測試,先僅僅是測試靜態頁面。

 

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


 

 

 

 

Tomcat七、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這個參數和併發數,當併發數超過這個參數則請求會排隊,這時響應的快慢就看你的程序性能了。

 

 

這些僅僅是告訴咱們,若是須要了解細節還須要閱讀下源碼。有些讀了源碼可能參數的理解更清楚了。


 

  1.  
    public class StandardThreadExecutor extends LifecycleMBeanBase
  2.  
    implements Executor, ResizableExecutor {
  3.  
    //默認線程的優先級
  4.  
    protected int threadPriority = Thread.NORM_PRIORITY;
  5.  
    //守護線程
  6.  
    protected boolean daemon = true;
  7.  
    //線程名稱的前綴
  8.  
    protected String namePrefix = "tomcat-exec-";
  9.  
    //最大線程數默認200個
  10.  
    protected int maxThreads = 200;
  11.  
    //最小空閒線程25個
  12.  
    protected int minSpareThreads = 25;
  13.  
    //超時時間爲6000
  14.  
    protected int maxIdleTime = 60000;
  15.  
    //線程池容器
  16.  
    protected ThreadPoolExecutor executor = null;
  17.  
    //線程池的名稱
  18.  
    protected String name;
  19.  
    //是否提早啓動線程
  20.  
    protected boolean prestartminSpareThreads = false;
  21.  
    //隊列最大大小
  22.  
    protected int maxQueueSize = Integer.MAX_VALUE;
  23.  
    //爲了不在上下文中止以後,全部的線程在同一時間段被更新,因此進行線程的延遲操做
  24.  
    protected long threadRenewalDelay = 1000L;
  25.  
    //任務隊列
  26.  
    private TaskQueue taskqueue = null;
  27.  
     
  28.  
    //容器啓動時進行,具體可參考org.apache.catalina.util.LifecycleBase#startInternal()
  29.  
    @Override
  30.  
    protected void startInternal() throws LifecycleException {
  31.  
    //實例化任務隊列
  32.  
    taskqueue = new TaskQueue(maxQueueSize);
  33.  
    //自定義的線程工廠類,實現了JDK的ThreadFactory接口
  34.  
    TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
  35.  
    //這裏的ThreadPoolExecutor是tomcat自定義的,不是JDK的ThreadPoolExecutor
  36.  
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
  37.  
    executor.setThreadRenewalDelay(threadRenewalDelay);
  38.  
    //是否提早啓動線程,若是爲true,則提早初始化minSpareThreads個的線程,放入線程池內
  39.  
    if (prestartminSpareThreads) {
  40.  
    executor.prestartAllCoreThreads();
  41.  
    }
  42.  
    //設置任務容器的父級線程池對象
  43.  
    taskqueue.setParent(executor);
  44.  
    //設置容器啓動狀態
  45.  
    setState(LifecycleState.STARTING);
  46.  
    }
  47.  
     
  48.  
    //容器中止時的生命週期方法,進行關閉線程池和資源清理
  49.  
    @Override
  50.  
    protected void stopInternal() throws LifecycleException {
  51.  
     
  52.  
    setState(LifecycleState.STOPPING);
  53.  
    if ( executor != null ) executor.shutdownNow();
  54.  
    executor = null;
  55.  
    taskqueue = null;
  56.  
    }
  57.  
     
  58.  
    //這個執行線程方法有超時的操做,參考org.apache.catalina.Executor接口
  59.  
    @Override
  60.  
    public void execute(Runnable command, long timeout, TimeUnit unit) {
  61.  
    if ( executor != null ) {
  62.  
    executor.execute(command,timeout,unit);
  63.  
    } else {
  64.  
    throw new IllegalStateException("StandardThreadExecutor not started.");
  65.  
    }
  66.  
    }
  67.  
     
  68.  
    //JDK默認操做線程的方法,參考java.util.concurrent.Executor接口
  69.  
    @Override
  70.  
    public void execute(Runnable command) {
  71.  
    if ( executor != null ) {
  72.  
    try {
  73.  
    executor.execute(command);
  74.  
    } catch (RejectedExecutionException rx) {
  75.  
    //there could have been contention around the queue
  76.  
    if ( !( (TaskQueue) executor.getQueue()).force(command) ) throw new RejectedExecutionException("Work queue full.");
  77.  
    }
  78.  
    } else throw new IllegalStateException("StandardThreadPool not started.");
  79.  
    }
  80.  
     
  81.  
    //因爲繼承了org.apache.tomcat.util.threads.ResizableExecutor接口,因此能夠從新定義線程池的大小
  82.  
    @Override
  83.  
    public boolean resizePool(int corePoolSize, int maximumPoolSize) {
  84.  
    if (executor == null)
  85.  
    return false;
  86.  
     
  87.  
    executor.setCorePoolSize(corePoolSize);
  88.  
    executor.setMaximumPoolSize(maximumPoolSize);
  89.  
    return true;
  90.  
    }
  91.  
    }

 

 

Tomcat的線程池的名字也叫做ThreadPoolExecutor,剛開始看源代碼的時候還覺得是使用了JDK的ThreadPoolExecutor了呢,後面仔細查看才知道是Tomcat本身實現的一個ThreadPoolExecutor,不過基本上都差很少。

 

看到這裏覺得tomcat線程池的原理和jdk的線程池原理同樣了,其實不是的。

問題的關鍵在這裏


 

TaskQueue這個任務隊列是專門爲線程池而設計的。優化任務隊列以適當地利用線程池執行器內的線程。

Jdk的execute執行策略:        優先offer到queue,queue滿後再擴充線程到maxThread,若是已經到了maxThread就reject

Tomcat的execute執行策略: 優先擴充線程到maxThread,再offer到queue,若是滿了就reject比較適合於業務處理須要遠程資源的場景


 

修改成:

 

  1.  
    <Executor name= "tomcatThreadPool" namePrefix="catalina-exec-"
  2.  
    maxThreads= "350" minSpareThreads="20" prestartminSpareThreads="true"/>


 

 

  1.  
    <Connector executor= "tomcatThreadPool" acceptCount="300000"
  2.  
    port= "8080" protocol="HTTP/1.1"
  3.  
    connectionTimeout= "20000"
  4.  
    redirectPort= "8443" />


因爲多是靜態頁面返回很快,設置500 800 1000線程效果都不怎麼明顯,若是是加項目應該會有所區別,因此線程池也並非越多越好。

Tomcat7和tomcat8性能都有所提高,因此池很重要,可是8和7的tps在都提升了2000左右。在修改線程池以後,查看jvm gc狀況都良好,因此並無在此調整jvm參數了。

通過這麼多分析也瞭解到了tomcat該如何調優了,以及tomcat七、tomcat8的一些性能區別了。

因爲測試的是靜態頁面,不少有些問題尚未涉及到,後續若是測試服務估計須要修改,調試排查的問題更多,到時候繼續查看後續文章!!

相關文章
相關標籤/搜索