亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot文件上傳接口并發(fā)性能調(diào)優(yōu)

 更新時(shí)間:2024年06月02日 08:46:54   作者:junyuz  
在一個(gè)項(xiàng)目現(xiàn)場(chǎng),文件上傳接口(文件500K)QPS只有30,這個(gè)并發(fā)性能確實(shí)堪憂,此文記錄出坑過(guò)程,文中通過(guò)代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下

前言

在一個(gè)項(xiàng)目現(xiàn)場(chǎng),文件上傳接口(文件500K)QPS只有30,這個(gè)并發(fā)性能確實(shí)堪憂。此文記錄出坑過(guò)程。

問(wèn)題一、InputStream按字節(jié)讀取效率低

// 讀取上傳的文件
Part part = request.getPart("data");
InputStream in = part.getInputStream();

ByteArrayOutputStream str=new ByteArrayOutputStream();
int k;
byte[] file = null;
while((k=in.read())!=-1){
    str.write(k);
}
file = str.toByteArray();
str.close();
in.close();
/**
 * Reads the next byte of data from the input stream. The value byte is
 * returned as an {@code int} in the range {@code 0} to
 * {@code 255}. If no byte is available because the end of the stream
 * has been reached, the value {@code -1} is returned. This method
 * blocks until input data is available, the end of the stream is detected,
 * or an exception is thrown.
 *
 * <p> A subclass must provide an implementation of this method.
 *
 * @return     the next byte of data, or {@code -1} if the end of the
 *             stream is reached.
 * @throws     IOException  if an I/O error occurs.
 */
public abstract int read() throws IOException;

直接調(diào)用接口發(fā)現(xiàn)接口響應(yīng)確實(shí)比較慢,經(jīng)過(guò)排查是上述代碼in.read()按字節(jié)讀取效率特別低。既然定位到問(wèn)題了,換個(gè)方式,每次讀取8K數(shù)據(jù)。

byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
    str.write(buffer, 0, bytesRead);
}
/**
 * Reads some number of bytes from the input stream and stores them into
 * the buffer array <code>b</code>. The number of bytes actually read is
 * returned as an integer.  This method blocks until input data is
 * available, end of file is detected, or an exception is thrown.
 *
 * <p> If the length of <code>b</code> is zero, then no bytes are read and
 * <code>0</code> is returned; otherwise, there is an attempt to read at
 * least one byte. If no byte is available because the stream is at the
 * end of the file, the value <code>-1</code> is returned; otherwise, at
 * least one byte is read and stored into <code>b</code>.
 *
 * <p> The first byte read is stored into element <code>b[0]</code>, the
 * next one into <code>b[1]</code>, and so on. The number of bytes read is,
 * at most, equal to the length of <code>b</code>. Let <i>k</i> be the
 * number of bytes actually read; these bytes will be stored in elements
 * <code>b[0]</code> through <code>b[</code><i>k</i><code>-1]</code>,
 * leaving elements <code>b[</code><i>k</i><code>]</code> through
 * <code>b[b.length-1]</code> unaffected.
 *
 * <p> The <code>read(b)</code> method for class <code>InputStream</code>
 * has the same effect as: <pre><code> read(b, 0, b.length) </code></pre>
 *
 * @param      b   the buffer into which the data is read.
 * @return     the total number of bytes read into the buffer, or
 *             <code>-1</code> if there is no more data because the end of
 *             the stream has been reached.
 * @exception  IOException  If the first byte cannot be read for any reason
 * other than the end of the file, if the input stream has been closed, or
 * if some other I/O error occurs.
 * @exception  NullPointerException  if <code>b</code> is <code>null</code>.
 * @see        java.io.InputStream#read(byte[], int, int)
 */
public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

如果JDK>=9,可以使用readAllBytes方法,更為便捷。內(nèi)部實(shí)現(xiàn)其實(shí)也是按照8K進(jìn)行讀取的。

