springboot內(nèi)置tomcat調(diào)優(yōu)并發(fā)線程數(shù)解析
前言
本文解析springboot內(nèi)置tomcat調(diào)優(yōu)并發(fā)線程數(shù)的一些參數(shù),并結(jié)合源碼進(jìn)行分析
參數(shù)
線程池核心線程數(shù)
server.tomcat.min-spare-threads
:該參數(shù)為tomcat處理業(yè)務(wù)的核心線程數(shù)大小,默認(rèn)值為10
線程池最大線程數(shù)
server.tomcat.max-threads
:該參數(shù)為tomcat處理業(yè)務(wù)的最大線程數(shù)大小,默認(rèn)值為200,當(dāng)對(duì)并發(fā)量有一點(diǎn)值時(shí)可以調(diào)大該參數(shù)
請(qǐng)求最大連接數(shù)
server.tomcat.max-connections
:該參數(shù)為請(qǐng)求的最大連接數(shù),默認(rèn)值為10000,注意這個(gè)參數(shù)并不是設(shè)置在線程池上的,而是在tomcat的Acceptor類(專門處理連接的線程類)來(lái)控制的,結(jié)合源碼我們可以看到
protected void countUpOrAwaitConnection() throws InterruptedException { if (maxConnections==-1) return; LimitLatch latch = connectionLimitLatch; if (latch!=null) latch.countUpOrAwait(); }
可以看到當(dāng)最大連接數(shù)滿了之后會(huì)進(jìn)行等待
accept-count
server.tomcat.accept-count:這個(gè)參數(shù)實(shí)際上和tomcat沒有太大關(guān)系,默認(rèn)值為100
我們先看下文檔的定義
/** * Allows the server developer to specify the acceptCount (backlog) that * should be used for server sockets. By default, this value * is 100. */ private int acceptCount = 100;
這個(gè)參數(shù)是服務(wù)端創(chuàng)建ServerSocket時(shí)操作系統(tǒng)控制同時(shí)連接的最大數(shù)量的,服務(wù)端接收連接是通過(guò)accept()來(lái)的,accept()是非??斓?,所以accept-count的不需要太大,正常保持默認(rèn)值100即可了,acceptCount這個(gè)參數(shù)和線程池?zé)o關(guān),會(huì)被映射為backlog參數(shù),是socket的參數(shù),在源碼的使用是在NioEndpoint類的initServerSocket方法,在tomcat中的名字是backlog在springboot內(nèi)置tomcat中名字沒有使用backlog而是使用acceptCount
serverSock.socket().bind(addr,getAcceptCount())
protected void initServerSocket() throws Exception { if (!getUseInheritedChannel()) { serverSock = ServerSocketChannel.open(); socketProperties.setProperties(serverSock.socket()); InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort())); // 核心代碼 serverSock.socket().bind(addr,getAcceptCount()); } else { // Retrieve the channel provided by the OS Channel ic = System.inheritedChannel(); if (ic instanceof ServerSocketChannel) { serverSock = (ServerSocketChannel) ic; } if (serverSock == null) { throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited")); } } serverSock.configureBlocking(true); //mimic APR behavior }
tomcat線程池處理機(jī)制
tomcat最終使用線程池來(lái)處理業(yè)務(wù)邏輯,java默認(rèn)的線程池的規(guī)則:
核心線程數(shù)滿了則優(yōu)先放入隊(duì)列,當(dāng)隊(duì)列滿了之后才會(huì)創(chuàng)建非核心線程來(lái)處理,那么tomcat是這樣做的嗎?
首先如果tomcat這樣做,那么當(dāng)達(dá)到核心線程后后續(xù)任務(wù)就需要等待了,這顯然是不合理的,我們通過(guò)源碼來(lái)看下tomcat是如何處理的
在AbstractEndpoint的createExecutor創(chuàng)建了處理業(yè)務(wù)數(shù)據(jù)的線程池
public void createExecutor() { internalExecutor = true; TaskQueue taskqueue = new TaskQueue(); TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority()); executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf); taskqueue.setParent( (ThreadPoolExecutor) executor); }
主要是使用了TaskQueue隊(duì)列,ThreadPoolExecutor并不是jdk的,而是tomcat重寫的。
我們從線程池的處理方法execute看起
public void execute(Runnable command) { execute(command,0,TimeUnit.MILLISECONDS); }
public void execute(Runnable command, long timeout, TimeUnit unit) { submittedCount.incrementAndGet(); try { // 核心代碼 super.execute(command); } catch (RejectedExecutionException rx) { if (super.getQueue() instanceof TaskQueue) { final TaskQueue queue = (TaskQueue)super.getQueue(); try { if (!queue.force(command, timeout, unit)) { submittedCount.decrementAndGet(); throw new RejectedExecutionException("Queue capacity is full."); } } catch (InterruptedException x) { submittedCount.decrementAndGet(); throw new RejectedExecutionException(x); } } else { submittedCount.decrementAndGet(); throw rx; } } }
又調(diào)用會(huì)jdk的execute了
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // 1、工作線程數(shù)小于核心線程數(shù)則添加任務(wù),核心線程會(huì)處理 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } // 2、工作線程不小于核心線程數(shù),則放到workQueue隊(duì)列中 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 3、否則添加任務(wù),addWorker會(huì)進(jìn)行創(chuàng)建線程 else if (!addWorker(command, false)) reject(command); }
從這里可以看到j(luò)dk線程池的機(jī)制,tomcat使用了自己的TaskQueue隊(duì)列,所以我們看代碼2處當(dāng)核心線程用完了會(huì)調(diào)用隊(duì)列的offer方法
我們看TaskQueue的offer
public boolean offer(Runnable o) { //we can't do any checks // parent就是指線程池,沒有線程池則添加到隊(duì)列 if (parent==null) return super.offer(o); //we are maxed out on threads, simply queue the object // 線程數(shù)量已經(jīng)達(dá)到了最大線程數(shù),那么只能添加到隊(duì)列 if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o); //we have idle threads, just add it to the queue // 如果當(dāng)前處理的任務(wù)數(shù)量小于當(dāng)前線程池中線程的數(shù)量,那么任務(wù)放到線程池,即相當(dāng)于馬上會(huì)有空閑線程來(lái)處理 if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o); //if we have less threads than maximum force creation of a new thread // TODO 核心代碼,如果當(dāng)前線程數(shù)量還沒有達(dá)到線程池最大線程池的數(shù)量,那么就直接創(chuàng)建線程,這里返回false if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false; //if we reached here, we need to add it to the queue // 最后的策略,放到隊(duì)列 return super.offer(o); }
可以看到當(dāng)執(zhí)行offer時(shí),不是直接放到隊(duì)列的,當(dāng)線程池總線程數(shù)量還沒達(dá)到線程池最大線程數(shù)時(shí)會(huì)返回false,返回false時(shí)就會(huì)執(zhí)行線程池execute的代碼3處,執(zhí)行addWorker(command, false),也就開始創(chuàng)建新的線程來(lái)處理當(dāng)前任務(wù)了
總結(jié)
tomcat主要通過(guò)使用自己的TaskQueue隊(duì)列來(lái)對(duì)線程池做出了不同的策略,也就是tomcat當(dāng)線程數(shù)大于核心數(shù)時(shí)就會(huì)直接創(chuàng)建新的線程來(lái)處理,而不是放到隊(duì)列
想了解tomcat架構(gòu)和處理流程的可以看這篇文章 www.dbjr.com.cn/article/233207.htm
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot內(nèi)置tomcat參數(shù)調(diào)優(yōu)的實(shí)現(xiàn)
- Springboot內(nèi)置Tomcat配置參數(shù)調(diào)優(yōu)方式
- springboot內(nèi)置的tomcat支持最大的并發(fā)量問(wèn)題
- SpringBoot內(nèi)置tomcat調(diào)優(yōu)測(cè)試優(yōu)化
- SpringBoot內(nèi)置tomcat啟動(dòng)原理詳解
- Springboot內(nèi)置tomcat配置虛擬路徑過(guò)程解析
- springboot查看和修改內(nèi)置tomcat版本的方法步驟
相關(guān)文章
Scala中Array和List的區(qū)別說(shuō)明
這篇文章主要介紹了Scala中Array和List的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10詳解一個(gè)簡(jiǎn)單的Servlet容器的設(shè)計(jì)與實(shí)現(xiàn)
Servlet算是Java Web開發(fā)請(qǐng)求鏈路調(diào)用棧中底層的一個(gè)技術(shù),而了解一個(gè)Servlet容器的實(shí)現(xiàn)有助于更好的理解JavaWeb開發(fā),所以下面就來(lái)看看如何設(shè)計(jì)與實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Servlet容器吧2023-07-07java中volatile不能保證線程安全(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇java中volatile不能保證線程安全(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09基于ElasticSearch Analyzer的使用規(guī)則詳解
這篇文章主要介紹了基于ElasticSearch Analyzer的使用規(guī)則,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07JAVA WSIMPORT生成WEBSERVICE客戶端401認(rèn)證過(guò)程圖解
這篇文章主要介紹了JAVA WSIMPORT生成WEBSERVICE客戶端401認(rèn)證過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10