後端調優基礎——Tomcat調優

jdk8和Tomcat8.5是JDK和Tomcat的史詩級提升,直接把單車變跑車,所以如果你還是使用的jdk7和Tomcat8.5以後的版本,那可以考慮去線上換一下,但是不知道到時是架構師打死你還是你打死架構師

Tomcat的四種運行模式:

  • BIO:同步阻塞模式,Tomcat7及之前默認的模式,性能最差,Tomcat8.5後已拋棄
  • NIO:同步非阻塞模式,Tomcat8及之後默認的模式,性能高,現在的主流模式
  • AIO:異步非阻塞模式,由於純異步,性能最高
  • APR:需要額外安裝依賴庫,從操作系統級別解決異步IO,大幅度的提高服務器的處理和響應性能,也是Tomcat運行高併發應用的首選模式,但是使用困難,依賴系統的底層網絡包,團隊沒有C++大神和熟悉Linux底層的大神還是別拿出來秀了

主流是NIO模式,我們主要學習NIO模式的

配置優化

server status

通過配置可以看到Tomcat管理頁server status,利用server status幫我們獲取Tomcat的信息
我們需要修改一下配置文件,conf/tomcat-users.xml和webapps/manager/META-INF/context.xml。
找到conf/tomcat-users.xml

<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<user username="tzb" password="123456" roles="manager-gui,admin-gui,manager-script,manager-jmx,manager-status"/>

找到webapps/manager/META-INF/context.xml,將以下內容註釋掉,我已經註釋掉了,這樣就能遠程訪問web manager了:

通過server status可以看到jvm的信息,還要很多的選項可供,例如應用程序列表、JVM信息、NIO模型等

禁用ajp協議

ajp協議是基於TCP協議的,Tomct使用ajp協議主要是爲了連接http apache服務器的,http apache這個服務器已經被Ngnix完爆了,如果沒有什麼特殊癖好的話應該沒人會用它了,但是這個配置會在程序中默認跑着十個線程,所以爲了性能把這個沒用的功能去除,如果不是2020年2月以後下載的Tomcat的話,是沒有默認禁用的,所以需要手動禁掉

我的Tomcat是默認禁止的,我在配置中開啓了ajp,我們看到這裏跑了十個線程,跑了線程又用不到,浪費CUP的性能

在conf/server.xml中把這個配置註釋了就禁止掉ajp了(如果你有特殊的癖好,想打開ajp,就把註釋打開, 並且把secretRequired="true"修改爲secretRequired="")

重啓服務器,查看server status,ajp已經沒有了

自定義Tomcat線程池

Tomcat需要給每一個請求創建線程,Tomcat默認的線程池最大線程數是200,核心線程數是10,如何併發高的場景,Tomcat就得不斷的創建和銷燬線程,所以就得自定義線程池提高核心線程數,這樣可以幫助我們提高性能。但是如果不是是連接請求特別多的場景,最後別亂改,核心線程是需要佔用內存的

先看看我們沒修改前的狀態,我使用Jvisualvm工具監控Tomcat的線程狀態,我們看到這裏默認是十個線程,由於我用的是Tomcat8.5所以運行模式默認是nio模式,線程的前綴是exec,記住這個名字後面用到的

修改server.xml文件,打開Executor的註釋,我配置的參數是:最大線程數150,核心線程數15,線程池名稱,每個線程的前綴。
爲了區別,我把線程的前綴改爲tzb-nb-,接着把默認的連接器配置註釋掉,打開下面的連接器,讓自定義的連接線程池生效

重啓服務器,查看Jvisualvm,我們看到連接池已經生效了,整好十五個

Tomcat線程模型

我們主要講Tomcat8後的NIO,因爲NIO纔是主流,Tomcat的線程有很多,主線程叫做main是負責啓動和關閉

看監控工具可以看到Tomcat的主要線程

  • Acceptor