文件上傳接口通常僅對(duì)業(yè)務(wù)邏輯做處理,文件存儲(chǔ)往往會(huì)調(diào)用專門(mén)的存儲(chǔ)服務(wù)。有2種處理思路:1、接收到完整文件數(shù)據(jù),存儲(chǔ)至內(nèi)存中,然后調(diào)用存儲(chǔ)接口;2、用流的方式,一邊read ServletRequest#InputStream,一邊write 到存儲(chǔ)服務(wù)的Stream中。個(gè)人認(rèn)為方式2更合理,節(jié)約內(nèi)存。

問(wèn)題二、tomcat暫存性能瓶頸

接口采用multipart/form-data方式上傳文件,tomcat接收到請(qǐng)求后會(huì)將請(qǐng)求內(nèi)容暫存至本地磁盤(pán),目錄通常位于tomcat basedir目錄下,比如我本地路徑為{basedir}\work\Tomcat\localhost\ROOT。受限于磁盤(pán)寫(xiě)入速率瓶頸,限制了接口性能上限。

機(jī)械硬盤(pán)寫(xiě)入速率預(yù)估100MB/s,則在千兆組網(wǎng)場(chǎng)景不存在性能瓶頸,如果是固態(tài)硬盤(pán),則寫(xiě)入速率更高。所以此項(xiàng)配置在2G以上組網(wǎng)才需考慮配置。

修改方法為修改sizeThreshold,默認(rèn)值為0。如下所示修改為1MB,即內(nèi)容大于1MB才存入磁盤(pán),小于直接存入內(nèi)存。

關(guān)于sizeThreshold,catalina包中處理邏輯為:如果對(duì)servlet做了配置,會(huì)使用配置的值。如果未配置,默認(rèn)值為0。util包中DiskFileItemFactory默認(rèn)值為10k。

servlet:
  multipart:
    file-size-threshold: 1MB

Tomcat中的相關(guān)處理邏輯,parseRequest方法按照RFC 1867規(guī)范對(duì)request進(jìn)行處理。

// org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory.java

/**
 * <p>The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory}
 * implementation. This implementation creates
 * {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep
 * their
 * content either in memory, for smaller items, or in a temporary file on disk,
 * for larger items. The size threshold, above which content will be stored on
 * disk, is configurable, as is the directory in which temporary files will be
 * created.</p>
 *
 * <p>If not otherwise configured, the default configuration values are as
 * follows:</p>
 * <ul>
 *   <li>Size threshold is 10 KiB.</li>
 *   <li>Repository is the system default temp directory, as returned by
 *       {@code System.getProperty("java.io.tmpdir")}.</li>
 * </ul>
 * <p>
 * <b>NOTE</b>: Files are created in the system default temp directory with
 * predictable names. This means that a local attacker with write access to that
 * directory can perform a TOUTOC attack to replace any uploaded file with a
 * file of the attackers choice. The implications of this will depend on how the
 * uploaded file is used but could be significant. When using this
 * implementation in an environment with local, untrusted users,
 * {@link #setRepository(File)} MUST be used to configure a repository location
 * that is not publicly writable. In a Servlet container the location identified
 * by the ServletContext attribute {@code javax.servlet.context.tempdir}
 * may be used.
 * </p>
 *
 * <p>Temporary files, which are created for file items, will be deleted when
 * the associated request is recycled.</p>
 *
 * @since FileUpload 1.1
 */
public class DiskFileItemFactory implements FileItemFactory {

    // ----------------------------------------------------- Manifest constants

    /**
     * The default threshold above which uploads will be stored on disk.
     */
    public static final int DEFAULT_SIZE_THRESHOLD = 10240;
}
// org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.java

/**
 * The threshold above which uploads will be stored on disk.
 */
private final int sizeThreshold;

/**
 * Returns an {@link java.io.OutputStream OutputStream} that can
 * be used for storing the contents of the file.
 *
 * @return An {@link java.io.OutputStream OutputStream} that can be used
 *         for storing the contents of the file.
 *
 */
