欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解SpringBoot?最大連接數(shù)及最大并發(fā)數(shù)

 更新時間:2023年08月31日 15:11:52   作者:lakernote  
SpringBoot能支持的最大并發(fā)量主要看其對Tomcat的設置,可以在配置文件中對其進行更改,本文就來介紹一下SpringBoot?最大連接數(shù)及最大并發(fā)數(shù),感興趣的可以了解一下

每個Spring Boot版本和內置容器不同,結果也不同,這里以Spring Boot 2.7.10版本 + 內置Tomcat容器舉例。

概序

SpringBoot2.7.10版本中內置Tomcat版本是9.0.73,SpringBoot內置Tomcat的默認設置如下:

  • Tomcat的連接等待隊列長度,默認是100
  • Tomcat的最大連接數(shù),默認是8192
  • Tomcat的最小工作線程數(shù),默認是10
  • Tomcat的最大線程數(shù),默認是200
  • Tomcat的連接超時時間,默認是20s

相關配置及默認值如下

server:
  tomcat:
    # 當所有可能的請求處理線程都在使用中時,傳入連接請求的最大隊列長度
    accept-count: 100
    # 服務器在任何給定時間接受和處理的最大連接數(shù)。一旦達到限制,操作系統(tǒng)仍然可以接受基于“acceptCount”屬性的連接。
    max-connections: 8192
    threads:
      # 工作線程的最小數(shù)量,初始化時創(chuàng)建的線程數(shù)
      min-spare: 10
      # 工作線程的最大數(shù)量 io密集型建議10倍的cpu數(shù),cpu密集型建議cpu數(shù)+1,絕大部分應用都是io密集型
      max: 200
    # 連接器在接受連接后等待顯示請求 URI 行的時間。
    connection-timeout: 20000
    # 在關閉連接之前等待另一個 HTTP 請求的時間。如果未設置,則使用 connectionTimeout。設置為 -1 時不會超時。
    keep-alive-timeout: 20000
    # 在連接關閉之前可以進行流水線處理的最大HTTP請求數(shù)量。當設置為0或1時,禁用keep-alive和流水線處理。當設置為-1時,允許無限數(shù)量的流水線處理或keep-alive請求。 
    max-keep-alive-requests: 100

架構圖

當連接數(shù)大于maxConnections+acceptCount + 1時,新來的請求不會收到服務器拒絕連接響應,而是不會和新的請求進行3次握手建立連接,一段時間后(客戶端的超時時間或者Tomcat的20s后)會出現(xiàn)請求連接超時。

TCP的3次握手4次揮手

時序圖

核心參數(shù)

AcceptCount

全連接隊列容量,等同于 backlog 參數(shù),與 Linux 中的系統(tǒng)參數(shù) somaxconn 取較小值, Windows 中沒有系統(tǒng)參數(shù)。

NioEndpoint.java

serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
// 這里
serverSock.socket().bind(addr,getAcceptCount());

MaxConnections

Acccptor.java