使用NIO的原生ServerSocketChannel的accept()方法監聽客戶的連接請求,但是這個ServerSocketChannel是設置阻塞的,所以當沒有連接請求來時,線程會阻塞在accept方法上。ServerSocketChannel設置的TCP連接隊列大小默認是100,TCP連接隊列就是把當前連接信息存放到全連接隊列中,隊列中的連接信息等待ServerSocket.accpt()處理,Acceptor隊列由acceptCount控制,但是Tomcat保持的通道數默認是10000,也就是ServerSocket.accpt()進去的通道Tomcat能保持10000個,由maxConnections控制。然後將ServerSocket.accpt()監聽獲取到的客戶端通道SocketChanel設置爲非阻塞,同時將SocketChanel和註冊事件封裝成一個PollerEvent對象扔進隊列SynchronizedStack中,SynchronizedStack內部是數組,然後隊列裏的PollerEvent等待Poller線程來註冊處理,Poller線程內部就是使用了NIO中的Selector多路複用器,PollerEvent對象主要是裝載了SocketChanel和註冊事件OP_REGISTER在Poller內部其實就是給Selector註冊OP_READ,PollerEvent其實本身也是個線程。Poller線程是隊列的消費者,Acceptor是生產者。這個Acceptor線程默認只有1個,但是可以在Tomcat中配置數量

 public void bind() throws Exception {
        if (!getUseInheritedChannel()) {
//Acceptor的ServerSocketChannel
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = (getAddress()!=null?
new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
            serverSock.socket().bind(addr,getAcceptCount());//getAcceptCount()默認是100
        } else {
                     //............................省略
//設置爲阻塞的連接監聽通道
        serverSock.configureBlocking(true);
                //............................省略
    protected final void startAcceptorThreads() {
//Acceptor的線程個數,這個是可以通過配置文件配置的
        int count = getAcceptorThreadCount();
//創建Acceptor線程
        acceptors = new Acceptor[count];
        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon());
//啓動Acceptor線程
            t.start();
        }
    }
 protected class Acceptor extends AbstractEndpoint.Acceptor {
  //............................省略
        @Override
        public void run() {
  //............................省略
            while (running) {
//計數+1,達到最大值則等待,tomcat設定個最大連接數是10000,達到這個閾值後,就會拒絕連接請求,進行阻塞,這個是用AQS阻塞隊列實現的
                    countUpOrAwaitConnection();

                    SocketChannel socket = null;
                    try {
//ServerSocketChannel開始監聽連接
                        socket = serverSock.accept();
                  //............................省略
                    if (running && !paused) { 
                        if (!setSocketOptions(socket)) {//setSocketOptions設置socket的方法
                            closeSocket(socket);
                        }
              //............................省略
 private void closeSocket(SocketChannel socket) {
//斷開連接計數器將會減1
            countDownConnection();
 protected boolean setSocketOptions(SocketChannel socket) {
        try {
          //將監聽到的SocketChannel 設置爲非阻塞
            socket.configureBlocking(false);
            Socket sock = socket.socket();
        //............................省略
//註冊到ClientPoller的方法
            getPoller0().register(channel);
        //............................省略
    }
   public void register(final NioChannel socket) {
            //............................省略
            if ( r==null)
                  //封裝成PollerEvent,與OP_REGISTER註冊事件綁定
                   r = new PollerEvent(socket,ka,OP_REGISTER);
            else r.reset(socket,ka,OP_REGISTER);
//添加到隊列
            addEvent(r);
        }
    public static class PollerEvent implements Runnable {
   //............................省略
        @Override
        public void run() {
//註冊事件,我們看到註冊事件其實就是OP_READ讀事件
            if (interestOps == OP_REGISTER) {
                try {
//將事件和通道註冊到ClientPoller中的多路複用器中
                    socket.getIOChannel().register(
                            socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
             
            
  • ClientPoller

Tomcat8以後的NIO模式比Tomcat7以前強悍就是因爲這個ClientPoller,ClientPoller是實現了Runnable,ClientPoller線程內部有一個jdk原生的Selector對象,也就是NIO的多路複用器。ClientPoller逐一啓動隊列SynchronizedStack裏的PollerEvent線程,啓動後PollerEvent線程將SocketChanel和對應的事件逐一註冊註冊到ClientPoller的Selector,Selector找出SocketChanel中就緒的SelectionKey,將SelectionKey和事件類型交給工作線程Exec進行處理。整個過程就是典型的NIO實現。Tomcat默認啓動Math.min(2,Runtime.getRuntime().availableProcessors())個ClientPoller線程

//processKey源碼大致邏輯,省略詳細代碼
 protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
                //............................省略
                if ( close ) {
                //............................省略
                } else if ( sk.isValid() && attachment != null ) {
                    if (sk.isReadable() || sk.isWritable() ) {
                              // 寫事件
                          if ( attachment.getSendfileData() != null ) {
                            processSendfile(sk,attachment, false);
                        } else {
                            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) {
                          ............................
                                  // 關閉通道
                            }
                            ..................................
}
  • NioBlockingSelector.BlockPoller

NioBlockingSelector主要處理socketChanel的寫操作,也就是servlet的回寫過程,NioBlockingSelector有兩個對象BlockPoller和Selector叫做sharedSelector。BlockPoller中也有一個Selector對象,這個Selector主要是在blockingSelector.write時如果出現寫失敗就把socketChanel註冊到BlockPoller的Selector不在佔用ClientPoller的時間片,然後就利用CountDownLatch進行阻塞這是Tomcat默認的寫操作過程,在BlockPoller的Selector註冊的socketChanel以後的讀寫操作都是由這個Selector阻塞輪詢。同時NioBlockingSelector是NioSelectorPool中一個成員對象,NioSelectorPool中有兩個成員對象NioBlockingSelector和一個Selector叫做SHARED_SELECTOR,SHARED_SELECTOR主要在非默認的寫過程中處理由ClientPoller輪詢的socketChanel出現寫失敗時,爲了節省ClientPoller寶貴的時間片socketChanel寫事件會註冊到SHARED_SELECTOR上,這個SHARED_SELECTOR是叫做輔Selector,值得一提的是NioSelectorPool的SHARED_SELECTOR、NioBlockingSelector的sharedSelector、BlockPoller的Selector都是同一對象,這個輔Selector輪詢出現寫事件失敗的socketChanel,由輔Selector負責這些socketChanel以後的讀寫事件,這樣減少線程間的切換,同時還可減輕主Selector的負擔。

public class NioSelectorPool {
//SHARED 默認是true
    protected static final boolean SHARED =
        Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.net.NioSelectorShared", "true"));
    protected NioBlockingSelector blockingSelector;
    protected volatile Selector SHARED_SELECTOR;
    //........................省略
 public int write(ByteBuffer buf, NioChannel socket, Selector selector,
                     long writeTimeout, boolean block) throws IOException {
        if ( SHARED && block ) {//block 和SHARED 是默認true,所以寫事件默認走這裏
            //阻塞寫操作
            return blockingSelector.write(buf,socket,writeTimeout);
        }
    //........................省略
                if ( keycount > 0 ) { //only write if we were registered for a write
//寫操做,cnt -1爲失敗
                    cnt = socket.write(buf); //write the data
                    if (cnt > 0) {                 
                        continue; 
                    }
//非阻塞寫
                    if (cnt==0 && (!block)) break; //don't block
                }
                if ( selector != null ) {
      //非阻塞寫失敗後重新註冊到SHARED_SELECTOR,不佔用ClientPoller的時間片
                    if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_WRITE);
                    else key.interestOps(SelectionKey.OP_WRITE);
                    } else if (writeTimeout<0) {
                //SHARED_SELECTOR輪詢
                        keycount = selector.select();
                    } else {
                        keycount = selector.select(writeTimeout);
                    }
                }
//.....................................................................省略

ublic class NioBlockingSelector {

    protected Selector sharedSelector;

    protected BlockPoller poller;
//阻塞寫
  public int write(ByteBuffer buf, NioChannel socket, long writeTimeout)
            throws IOException {
        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
        try {
            while ( (!timedout) && buf.hasRemaining()) {
                if (keycount > 0) { //only write if we were registered for a write
                    int cnt = socket.write(buf); //write the data
                    written += cnt;
//寫操做,cnt -1爲失敗
                    if (cnt > 0) {
                        time = System.currentTimeMillis(); //reset our timeout timer
                        continue; //we successfully wrote, try again without a selector
                    }
                }
                try {
                    if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
//失敗則重新註冊寫事件到BlockPoller 中
                    poller.add(att,SelectionKey.OP_WRITE,reference);
//利用CountDownLatch進行阻塞
                    if (writeTimeout < 0) {
                        att.awaitWriteLatch(Long.MAX_VALUE,TimeUnit.MILLISECONDS);
                    } else {
                        att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
                    }
            
    }
  • Exec

Exec是一個線程池很多博客叫它爲Work,默認的線程名稱是和server.xml中的一致,核心線程默認是10,最大線程數是200,可以通過我們上面的方法配置server.xml文件設置線程的數量,它主要是獲取ClientPoller輪詢出來的socketChanel後,做相應的邏輯處理:先進行三次握手,然後調用Http11Processor的service方法解析HTTP協議,將http協議解析成鍵值對放入request,最終調用CoyoteAdapter的service方法將request和response傳入Tomcat的各種容器中執行servlet的業務邏輯。

//工作線程執行run處理socketChanel
 public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            //將Socket封裝到這個對象,然後扔給工作線程執行
            SocketProcessorBase<S> sc = processorCache.pop();
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            //工作線程,我們在配置文件中配置的Executor,不配默認是10個。最大數200
            Executor executor = getExecutor();
            //工作線程執行工作
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }
//工作線程執行的東西,主要是三次握手後開始解析HTTP,然後關閉socket,省略部分代碼
  protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
        public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
            super(socketWrapper, event);
        }
        @Override
        protected void doRun() {
if (handshake == 0) {//三次握手已完成
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        state = getHandler().process(socketWrapper, event);
                    }
                    if (state == SocketState.CLOSED) {
                        close(socket, key);
                    }
..........................
//邏輯處理主要是調用了這給ConnectionHandler處理socket的數據包
  protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
//省略  ...............................................            
//處理socket數據包,將HTTP其解析成request 對象,傳給Tomcat容器
        @Override
        public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
//省略  ...............................................            
        do {
//省略  ...............................................            
            state = processor.process(wrapper, status);//真正繼續數據包的方法,這個processor是AbstractProcessorLight 類型的,但是process方法主要是調用子類Http11Processor類實現的service進行處理
//省略  ...............................................            
                    }
      }
}

點進process方法,裏面調用了service

public abstract class AbstractProcessorLight implements Processor {
    @Override
    public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
//省略  ...............................................        
            throws IOException {
            } else if (status == SocketEvent.OPEN_READ) {
//這裏是調用Http11Processor 的service方法
                state = service(socketWrapper);
       }
//省略  ...............................................        

Http11Processor 的service方法進行系列的解析各種

public class Http11Processor extends AbstractProcessor {
//省略  ............................................... 
   @Override
    public SocketState service(SocketWrapperBase<?> socketWrapper)
        throws IOException {
//省略  ............................................... 
//經過一系列的解析和設置後,將http的各種信息都存放到request中調用CoyoteAdapter的service方法將request, response傳遞到Tomcat容器
getAdapter().service(request, response);
//省略  ............................................... 
        }
}

NioEndpoint組件

NioEndpoint是Tomcat的NIO關鍵的類,要理解tomcat的nio最主要就是對NioEndpoint的理解

這個我是摘抄別人的,出處https://www.jianshu.com/p/76ff17bc6dea
,有空得自己去看看《Tomcat的內核設計剖析》

NioEndpoint它一共包含LimitLatch、Acceptor、Poller、SocketProcessor、Excutor5個部分。LimitLatch是連接控制器,它負責維護連接數的計算,nio模式下默認是10000,達到這個閾值後,就會拒絕連接請求。Acceptor負責接收連接,默認是1個線程來執行,將請求的事件註冊到事件列表。有Poller來負責輪詢,Poller線程數量是cpu的核數Math.min(2,Runtime.getRuntime().availableProcessors())。由Poller將就緒的事件生成SocketProcessor同時交給Excutor去執行。Excutor線程池的大小就是我們在Connector節點配置的maxThreads的值。在Excutor的線程中,會完成從socket中讀取http request,解析成HttpServletRequest對象,分派到相應的servlet並完成邏輯,然後將response通過socket發回client。在從socket中讀數據和往socket中寫數據的過程,並沒有像典型的非阻塞的NIO的那樣,註冊OP_READ或OP_WRITE事件到主Selector,而是直接通過socket完成讀寫,這時是阻塞完成的,但是在timeout控制上,使用了NIO的Selector機制,但是這個Selector並不是Poller線程維護的主Selector,而是BlockPoller線程中維護的Selector,稱之爲輔Selector

Tomcat文件傳輸

sendfile零拷貝:

在普通的輸入輸出流進行讀寫時,實際上是進行了多次上下文切換,應用讀取數據時,先在內核態將數據從磁盤讀取到內核緩存,再切換到用戶態將數據從內核緩存讀取到用戶緩存,在用戶態對數據進行加工,然後再從用戶態切換回內核態,將用戶緩存數據拷貝到內核緩存中,然後讀到socket中再到網卡

sendfile實質是linux系統中一項優化技術,用以發送文件和網絡通信時,減少用戶態空間與磁盤倒換數據,而直接在內核級做數據拷貝,在Tomcat中是可以使用sendfile對一些靜態數據如:圖片、文件等進行傳輸,這個sendfile是在Tomcat是默認打開的,可以有效的提高Tomcat的傳輸性能

compression文件壓縮:

compression可以對傳輸文件進行壓縮有效解決文件傳輸的帶寬問題,在Tomcat不支持圖片壓縮,因爲要進行上層的用戶態數據加工所以與sendfile互斥,Tomcat默認關閉,開啓的話Tomcat會調用Filter利用jdk解壓縮流進行解壓縮

//源碼中compression支持的壓縮格式中沒有圖片
  private String compressibleMimeType = "text/html,text/xml,text/plain,text/css," +
            "text/javascript,application/javascript,application/json,application/xml";
Accept-Encoding 和Content-Encoding

Accept-Encoding 和Content-Encoding是HTTP中用來對採用哪種編碼格式傳輸正文進行協定的一對頭部字段。瀏覽器發送請求時,會在Request-heads攜帶Accept-Encoding會說明自己支持的編碼列表,服務端在接收到請求後,從中挑選出一種用來對響應信息進行編碼,並通過Response-heads攜帶Content-Encoding來說明服務端選擇的編碼信息,瀏覽器在拿到響應正文後,依據Content-Encoding進行解壓。絕大部分是gzip格式

Tomcat重要配置參數

server.xml

自定義線程池我們上面講過了,現在有了一定的基礎更好介紹了

Executor標籤是專門配置Exec線程池的,專門用於處理多路複用器傳遞進來的socketChanel,我們上面介紹過了
<Executor name="tomcatThreadPool"   
         namePrefix="tzb-nb-"   
         maxThreads="1000"   
         minSpareThreads="30"  
         maxIdleTime="60000"  
         maxQueueSize="1000000"  
         className="org.apache.catalina.core.StandardThreadExecutor"/>
        name:線程池名稱,用於 Connector中指定。

        namePrefix:所創建的每個線程的名稱前綴。

        maxThreads:池中最大線程數。

        minSpareThreads:核心池線程數。

        maxIdleTime:線程空閒時間,超過該時間後,非核心線程會被銷燬,默認值爲6000(1分鐘),單位毫秒。

        maxQueueSize:在被執行前最大線程排隊數目,任務數超出會執行拒絕策略,默認爲Int的最大值
               
        className:線程池實現類,未指定情況下,默認實現類爲org.apache.catalina.core.StandardThreadExecutor。
        如果想使用自定義線程池首先需要實現 org.apache.catalina.Executor接口。
Connector用於配置Tomcat運行模式、Acceptor、CilentPoller、sendfile、是否開啓文件壓縮
<Connector port="8080"   
          protocol="HTTP/1.1"   
          maxThreads="1000"   
          minSpareThreads="100"  
          acceptorThreadCount="2"
          acceptCount="1000"  
          maxConnections="1000"  
          connectionTimeout="20000"   
          maxHttpHeaderSize="8192"  
          compression="on"  
          compressionMinSize="2048"  
          redirectPort="8443"  
          URIEncoding="UTF-8" />

參數很多參考這裏吧:https://blog.csdn.net/lijunwyf/article/details/84244209
我只列出幾個有意思的

port:代表Tomcat監聽端口,也就是網站的訪問端口,默認爲8080,可以根據需要改成其他。

protocol:運行模式,可選類型有四種,分別爲BIO,NIO,AIO和APR。
protocol="org.apache.coyote.http11.Http11NioProtocol"  //NIO  
protocol="org.apache.coyote.http11.Http11Nio2Protocol" //AIO  
protocol="org.apache.coyote.http11.Http11AprProtocol"  //ARP

acceptCount:就是Acceptor線程工作時通道的長度也就是前面說的SynchronizedStack,當請求PollerEvent超出隊列時請求就會被拒絕,默認是100。
一般是設置的跟 maxThreads一樣或一半,此值設置的過大會導致排隊的請求超時而未被處理。所以這個值應該是主要根據應用的訪問峯值與平均值來權衡配置。

acceptorThreadCount:Acceptor線程的數量,默認是1,如果在多核CPU架構下,此值可以設置爲2,官方不建議設定超過2個的值。

maxConnections:tomcat最大連接數也就是上面說的LimitLatch。NIO默認是10000,超出則Acceptor線程就被阻塞了,即不再隊列中獲取已經建立的連接。
但是它並不阻止新的連接的建立,新的連接的建立過程不是Acceptor控制的,Acceptor僅僅是從隊列中獲取新建立的連接。
所以當連接數已經超過maxConnections後,仍然是可以建立新的連接的,存放在上述acceptCount大小的隊列中,這個隊列裏面的連接沒有被Acceptor獲取,
就處於連接建立了但是不被處理的狀態。當連接數低於maxConnections之後,Acceptor線程就不再阻塞

connectionTimeout:當請求已經被接受,但未被處理,也就是等待中的超時時間。單位爲毫秒,默認值爲60000。

executor:就是把executor標籤的內容引過來,不要它的話可以把executor的內容複製到這裏

URIEncoding:URL編碼字符集。

pollerThreadCount:ClientPoller的線程數,默認爲2。官方不建議設置大於2的值,因爲鎖的競爭會導致性能下降,事實上一個線程也足夠快速。

useSendfile:是否開啓sendfile特性,默認爲true。對於web應用而言,通常project中還會包含一定數量的靜態資源,比如圖片、CSS、js、html等,sendfile在一定程度上可以提高性能。

compression:是否開啓壓縮,這個屬性與useSendfile互斥,useSendfile開啓了,這個認關閉,compression是默認關閉的

compressionMinSize:如果compression="on",則啓用此項。被壓縮前數據的最小值,也就是超過這個值後才被壓縮。如果沒有指定,這個屬性默認爲“2048”(2K),單位爲byte。

實戰:

我們看到Acceptor線程線程是3個、Client線程是5個,exec線程是15個和我上圖的配置一致

catalina.sh

使用JAVA_OPTS配置JVM參數

//例如:
JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms2048m -Xmx2048m 
-XX:NewSize=512m -XX:MaxNewSize=1024m 
-XX:PermSize=256m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"

下面是參考這裏的https://blog.csdn.net/fly910905/article/details/78518599

  • 一般說來,您應該使用物理內存的 80% 作爲堆大小。說明:以上兩個參數關係到tomcat承受的訪問性能,但也要根據服務器實際內存情況設定。有人建議Xms和Xmx的值取成一樣比較好,說是可以加快內存回收速度。

  • Xms和Xmx這兩個值的大小一般根據需要進行配置。初始化堆的大小執行了虛擬機在啓動時向系統申請的內存的大小。一般而言,這個參數不重要。但是有的應用程序在大負載的情況下會急劇地佔用更多的內存,此時這個參數就是顯得很重要,假如虛擬機啓動時配置使用的內存比較小而在這種情況下有許多對象進行初始化,虛擬機就必須重複地增加內存來滿足使用。由於這種原因,我們一般把-Xms和-Xmx設爲相同大,而堆的最大值受限於系統使用的物理內存。一般使用數據量較大的應用程序會使用持久對象,內存使用有可能迅速地增長。當應用程序需要的內存超出堆的最大值時虛擬機就會提示內存溢出,並且導致應用服務崩潰。因此一般建議堆的最大值配置爲可用內存的最大值的80%。

  • 另外需要考慮的是Java提供的垃圾回收機制。虛擬機的堆大小決定了虛擬機花費在收集垃圾上的時間和頻度。收集垃圾能夠接受的速度和應用有關,應該通過分析實際的垃圾收集的時間和頻率來調整。假如堆的大小很大,那麼完全垃圾收集就會很慢,但是頻度會降低。假如您把堆的大小和內存的需要一致,完全收集就很快,但是會更加頻繁。調整堆大小的的目的是最小化垃圾收集的時間,以在特定的時間內最大化處理客戶的請求。在基準測試的時候,爲確保最好的性能,要把堆的大小設大,確保垃圾收集不在整個基準測試的過程中出現。

  • 假如系統花費很多的時間收集垃圾,請減小堆大小。一次完全的垃圾收集應該不超過 3-5 秒。假如垃圾收集成爲瓶頸,那麼需要指定代的大小,檢查垃圾收集的周詳輸出,研究 垃圾收集參數對性能的影響。一般說來,您應該使用物理內存的 80% 作爲堆大小。當增加處理器時,記得增加內存,因爲分配能夠並行進行,而垃圾收集不是並行的。

Tomcat壓力測試

使用jmeter可以進行對程序進行壓力測試,根據用戶的併發,調整Tomcat的線程數量

我使用一千個線程,一次併發一千,循環十次

主要看吞吐量,根據吞吐量慢慢調試Tomcat的線程設置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章