@Override
public OutputStream getOutputStream() {
    if (dfos == null) {
        final File outputFile = getTempFile();
        dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
    }
    return dfos;
}
// org.apache.tomcat.util.http.fileupload.DeferredFileOutputStream.java

/**
 * An output stream which will retain data in memory until a specified
 * threshold is reached, and only then commit it to disk. If the stream is
 * closed before the threshold is reached, the data will not be written to
 * disk at all.
 * <p>
 * This class originated in FileUpload processing. In this use case, you do
 * not know in advance the size of the file being uploaded. If the file is small
 * you want to store it in memory (for speed), but if the file is large you want
 * to store it to file (to avoid memory issues).
 */
public class DeferredFileOutputStream
    extends ThresholdingOutputStream
{
     /**
     * Constructs an instance of this class which will trigger an event at the
     * specified threshold, and save data to a file beyond that point.
     * The initial buffer size will default to 1024 bytes which is ByteArrayOutputStream's default buffer size.
     *
     * @param threshold  The number of bytes at which to trigger an event.
     * @param outputFile The file to which data is saved beyond the threshold.
     */
    public DeferredFileOutputStream(final int threshold, final File outputFile)
    {
        this(threshold,  outputFile, null, null, null, ByteArrayOutputStream.DEFAULT_SIZE);
    }
}

問(wèn)題三、網(wǎng)絡(luò)帶寬瓶頸

對(duì)于常規(guī)企業(yè)內(nèi)部應(yīng)用,局域網(wǎng)環(huán)境下,至少能提供穩(wěn)定的千兆帶寬,常規(guī)業(yè)務(wù)接口不存在網(wǎng)絡(luò)帶寬瓶頸。但是對(duì)于文件上傳接口而言,即使是小文件上傳,接口并發(fā)高的場(chǎng)景帶寬消耗依然較大,可能是性能瓶頸。

以千兆帶寬為例,理論最大上傳速率=1000Mbps÷8=125MB/s理論最大上傳速率=1000Mbps÷8=125MB/s理論最大上傳速率=1000Mbps÷8=125MB/s,實(shí)際場(chǎng)景很難達(dá)到理論最大速率,按照100MB/s預(yù)估。500K:200QPS,1M:100QPS,2M:50QPS

問(wèn)題解決思路整理

  • client
    指請(qǐng)求接口的客戶端
  • nginx
    作為反向代理服務(wù)器
  • tomcat
    web容器
  • webserver
    web服務(wù),比如springboot項(xiàng)目

排查過(guò)程可以根據(jù)由外向內(nèi)層層遞進(jìn)的方式進(jìn)行排查,當(dāng)然也可采用經(jīng)驗(yàn)判斷法,對(duì)最有可能出現(xiàn)性能瓶頸的webserver進(jìn)行排查。

  • 復(fù)現(xiàn)問(wèn)題,在高負(fù)載場(chǎng)景請(qǐng)求接口復(fù)現(xiàn)問(wèn)題或者使用Jmeter等工作做并發(fā)壓力測(cè)試。復(fù)現(xiàn)問(wèn)題是解決問(wèn)題的基礎(chǔ)。
  • 查看接口請(qǐng)求耗時(shí),對(duì)耗時(shí)結(jié)構(gòu)進(jìn)行分析,比如Wating(TTFB)、Content Download耗時(shí)長(zhǎng),。比如Content Download耗時(shí)長(zhǎng),那就會(huì)首先懷疑帶寬。
  • nginx性能較高,出現(xiàn)瓶頸概率低??赏ㄟ^(guò)查看nginx訪問(wèn)日志,對(duì)比接口總耗時(shí),如果耗時(shí)差異較大,就需要排查nginx本身性能、nginx與tomcat之間網(wǎng)絡(luò)。
  • tomcat作為主流的web容器,影響性能的配置主要是maxThreads、maxConnections、堆內(nèi)存、垃圾回收。對(duì)于成熟的應(yīng)用開(kāi)發(fā)團(tuán)隊(duì),會(huì)有相對(duì)合理的初始配置??赏ㄟ^(guò)查看tomcat訪問(wèn)日志,對(duì)比webserver接口耗時(shí),如果耗時(shí)差異較大,就需要排查tomcat自身性能問(wèn)題。
  • webserver中的業(yè)務(wù)處理邏輯,通常是接口總耗時(shí)占比最高的。優(yōu)先在controller入口和出口記錄日志,計(jì)算controller總耗時(shí)。如果確定是業(yè)務(wù)邏輯耗時(shí)長(zhǎng),再層層遞進(jìn)排查縮小范圍,找到罪魁禍?zhǔn)住?/li>