// 線程的run方法。
public void run() {    
		while (!stopCalled) { 
	   		// 如果我們已達到最大連接數(shù),等待
        	connectionLimitLatch.countUpOrAwait();
            // 接受來自服務器套接字的下一個傳入連接
            socket = endpoint.serverSocketAccept()
            // socket.close 釋放的時候 調用 connectionLimitLatch.countDown();    

MinSpareThread/MaxThread

AbstractEndpoint.java

// tomcat 啟動時
public void createExecutor() {
        internalExecutor = true;
    	// 容量為Integer.MAX_VALUE
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    	// Tomcat擴展的線程池
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
}

重點重點重點

Tomcat擴展了線程池增強了功能。

  • JDK線程池流程:minThreads --> queue --> maxThreads --> Exception
  • Tomcat增強后: minThreads --> maxThreads --> queue --> Exception

MaxKeepAliveRequests

長連接,在發(fā)送了 maxKeepAliveRequests個請求后就會被服務器端主動斷開連接。

在連接關閉之前可以進行流水線處理的最大HTTP請求數(shù)量。當設置為0或1時,禁用keep-alive和流水線處理。當設置為-1時,允許無限數(shù)量的流水線處理或keep-alive請求。

較大的 MaxKeepAliveRequests 值可能會導致服務器上的連接資源被長時間占用。根據(jù)您的具體需求,您可以根據(jù)服務器的負載和資源配置來調整 MaxKeepAliveRequests 的值,以平衡并發(fā)連接和服務器資源的利用率。

NioEndpoint.setSocketOptions	
	socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
Http11Processor.service(SocketWrapperBase<?> socketWrapper)
  keepAlive = true;
  while(!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
                sendfileState == SendfileState.DONE && !protocol.isPaused()) {
    // 默認100  
	int maxKeepAliveRequests = protocol.getMaxKeepAliveRequests();
	if (maxKeepAliveRequests == 1) {
    	keepAlive = false;
	} else if (maxKeepAliveRequests > 0 &&
            //    
        	socketWrapper.decrementKeepAlive() <= 0) {
    	keepAlive = false;
	}

ConnectionTimeout

連接的生存周期,當已經建立的連接,在 connectionTimeout 時間內,如果沒有請求到來,服務端程序將會主動關閉該連接。

  • 在Tomcat 9中,ConnectionTimeout的默認值是20000毫秒,也就是20秒。如果該時間過長,服務器將要等待很長時間才會收到客戶端的請求結果,從而導致服務效率低下。
  • 如果該時間過短,則可能會出現(xiàn)客戶端在請求過程中網絡慢等問題,而被服務器取消連接的情況。
  • 由于某個交換機或者路由器出現(xiàn)了問題,導致某些post大文件的請求堆積在交換機或者路由器上,tomcat的工作線程一直拿不到完整的文件數(shù)據(jù)。

NioEndpoint.Poller#run()

 // Check for read timeout
 if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
     long delta = now - socketWrapper.getLastRead();
     long timeout = socketWrapper.getReadTimeout();
     if (timeout > 0 && delta > timeout) {
         readTimeout = true;
     }
 }
 // Check for write timeout
 if (!readTimeout && (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
     long delta = now - socketWrapper.getLastWrite();
     long timeout = socketWrapper.getWriteTimeout();
     if (timeout > 0 && delta > timeout) {
         writeTimeout = true;
     }
 }

KeepAliveTimeout

等待另一個 HTTP 請求的時間,然后關閉連接。當未設置時,將使用 connectionTimeout。當設置為 -1 時,將沒有超時。

Http11InputBuffer.parseRequestLine

// Read new bytes if needed
if (byteBuffer.position() >= byteBuffer.limit()) {
    if (keptAlive) {
        // 還沒有讀取任何請求數(shù)據(jù),所以使用保持活動超時
        wrapper.setReadTimeout(keepAliveTimeout);
    }
    if (!fill(false)) {
        // A read is pending, so no longer in initial state
        parsingRequestLinePhase = 1;
        return false;
    }
    // 	至少已收到請求的一個字節(jié) 切換到套接字超時。
     wrapper.setReadTimeout(connectionTimeout);
}

內部線程

Acceptor

Acceptor : 接收器,作用是接受scoket網絡請求,并調用 setSocketOptions() 封裝成為 NioSocketWrapper ,并注冊到Poller的events中。注意查看run方法 org.apache.tomcat.util.net.Acceptor#run

public void run() {
       while (!stopCalled) {
           // 等待下一個請求進來
           socket = endpoint.serverSocketAccept();
            // 注冊socket到Poller,生成PollerEvent事件
           endpoint.setSocketOptions(socket);
           			// 向輪詢器注冊新創(chuàng)建的套接字
                    - poller.register(socketWrapper);
                        - (SynchronizedQueue(128))events.add(new PollerEvent(socketWrapper))    

Poller

Poller :輪詢器,輪詢是否有事件達到,有請求事件到達后,以NIO的處理方式,查詢Selector取出所有請求,遍歷每個請求的需求,分配給Executor線程池執(zhí)行。查看 org.apache.tomcat.util.net.NioEndpoint.Poller#run()

public void run() {
       while (true) {
               //查詢selector取出所有請求事件
               Iterator<SelectionKey> iterator =
                   keyCount > 0 ? selector.selectedKeys().iterator() : null;
               // 遍歷就緒鍵的集合并調度任何活動事件。
               while (iterator != null && iterator.hasNext()) {
                   SelectionKey sk = iterator.next();
                   iterator.remove();
                   NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                   // 分配給Executor線程池執(zhí)行處理請求key
                   if (socketWrapper != null) {
                       processKey(sk, socketWrapper);
                       - processSocket(socketWrapper, SocketEvent.OPEN_READ/SocketEvent.OPEN_WRITE)
                           - executor.execute((Runnable)new SocketProcessor(socketWrapper,SocketEvent))
                   }
               }

TomcatThreadPoolExecutor

真正執(zhí)行連接讀寫操作的線程池,在JDK線程池的基礎上進行了擴展優(yōu)化。

AbstractEndpoint.java

public void createExecutor() {
        internalExecutor = true;
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    	// tomcat自定義線程池
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }

TomcatThreadPoolExecutor.java

// 與 java.util.concurrent.ThreadPoolExecutor 相同,但實現(xiàn)了更高效的getSubmittedCount()方法,用于正確處理工作隊列。
// 如果未指定 RejectedExecutionHandler,將配置一個默認的,并且該處理程序將始終拋出 RejectedExecutionException
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
	// 已提交但尚未完成的任務數(shù)。這包括隊列中的任務和已交給工作線程但后者尚未開始執(zhí)行任務的任務。
    // 這個數(shù)字總是大于或等于getActiveCount() 。
    private final AtomicInteger submittedCount = new AtomicInteger(0);
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        if (!(t instanceof StopPooledThreadException)) {
            submittedCount.decrementAndGet();
        }
    @Override
    public void execute(Runnable command){
        // 提交任務的數(shù)量+1
        submittedCount.incrementAndGet();
        try {
            //  線程池內部方法,真正執(zhí)行的方法。就是JDK線程池原生的方法。
            super.execute(command);
        } catch (RejectedExecutionException rx) {
            // 再次把被拒絕的任務放入到隊列中。
            if (super.getQueue() instanceof TaskQueue) {
                final TaskQueue queue = (TaskQueue)super.getQueue();
                try {
                      //強制的將任務放入到阻塞隊列中
                    if (!queue.force(command, timeout, unit)) {
                        //放入失敗,則繼續(xù)拋出異常
                        submittedCount.decrementAndGet();
                        throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                    }
                } catch (InterruptedException x) {
                     //被中斷也拋出異常
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(x);
                }
            } else {
                 //不是這種隊列,那么當任務滿了之后,直接拋出去。
                submittedCount.decrementAndGet();
                throw rx;
            }
        }
    }
/**
 * 實現(xiàn)Tomcat特有邏輯的自定義隊列
 */
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
    private static final long serialVersionUID = 1L;
    private transient volatile ThreadPoolExecutor parent = null;
    private static final int DEFAULT_FORCED_REMAINING_CAPACITY = -1;
    /**
     * 強制遺留的容量
     */
    private int forcedRemainingCapacity = -1;
    /**
     * 隊列的構建方法
     */
    public TaskQueue() {
    }
    public TaskQueue(int capacity) {
        super(capacity);
    }
    public TaskQueue(Collection<? extends Runnable> c) {
        super(c);
    }
    /**
     * 設置核心變量
     */
    public void setParent(ThreadPoolExecutor parent) {
        this.parent = parent;
    }
    /**
     * put:向阻塞隊列填充元素,當阻塞隊列滿了之后,put時會被阻塞。
     * offer:向阻塞隊列填充元素,當阻塞隊列滿了之后,offer會返回false。
     *
     * @param o 當任務被拒絕后,繼續(xù)強制的放入到線程池中
     * @return 向阻塞隊列塞任務,當阻塞隊列滿了之后,offer會返回false。
     */
    public boolean force(Runnable o) {
        if (parent == null || parent.isShutdown()) {
            throw new RejectedExecutionException("taskQueue.notRunning");
        }
        return super.offer(o);
    }
    /**
     * 帶有阻塞時間的塞任務
     */
    @Deprecated
    public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
        if (parent == null || parent.isShutdown()) {
            throw new RejectedExecutionException("taskQueue.notRunning");
        }
        return super.offer(o, timeout, unit); //forces the item onto the queue, to be used if the task is rejected
    }
    /**
     * 當線程真正不夠用時,優(yōu)先是開啟線程(直至最大線程),其次才是向隊列填充任務。
     *
     * @param runnable 任務
     * @return false 表示向隊列中添加任務失敗,
     */
    @Override
    public boolean offer(Runnable runnable) {
        if (parent == null) {
            return super.offer(runnable);
        }
        //若是達到最大線程數(shù),進隊列。
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) {
            return super.offer(runnable);
        }
        //當前活躍線程為10個,但是只有8個任務在執(zhí)行,于是,直接進隊列。
        if (parent.getSubmittedCount() < (parent.getPoolSize())) {
            return super.offer(runnable);
        }
        //當前線程數(shù)小于最大線程數(shù),那么直接返回false,去創(chuàng)建最大線程
        if (parent.getPoolSize() < parent.getMaximumPoolSize()) {
            return false;
        }
        //否則的話,將任務放入到隊列中
        return super.offer(runnable);
    }
    /**
     * 獲取任務
     */
    @Override
    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
        Runnable runnable = super.poll(timeout, unit);
        //取任務超時,會停止當前線程,來避免內存泄露
        if (runnable == null && parent != null) {
            parent.stopCurrentThreadIfNeeded();
        }
        return runnable;
    }
    /**
     * 阻塞式的獲取任務,可能返回null。
     */
    @Override
    public Runnable take() throws InterruptedException {
        //當前線程應當被終止的情況下:
        if (parent != null && parent.currentThreadShouldBeStopped()) {
            long keepAliveTime = parent.getKeepAliveTime(TimeUnit.MILLISECONDS);
            return poll(keepAliveTime, TimeUnit.MILLISECONDS);
        }
        return super.take();
    }
    /**
     * 返回隊列的剩余容量
     */
    @Override
    public int remainingCapacity() {
        if (forcedRemainingCapacity > DEFAULT_FORCED_REMAINING_CAPACITY) {
            return forcedRemainingCapacity;
        }
        return super.remainingCapacity();
    }
    /**
     * 強制設置剩余容量
     */
    public void setForcedRemainingCapacity(int forcedRemainingCapacity) {
        this.forcedRemainingCapacity = forcedRemainingCapacity;
    }
    /**
     * 重置剩余容量
     */
    void resetForcedRemainingCapacity() {
        this.forcedRemainingCapacity = DEFAULT_FORCED_REMAINING_CAPACITY;
    }
}

JDK線程池架構圖

Tomcat線程架構

測試

如下配置舉例

server:
  port: 8080
  tomcat:
    accept-count: 3
    max-connections: 6
    threads:
      min-spare: 2
      max: 3

使用 ss -nlt 查看全連接隊列容量。

ss -nltp
ss -nlt|grep 8080
- Recv-Q表示(acceptCount)全連接隊列目前長度
- Send-Q表示(acceptCount)全連接隊列的容量。

靜默狀態(tài)

6個并發(fā)連接

結果同上

9個并發(fā)連接

10個并發(fā)連接

11個并發(fā)連接

結果同上

使用 ss -nt 查看連接狀態(tài)。

ss -ntp
ss -nt|grep 8080
- Recv-Q表示客戶端有多少個字節(jié)發(fā)送但還沒有被服務端接收
- Send-Q就表示為有多少個字節(jié)未被客戶端接收。

靜默狀態(tài)

6個并發(fā)連接

9個并發(fā)連接

補充個netstat

10個并發(fā)連接

結果同上,隊列中多加了個

11個并發(fā)連接

超出連接后,會有個連接一直停留在SYN_RECV狀態(tài),不會完成3次握手了。

超出連接后客戶端一直就停留在SYN-SENT狀態(tài),服務端不會再發(fā)送SYN+ACK,直到客戶端超時(20s內核控制)斷開。

客戶端請求超時(需要等待一定時間(20s))。

這里如果客戶端設置了超時時間,要和服務端3次握手超時時間對比小的為準。

12個并發(fā)連接

參考

到此這篇關于深入理解SpringBoot 最大連接數(shù)及最大并發(fā)數(shù)的文章就介紹到這了,更多相關SpringBoot 最大連接數(shù)及最大并發(fā)數(shù)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring AOP中定義切點的實現(xiàn)方法示例

    Spring AOP中定義切點的實現(xiàn)方法示例

    這篇文章主要介紹了Spring AOP中定義切點的實現(xiàn)方法,結合實例形式分析了spring面向切面AOP定義切點的具體步驟、實現(xiàn)方法與相關操作技巧,需要的朋友可以參考下
    2020-01-01
  • Druid(新版starter)在SpringBoot下的使用教程

    Druid(新版starter)在SpringBoot下的使用教程

    Druid是Java語言中最好的數(shù)據(jù)庫連接池,Druid能夠提供強大的監(jiān)控和擴展功能,DruidDataSource支持的數(shù)據(jù)庫,這篇文章主要介紹了Druid(新版starter)在SpringBoot下的使用,需要的朋友可以參考下
    2023-05-05
  • java charAt()返回數(shù)值型的使用示例

    java charAt()返回數(shù)值型的使用示例

    本文主要介紹了java charAt()返回數(shù)值型的使用示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-11-11
  • Spring 使用xml配置AOP的過程詳解

    Spring 使用xml配置AOP的過程詳解

    在之前的學習中,都是使用注解的方式進行AOP的配置.其實使用xml配置文件也可以配置AOP,本文給大家分享Spring 使用xml配置AOP的過程,感興趣的朋友一起看看吧
    2023-11-11
  • SpringSecurity OAtu2+JWT實現(xiàn)微服務版本的單點登錄的示例

    SpringSecurity OAtu2+JWT實現(xiàn)微服務版本的單點登錄的示例

    本文主要介紹了SpringSecurity OAtu2+JWT實現(xiàn)微服務版本的單點登錄的示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Java遞歸實現(xiàn)斐波那契數(shù)列

    Java遞歸實現(xiàn)斐波那契數(shù)列

    這篇文章主要為大家詳細介紹了Java遞歸實現(xiàn)斐波那契數(shù)列,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • MyBatis中?@Mapper?和?@MapperScan?的區(qū)別與使用解析

    MyBatis中?@Mapper?和?@MapperScan?的區(qū)別與使用解析

    本文介紹了SpringBoot中MyBatis的兩個常用注解:@Mapper和@MapperScan,@Mapper用于標記單個Mapper接口,而@MapperScan用于批量掃描指定包下的所有Mapper接口,兩者都有各自適用的場景,選擇合適的注解可以提高開發(fā)效率并使代碼更加簡潔,感興趣的朋友一起看看吧
    2025-01-01
  • Spring?BOOT?AOP基礎應用教程

    Spring?BOOT?AOP基礎應用教程

    這篇文章主要介紹了Spring?BOOT?AOP的使用,文章從相關問題展開全文內容詳情,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-07-07
  • Spring bean加載控制實現(xiàn)方法

    Spring bean加載控制實現(xiàn)方法

    很多時候我們需要根據(jù)不同的條件在容器中加載不同的Bean,或者根據(jù)不同的條件來選擇是否在容器中加載某個Bean,這就是Bean的加載控制,一般我們可以通過編程式或注解式兩種不同的方式來完成Bean的加載控制
    2022-12-12
  • 排序算法圖解之Java快速排序的分步刨析

    排序算法圖解之Java快速排序的分步刨析

    快速排序是通過一趟排序將要排序的數(shù)據(jù)分割為獨立的兩個部分,一部分的所有數(shù)據(jù)比另外一部分的所有數(shù)據(jù)要小,然后按照此方法對這兩部分分別進行快速排序,整個過程可以遞歸進行,以此達到整個數(shù)據(jù)變成有序序列。本文通過示例講解了快速排序的實現(xiàn),需要的可以參考一下
    2022-11-11

最新評論