1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        WebFlux和SpringMVC性能對比

        共 9839字,需瀏覽 20分鐘

         ·

        2019-11-17 23:21


        本文來源:https://blog.csdn.net/get_set/article/details/79492439作者:享學(xué)IT本文已收錄至我的GitHub

        從負(fù)載測試看異步非阻塞的優(yōu)勢

        前面總是“安利”異步非阻塞的好處,下面我們就實(shí)實(shí)在在感受一下響應(yīng)式編程在高并發(fā)環(huán)境下的性能提升。異步非阻塞的優(yōu)勢體現(xiàn)在I/O操作方面,無論是文件I/O、網(wǎng)絡(luò)I/O,還是數(shù)據(jù)庫讀寫,都可能存在阻塞的情況。

        我們的測試內(nèi)容有三:

        1. 首先分別創(chuàng)建基于WebMVC和WebFlux的Web服務(wù),來對比觀察異步非阻塞能帶來多大的性能提升,我們模擬一個簡單的帶有延遲的場景,然后啟動服務(wù)使用gatling進(jìn)行測試,并進(jìn)行分析;

        2. 由于現(xiàn)在微服務(wù)架構(gòu)應(yīng)用越來越廣泛,我們基于第一步的測試項(xiàng)目進(jìn)一步觀察調(diào)用存在延遲的服務(wù)的情況下的測試數(shù)據(jù),其實(shí)主要是針對客戶端的測試:阻塞的RestTemplate和非阻塞的WebClient;

        3. 針對MongoDB的同步和異步數(shù)據(jù)庫驅(qū)動進(jìn)行性能測試和分析。

        說明:本節(jié)進(jìn)行的并非是嚴(yán)謹(jǐn)?shù)幕谛阅苷{(diào)優(yōu)的需求的,針對具體業(yè)務(wù)場景的負(fù)載測試。本節(jié)測試場景簡單而直接,各位朋友GET到我的點(diǎn)即可。
        此外:由于本節(jié)主要是進(jìn)行橫向?qū)Ρ葴y試,因此不需要特定的硬件資源配置,不過還是建議在Linux環(huán)境下進(jìn)行測試,我最初是在Win10上跑的,當(dāng)用戶數(shù)上來之后出現(xiàn)了不少請求失敗的情況,下邊的測試數(shù)據(jù)是在一臺系統(tǒng)為Deepin Linux(Debian系)的筆記本上跑出來的。

        那么我們就開始搭建測試環(huán)境吧~ (關(guān)于Spring WebFlux 不熟悉的話,請參考Spring WebFlux快速上手)。

        1.4.1 帶有延遲的負(fù)載測試分析

        1)搭建待測試項(xiàng)目

        我們分別基于WebMVC和WebFlux創(chuàng)建兩個項(xiàng)目:mvc-with-latencyWebFlux-with-latency。

        為了模擬阻塞,我們分別在兩個項(xiàng)目中各創(chuàng)建一個帶有延遲的/hello/{latency}的API。比如/hello/100的響應(yīng)會延遲100ms。

        mvc-with-latency中創(chuàng)建HelloController.java

            @RestController
            public class HelloController {
                @GetMapping("/hello/{latency}")
                public String hello(@PathVariable long latency) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(latency);   // 1
                    } catch (InterruptedException e) {
                        return "Error during thread sleep";
                    }
                    return "Welcome to reactive world ~";
                }
            }
        1. 利用sleep來模擬業(yè)務(wù)場景中發(fā)生阻塞的情況。

        WebFlux-with-latency中創(chuàng)建HelloController.java

            @RestController
            public class HelloController {
                @GetMapping("/hello/{latency}")
                public Mono   hello (@PathVariable int latency)  {
                    return Mono.just("Welcome to reactive world ~")
                            .delayElement(Duration.ofMillis(latency)); // 1
                }
            }
        1. 使用delayElement操作符來實(shí)現(xiàn)延遲。

        然后各自在application.properties中配置端口號8091和8092:

        server.port=8091

        啟動應(yīng)用。

        2)編寫負(fù)載測試腳本

        本節(jié)我們采用gatling來進(jìn)行測試。創(chuàng)建測試項(xiàng)目gatling-scripts。

        POM中添加gatling依賴和插件(目前gradle暫時還沒有這個插件,所以只能是maven項(xiàng)目):

            <dependencies>
                <dependency>
                    <groupId>io.gatling.highchartsgroupId>
                    <artifactId>gatling-charts-highchartsartifactId>
                    <version>2.3.0version>
                    <scope>testscope>
                dependency>
            dependencies>
            <build>
                <plugins>
                    <plugin>
                        <groupId>io.gatlinggroupId>
                        <artifactId>gatling-maven-pluginartifactId>
                        <version>2.2.4version>
                    plugin>
                plugins>
            build>

        src/test下創(chuàng)建測試類,gatling使用scala語言編寫測試類:

            import io.gatling.core.scenario.Simulation
            import io.gatling.core.Predef._
            import io.gatling.http.Predef._

            import scala.concurrent.duration._

            class LoadSimulation extends Simulation {

              // 從系統(tǒng)變量讀取 baseUrl、path和模擬的用戶數(shù)
              val baseUrl = System.getProperty("base.url")
              val testPath = System.getProperty("test.path")
              val sim_users = System.getProperty("sim.users").toInt

              val httpConf = http.baseURL(baseUrl)

              // 定義模擬的請求,重復(fù)30次
              val helloRequest = repeat(30) {
                // 自定義測試名稱
                exec(http("hello-with-latency")
                  // 執(zhí)行g(shù)et請求
                  .get(testPath))
                  // 模擬用戶思考時間,隨機(jī)1~2秒鐘
                  .pause(1 second, 2 seconds)
              }

              // 定義模擬的場景
              val scn = scenario("hello")
                // 該場景執(zhí)行上邊定義的請求
                .exec(helloRequest)

              // 配置并發(fā)用戶的數(shù)量在30秒內(nèi)均勻提高至sim_users指定的數(shù)量
              setUp(scn.inject(rampUsers(sim_users).over(30 seconds)).protocols(httpConf))
            }

        如上,這個測試的場景是:

        • 指定的用戶量是在30秒時間內(nèi)勻速增加上來的;

        • 每個用戶重復(fù)請求30次指定的URL,中間會隨機(jī)間隔1~2秒的思考時間。

        其中URL和用戶量通過base.url、test.pathsim.users變量傳入,借助maven插件,通過如下命令啟動測試:

            mvn gatling:test -Dgatling.simulationClass=test.load.sims.LoadSimulation -Dbase.url=http://localhost:8091/ -Dtest.path=hello/100 -Dsim.users=300

        就表示用戶量為300的對http://localhost:8091/hello/100的測試。

        3)觀察線程數(shù)量

        測試之前,我們打開jconsole觀察應(yīng)用(連接MVCWithLatencyApplication)的線程變化情況:

        a5eeaae7a56c53616559af0c6c8324d7.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器

        如圖(分辨率問題顯示不太好)是剛啟動無任何請求進(jìn)來的時候,默認(rèn)執(zhí)行線程有10個,總的線程數(shù)31-33個。

        比如,當(dāng)進(jìn)行用戶數(shù)為2500個的測試時,執(zhí)行線程增加到了200個,總的線程數(shù)峰值為223個,就是增加的這190個執(zhí)行線程。如下:

        d1b3b0ca4f0dfd38e825761926bbf8b2.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器

        由于在負(fù)載過去之后,執(zhí)行線程數(shù)量會隨機(jī)減少回10個,因此看最大線程編號估算線程個數(shù)的話并不靠譜,我們可以用“峰值線程數(shù)-23”得到測試過程中的執(zhí)行線程個數(shù)。

        4)負(fù)載測試

        首先我們測試mvc-with-latency

        • -Dbase.url=http://localhost:8091/;

        • -Dtest.path=hello/100(延遲100ms);

        • -Dsim.users=1000/2000/3000/…/10000。

        測試數(shù)據(jù)如下(Tomcat最大線程數(shù)200,延遲100ms):

        d5b6404e2394f6a3912ae55e73203445.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器 0b2a24bf86abfd525500cc5822826f42.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器

        由以上數(shù)據(jù)可知:

        1. 用戶量在接近3000的時候,線程數(shù)達(dá)到默認(rèn)的最大值200;

        2. 線程數(shù)達(dá)到200前,95%的請求響應(yīng)時長是正常的(比100ms多一點(diǎn)點(diǎn)),之后呈直線上升的態(tài)勢;

        3. 線程數(shù)達(dá)到200后,吞吐量增幅逐漸放緩。

        這里我們不難得出原因,那就是當(dāng)所有可用線程都在阻塞狀態(tài)的話,后續(xù)再進(jìn)入的請求只能排隊(duì),從而當(dāng)達(dá)到最大線程數(shù)之后,響應(yīng)時長開始上升。我們以6000用戶的報告為例:

        7040a46dd0ae9bafcfdeaa1df58292a9.webptitle

        這幅圖是請求響應(yīng)時長隨時間變化的圖,可以看到大致可以分為五個段:

        • A. 有空閑線程可用,請求可以在100ms+時間返回;

        • B. 線程已滿,新來的請求開始排隊(duì),因?yàn)锳和B階段是用戶量均勻上升的階段,所以排隊(duì)的請求越來越多;

        • C. 每秒請求量穩(wěn)定下來,但是由于排隊(duì),維持一段時間的高響應(yīng)時長;

        • D. 部分用戶的請求完成,每秒請求量逐漸下降,排隊(duì)情況逐漸緩解;

        • E. 用戶量降至線程滿負(fù)荷且隊(duì)列消化后,請求在正常時間返回;

        所有請求的響應(yīng)時長分布如下圖所示:

        096b0413108d9d89f1a7e0c174168174.webptitle

        A/E段與C段的時長只差就是平均的排隊(duì)等待時間。在持續(xù)的高并發(fā)情況下,大部分請求是處在C段的。而且等待時長隨請求量的提高而線性增長。

        增加Servlet容器處理請求的線程數(shù)量可以緩解這一問題,就像上邊把最大線程數(shù)量從默認(rèn)的200增加的400。

        最高200的線程數(shù)是Tomcat的默認(rèn)設(shè)置,我們將其設(shè)置為400再次測試。在application.properties中增加:

        server.tomcat.max-threads=400

        測試數(shù)據(jù)如下:

        1a942ea98bf8b568fb3d7f6ec7fd96c4.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器 6b043e94d9452b45ab601fb1a5d33d94.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器

        由于工作線程數(shù)擴(kuò)大一倍,因此請求排隊(duì)的情況緩解一半,具體可以對比一下數(shù)據(jù):

        1. “最大線程數(shù)200用戶5000”的“95%響應(yīng)時長”恰好與“最大線程數(shù)400用戶10000”完全一致,我對天發(fā)誓,這絕對絕對是真實(shí)數(shù)據(jù),更加巧合的是,吞吐量也恰好是1:2的關(guān)系!有此巧合也是因?yàn)闇y試場景太簡單粗暴,哈哈;

        2. “95%響應(yīng)時長”的曲線斜率也是兩倍的關(guān)系。

        這也再次印證了我們上邊的分析。增加線程數(shù)確實(shí)可以一定程度下提高吞吐量,降低因阻塞造成的響應(yīng)延時,但此時我們需要權(quán)衡一些因素:

        • 增加線程是有成本的,JVM中默認(rèn)情況下在創(chuàng)建新線程時會分配大小為1M的線程棧,所以更多的線程異味著更多的內(nèi)存;

        • 更多的線程會帶來更多的線程上下文切換成本。

        我們再來看一下對于WebFlux-with-latency的測試數(shù)據(jù):

        fceba5dd75c4622b703a3383d20c7898.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器
        • 這里沒有統(tǒng)計(jì)線程數(shù)量,因?yàn)閷τ谶\(yùn)行在異步IO的Netty之上的WebFlux應(yīng)用來說,其工作線程數(shù)量始終維持在一個固定的數(shù)量上,通常這個固定的數(shù)量等于CPU核數(shù)(通過jconsole可以看到有名為reactor-http-nio-Xparallel-X的線程,我這是四核八線程的i7,所以X從1-8),因?yàn)楫惒椒亲枞麠l件下,程序邏輯是由事件驅(qū)動的,并不需要多線程并發(fā);

        • 隨著用戶數(shù)的增多,吞吐量基本呈線性增多的趨勢;

        • 95%的響應(yīng)都在100ms+的可控范圍內(nèi)返回了,并未出現(xiàn)延時的情況。

        可見,非阻塞的處理方式規(guī)避了線程排隊(duì)等待的情況,從而可以用少量而固定的線程處理應(yīng)對大量請求的處理。

        除此之外,我又一步到位直接測試了一下20000用戶的情況:

        1. mvc-with-latency的測試由于出現(xiàn)了許多的請求fail而以失敗告終;

        2. WebFlux-with-latency應(yīng)對20000用戶已然面不改色心不慌,吞吐量達(dá)到7228 req/sec(我擦,正好是10000用戶下的兩倍,太巧了今天怎么了,絕對是真實(shí)數(shù)據(jù)?。?,95%響應(yīng)時長僅117ms。

        最后,再給出兩個吞吐量和響應(yīng)時長的圖,更加直觀地感受異步非阻塞的WebFlux是如何一騎絕塵的吧:

        1746c30a2b0c993ec8e66b4d715ab65f.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器 6a0ce8f7131e00d440ae9ef927701aae.webp(6)Spring WebFlux性能測試——響應(yīng)式Spring的道法術(shù)器

        綜上來說,結(jié)論就是相對于Servlet多線程的處理方式來說,Spring WebFlux在應(yīng)對高并發(fā)的請求時,借助于異步IO,能夠以少量而穩(wěn)定的線程處理更高吞吐量的請求,尤其是當(dāng)請求處理過程如果因?yàn)闃I(yè)務(wù)復(fù)雜或IO阻塞等導(dǎo)致處理時長較長時,對比更加顯著。

        本文模擬的延遲時間較長,達(dá)到了100ms,雖然有些夸張,但是不能否認(rèn)IO阻塞的嚴(yán)重性。如果CPU執(zhí)行一條指令的時間是1秒,那么內(nèi)存尋址就需要4分20秒,SSD尋址需要4.5天,磁盤尋址需要1個月。異步IO能夠?qū)PU從“漫長”的等待中解放出來,不再需要堆砌大量的線程來提高CPU利用率。這也是Spring WebFlux能夠以少量線程處理更高吞吐量的原因。

        此時,我們更加理解了Nodejs的驕傲,不過我們大Java語言也有了Vert.x和現(xiàn)在的Spring WebFlux。


        兩年嘔心瀝血的文章「面試題」「基礎(chǔ)」「進(jìn)階」這里全都有!

        300多篇原創(chuàng)技術(shù)文章海量視頻資源精美腦圖面試題

        長按掃碼可關(guān)注獲取 

        在看和分享對我非常重要!930345e797bb08b4ea3e39d935fe6943.webp

        創(chuàng)作不易,各位的支持和認(rèn)可,就是我創(chuàng)作的最大動力,我們下篇文章見! 求點(diǎn)贊 求關(guān)注?  求分享? 求留言?

        b9dee5797a2c4db14991f63c031884f9.webp

        點(diǎn)擊閱讀原文,關(guān)注我的GitHub

        瀏覽 113
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中文字幕avav | 《上司的少妇做爰hd》 | 123日逼网| 天堂色| 透明人3未删减版啪啪 | 日韩欧美午夜成人无码 | 北岛玲一区二区三区四区 | 日韩干逼 | 女帝裸体被叫爽 | 私密直播全婐 |