測(cè)試性能匯總

測(cè)試環(huán)境

  • 服務(wù)器主機(jī)、客戶機(jī)
    測(cè)試環(huán)境所限,服務(wù)器主機(jī)、客戶機(jī)使用同一臺(tái)開(kāi)發(fā)主機(jī)。操作系統(tǒng):windows10,CPU:Intel(R) Xeon(R) Gold 6242R CPU @ 3.10GHz,內(nèi)存16G

  • 磁盤(pán)
    RND512KQ1T1 Read1219.86Mb/s Write44.88Mb/s

  • Jmeter
    400線程,60s拉起全部線程

  • tomcat
    tomcat9,做了如下配置

tomcat:
    threads:
        max: 400
    max-connections: 10000
    accept-count: 1000
  • jar啟動(dòng)參數(shù)
    配置了初始堆內(nèi)存 java -Dfile.encoding=UTF-8 -jar .\xxx.jar -server -Xms4096m -Xmx9000m

測(cè)試結(jié)果

類(lèi)型平均響應(yīng)時(shí)間 ms吞吐量/s
原始狀態(tài)220810.18
優(yōu)化Byte[]396689
優(yōu)化file-size-threshold1203265
基準(zhǔn)-(form-data)1279279
基準(zhǔn)-(優(yōu)化file-size-threshold)1092930
基準(zhǔn)-空接口2812401

原始狀態(tài):現(xiàn)場(chǎng)報(bào)性能問(wèn)題時(shí)的版本,性能太過(guò)炸裂,Jmeter線程數(shù)調(diào)整為4,測(cè)試上傳文件5KB
優(yōu)化Byte[]:優(yōu)化了從stream讀取存入優(yōu)化Byte[]方法,測(cè)試上傳文件5KB。此時(shí)網(wǎng)絡(luò)吞吐量45MB/s,生產(chǎn)環(huán)境服務(wù)器配置性能至少比當(dāng)前測(cè)試機(jī)器高2倍,接口性能至少提高1倍,對(duì)于千兆組網(wǎng)場(chǎng)景無(wú)須進(jìn)一步優(yōu)化,并發(fā)瓶頸是網(wǎng)絡(luò)帶寬
優(yōu)化file-size-threshold:優(yōu)化為>1MB文件才存入磁盤(pán),測(cè)試場(chǎng)景文件全部讀入內(nèi)存,測(cè)試上傳文件5KB。此時(shí)網(wǎng)絡(luò)吞吐量已大于100MB/s
基準(zhǔn)-(form-data):form-data配置簡(jiǎn)單key參數(shù),不上傳文件,服務(wù)端接口直接返回簡(jiǎn)單字符串。相當(dāng)于默認(rèn)情況下form-data參數(shù)類(lèi)型接口的性能基準(zhǔn),性能瓶頸是磁盤(pán)寫(xiě)入速率
基準(zhǔn)-(優(yōu)化file-size-threshold):form-data配置簡(jiǎn)單key參數(shù),不上傳文件,服務(wù)端接口直接返回簡(jiǎn)單字符串,優(yōu)化為>1MB文件才存入磁盤(pán)。可以對(duì)比看出磁盤(pán)與內(nèi)存的速率差異
基準(zhǔn)-空接口:普通的get無(wú)參接口,直接返回“hello”,作為當(dāng)前配置環(huán)境下,tomcat接口性能極限

