<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          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ù),來對比觀察異步非阻塞能帶來多大的性能提升,我們模擬一個(gè)簡單的帶有延遲的場景,然后啟動服務(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)建兩個(gè)項(xiàng)目:mvc-with-latencyWebFlux-with-latency

          為了模擬阻塞,我們分別在兩個(gè)項(xiàng)目中各創(chuàng)建一個(gè)帶有延遲的/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暫時(shí)還沒有這個(gè)插件,所以只能是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))
                    // 模擬用戶思考時(shí)間,隨機(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))
              }

          如上,這個(gè)測試的場景是:

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

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

          其中URL和用戶量通過base.urltest.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)來的時(shí)候,默認(rèn)執(zhí)行線程有10個(gè),總的線程數(shù)31-33個(gè)。

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

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

          由于在負(fù)載過去之后,執(zhí)行線程數(shù)量會隨機(jī)減少回10個(gè),因此看最大線程編號估算線程個(gè)數(shù)的話并不靠譜,我們可以用“峰值線程數(shù)-23”得到測試過程中的執(zhí)行線程個(gè)數(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í)候,線程數(shù)達(dá)到默認(rèn)的最大值200;

          2. 線程數(shù)達(dá)到200前,95%的請求響應(yīng)時(shí)長是正常的(比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)時(shí)長開始上升。我們以6000用戶的報(bào)告為例:

          7040a46dd0ae9bafcfdeaa1df58292a9.webptitle

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

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

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

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

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

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

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

          096b0413108d9d89f1a7e0c174168174.webptitle

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

          增加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í)長”恰好與“最大線程數(shù)400用戶10000”完全一致,我對天發(fā)誓,這絕對絕對是真實(shí)數(shù)據(jù),更加巧合的是,吞吐量也恰好是1:2的關(guān)系!有此巧合也是因?yàn)闇y試場景太簡單粗暴,哈哈;

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

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

          • 增加線程是有成本的,JVM中默認(rèn)情況下在創(chuàng)建新線程時(shí)會分配大小為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ù)量始終維持在一個(gè)固定的數(shù)量上,通常這個(gè)固定的數(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)延時(shí)的情況。

          可見,非阻塞的處理方式規(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)時(shí)長僅117ms。

          最后,再給出兩個(gè)吞吐量和響應(yīng)時(shí)長的圖,更加直觀地感受異步非阻塞的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ā)的請求時(shí),借助于異步IO,能夠以少量而穩(wěn)定的線程處理更高吞吐量的請求,尤其是當(dāng)請求處理過程如果因?yàn)闃I(yè)務(wù)復(fù)雜或IO阻塞等導(dǎo)致處理時(shí)長較長時(shí),對比更加顯著。

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

          此時(shí),我們更加理解了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ī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天看天天干天天操 | 草草在线观看 | 黄色操逼| 99在线观看视频免费 | 青青草原成人网站 |