現(xiàn)場(chǎng)問(wèn)題處理方案

經(jīng)過(guò)定位現(xiàn)場(chǎng)性能瓶頸是網(wǎng)絡(luò)。現(xiàn)場(chǎng)采用分布式架構(gòu),客戶端、服務(wù)端部署多個(gè)節(jié)點(diǎn),客戶端通過(guò)本地回環(huán)地址調(diào)用服務(wù)端,降低網(wǎng)絡(luò)壓力。

原架構(gòu)

新架構(gòu)

以上就是SpringBoot文件上傳接口并發(fā)性能調(diào)優(yōu)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot接口性能調(diào)優(yōu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java編程之如何通過(guò)JSP實(shí)現(xiàn)頭像自定義上傳

    Java編程之如何通過(guò)JSP實(shí)現(xiàn)頭像自定義上傳

    之前做這個(gè)頭像上傳功能還是花了好多時(shí)間的,今天我將我的代碼分享給大家,下面這篇文章主要給大家介紹了關(guān)于Java編程之如何通過(guò)JSP實(shí)現(xiàn)頭像自定義上傳的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • idea2022創(chuàng)建javaweb項(xiàng)目步驟(超詳細(xì))

    idea2022創(chuàng)建javaweb項(xiàng)目步驟(超詳細(xì))

    本文主要介紹了idea2022創(chuàng)建javaweb項(xiàng)目步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • mybatis-plus如何修改日志只打印SQL語(yǔ)句不打印查詢結(jié)果

    mybatis-plus如何修改日志只打印SQL語(yǔ)句不打印查詢結(jié)果

    這篇文章主要介紹了mybatis-plus如何修改日志只打印SQL語(yǔ)句不打印查詢結(jié)果問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • SpringBoot整合BootStrap實(shí)戰(zhàn)

    SpringBoot整合BootStrap實(shí)戰(zhàn)

    這篇文章主要介紹了SpringBoot整合BootStrap實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java 添加文本框到PPT幻燈片過(guò)程解析

    Java 添加文本框到PPT幻燈片過(guò)程解析

    這篇文章主要介紹了Java 添加文本框到PPT幻燈片過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • SpringBoot使用Redis單機(jī)版過(guò)期鍵監(jiān)聽(tīng)事件的實(shí)現(xiàn)示例

    SpringBoot使用Redis單機(jī)版過(guò)期鍵監(jiān)聽(tīng)事件的實(shí)現(xiàn)示例

    在緩存的使用場(chǎng)景中經(jīng)常需要使用到過(guò)期事件,本文主要介紹了SpringBoot使用Redis單機(jī)版過(guò)期鍵監(jiān)聽(tīng)事件的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • java 將方法作為傳參--多態(tài)的實(shí)例

    java 將方法作為傳參--多態(tài)的實(shí)例

    下面小編就為大家?guī)?lái)一篇java 將方法作為傳參--多態(tài)的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • 帶你快速搞定java多線程(4)

    帶你快速搞定java多線程(4)

    這篇文章主要介紹了java多線程編程實(shí)例,分享了幾則多線程的實(shí)例代碼,具有一定參考價(jià)值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下
    2021-07-07
  • java開(kāi)發(fā)主流定時(shí)任務(wù)解決方案全橫評(píng)詳解

    java開(kāi)發(fā)主流定時(shí)任務(wù)解決方案全橫評(píng)詳解

    這篇文章主要為大家介紹了java開(kāi)發(fā)主流定時(shí)任務(wù)解決方案全橫評(píng)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Java多線程之定時(shí)器Timer的實(shí)現(xiàn)

    Java多線程之定時(shí)器Timer的實(shí)現(xiàn)

    定時(shí)/計(jì)劃功能在Java應(yīng)用的各個(gè)領(lǐng)域都使用得非常多,比方說(shuō)Web層面。本文主要為大家介紹了Java多線程中定時(shí)器Timer的實(shí)現(xiàn),感興趣的小伙伴可以了解一下
    2022-10-10

最新評(píng)論