支援JDK19虛擬執行緒的web框架,之一:體驗

2023-09-13 12:00:27

歡迎存取我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos

關於虛擬執行緒

  • 隨著JDK19 GA版本的釋出,虛擬執行緒(來自Project Loom)這一特性也閃亮登場,虛擬執行緒是 JDK 而並非OS 實現的輕量級執行緒(Lightweight Process,LWP),許多虛擬執行緒共用同一作業系統執行緒,虛擬執行緒的數量可以遠大於作業系統執行緒的數量(可以參考golang的協程)
  • 虛擬執行緒方面的文章,如今已經有很多優秀博主寫得非常詳細深入,欣宸就不在這方面獻醜了,而是將重點放在虛擬執行緒的實用性方面,用實戰與大家一同學習:如何讓虛擬執行緒在web服務中發揮作用,效果究竟如何?

關於支援虛擬執行緒的框架

  • Quarkus的web模組已經支援JDK19的虛擬執行緒了,收到web請求後,可以交給虛擬執行緒去處理,本身開發的工作量也不大,文章標題《支援JDK19虛擬執行緒的web框架》指的就是Quarkus框架,也是整個系列所用到的框架
  • 關於Quarkus,這是個強大的java框架,介紹Quarkus並非本文重點,有興趣的讀者可以關注欣宸的《quarkus實戰》系列原創

本篇概覽

  • Quarkus框架已支援使用虛擬執行緒處理web請求,欣宸原創的《支援JDK19虛擬執行緒的web框架》系列會陪伴大家,全程以實戰動手為主,從快速體驗,再到實際編碼,以及部署構建都有詳細說明,確保讀者們既有成就感,又能學點小技能,在虛擬執行緒最火爆的時候搶先一步嚐鮮,窺探未來技術的一角
  • 整個系列一共四篇文章,分別是《極速體驗》、《細說開發》、《深入瞭解》(兩篇),本篇是系列的開篇,主要內容是:不寫程式碼,通過複製貼上輕鬆完成最初的體驗,把編碼和設定這些繁重的工作留在後面的文章
  • 接下來說說本篇的主要內容:
  1. 整個體驗環境,由quarkus應用和資料庫組成,當我們向quarkus應用發起請求的時候,應用會去查詢資料再將資料返回給前端
  2. quarkus應用提供了兩個介面,第一個名為/vt/persons,使用虛擬執行緒來處理web請求,第二個名為/pool/persons,使用傳統執行緒池來處理web請求,整體上如下圖所示

  1. 上述三臺電腦都用到了docker,按照官方檔案部署最新版即可,我這裡是20.10.19版本
  2. postgresql是用docker部署的,版本是15
  3. quarkus版本為2.13.2.Final,使用的JDK是19
  4. springboot版本為2.7.4,用到的JDK是11
  5. 壓測工具k6為docker版,版本號v0.40.0
  • 省吃儉用攢了這麼多電腦來搞事情,主要是想得到的壓測結果儘量客觀獨立,不受太多幹擾
  • 接下來,開始動手吧

部署資料庫

  • 首先是安裝資料庫,確保宿主機上的docker已經部署好
  • 部署資料庫很簡單,一行命令即可,如下所示,除了服務,還新建了資料庫,並且設定了賬號和密碼
sudo docker run \
--name postgres \
-p 5432:5432 \
-e POSTGRES_DB=quarkus_test \
-e POSTGRES_USER=quarkus \
-e POSTGRES_PASSWORD=123456 \
-d \
postgres:15

建表,新增資料

  • 用資料庫工具連線資料庫(我這裡用的是IDEA的資料庫工具),執行以下命令,會新建一個表,並匯入6條記錄
create table person (
  id serial primary key,
  name varchar(255),
  gender varchar(255),
  age int,
  external_id int
);
insert into person(name, age, gender, external_id) values('John Smith', 25, 'MALE', 10);
insert into person(name, age, gender, external_id) values('Paul Walker', 65, 'MALE', 20);
insert into person(name, age, gender, external_id) values('Lewis Hamilton', 35, 'MALE', 30);
insert into person(name, age, gender, external_id) values('Veronica Jones', 20, 'FEMALE', 40);
insert into person(name, age, gender, external_id) values('Anne Brown', 60, 'FEMALE', 50);
insert into person(name, age, gender, external_id) values('Felicia Scott', 45, 'FEMALE', 60);

部署quarkus應用

  • 登入用於部署quarkus應用的伺服器,確保伺服器上已經成功部署了docker
  • 新建名為application.properties的組態檔,內容是資料庫相關的引數,如下所示
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=123456
quarkus.datasource.reactive.url=postgresql://192.168.0.99:5432/quarkus_test
  • 我這裡,application.properties的路徑是:/home/lighthouse/config/quarkus/application.properties

  • 檢查application.properties檔案的可讀性,執行以下命令設定

sudo chmod a+r application.properties
  • 執行以下命令即可部署和啟動quarkus應用,注意application.properties檔案的位置要換成您自己的組態檔路徑
docker run -idt \
--name quarkus \
-p 8080:8080 \
-v /home/lighthouse/config/quarkus/application.properties:/application/config/application.properties \
bolingcavalry/quarkus-virual-threads-demo:x64-0.0.3
  • 上述命令中,quarkus應用的映象bolingcavalry/quarkus-virual-threads-demo:x64-0.0.3是我提前準備好的,本篇只管使用即可,至於如何製作此映象,接下來的文章會有詳細說明

  • sudo docker logs quarkus命令檢視啟動紀錄檔,如果啟動成功,資訊如下所示

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2022-10-16 11:46:41,406 INFO  [io.quarkus] (main) quarkus-virual-threads-demo 1.0-SNAPSHOT on JVM (powered by Quarkus 2.13.2.Final) started in 0.804s. Listening on: http://0.0.0.0:8080
2022-10-16 11:46:41,414 INFO  [io.quarkus] (main) Profile prod activated. 
2022-10-16 11:46:41,414 INFO  [io.quarkus] (main) Installed features: [cdi, reactive-pg-client, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]

驗證基本功能

  • 假設quarkus所在宿主機的IP地址是192.168.0.1,那麼在瀏覽器上存取地址http://192.168.0.1:8080/vt/persons/1,如下圖,應用成功從資料庫取得資料,並且將當前執行緒資訊也返回給瀏覽器

  • 作為對比,該伺服器上還有個使用虛擬執行緒的常規web服務,地址是:http://192.168.0.1:8080/pool/persons/1,如下圖,該介面也能從資料庫取得資料,並且將當前執行緒資訊也返回給瀏覽器,注意兩個介面執行緒資訊的差異

  • 對於虛擬執行緒的介面http://192.168.0.1:8080/vt/persons/1,可以嘗試多次存取,可見每次返回的虛擬執行緒Id都不一樣,而使用執行緒池的介面http://192.168.0.1:8080/pool/persons/1,多次存取,返回的始終是同一個執行緒的id

  • 基本功能似乎沒有問題,接下來可以壓測了,用資料說話

壓測使用虛擬執行緒的介面

  • 這次使用的壓測工具名為k6,選它的原因只有兩個字:簡單
  • 在執行壓測的電腦上,確保docker可以正常使用
  • 新建名為k6-vt-docker.js的檔案,內容如下,可見非常簡單,發請求再校驗響應,只要返回碼是200,並且body大小大於0就算一次成功,要注意的事兩個引數,其中vus是並行數,duration是測試持續時間,可見這裡設定的是模擬30使用者並行請求,持續時間是60秒
import http from 'k6/http';
import { sleep, check } from 'k6';

export let options = {
  vus: 30,
  duration: '60s',
};

export default function () {
  let r = Math.floor(Math.random() * 6) + 1;
  const res = http.get(`http://192.168.0.1:8080/vt/persons/${r}`);
  check(res, {
    'is status 200': (res) => res.status === 200,
    'body size is > 0': (r) => r.body.length > 0,
  });
  sleep(1);
}
  • 一行命令即可開始壓測,如下
 docker run --rm -i loadimpact/k6 run - < k6-vt-docker.js
  • 等待一分鐘,壓測完成,得到以下資料

  • 這裡將幾個重要引數介紹一下
  1. checks : 通過率,如果滿足返回碼200且包體大小大於0的條件,就表示本次請求成功,這裡顯示100.00%,表示全部通過
  2. http_req_waiting:等待響應的事件,這裡avg=33.83ms,表示平均等待時間為33.83毫秒
  3. http_reqs:每秒處理請求數,即常說的QPS,這裡是28個每秒
  • 接下來將並行數調整為300(即vue引數),如下圖

  • 再來個狠的,並行數一口暴漲到5000試試,如下圖,這麼高的並行,已經無法保障100%的成功率了,好在95%也不低,另外平均等待時間從39毫秒暴漲到6.26秒,至於QPS當然不會太高,僅比300並行的時候高了百分之五十

  • 壓測使用執行緒池的介面

    • 操作步驟和剛才差不多,只是要修改指令碼中的介面地址,如下所示
    import http from 'k6/http';
    import { sleep, check } from 'k6';
    
    export let options = {
      vus: 30,
      duration: '60s',
    };
    
    export default function () {
      let r = Math.floor(Math.random() * 6) + 1;
      const res = http.get(`http://192.168.0.1:8080/pool/persons/${r}`);
      check(res, {
        'is status 200': (res) => res.status === 200,
        'body size is > 0': (r) => r.body.length > 0,
      });
      sleep(1);
    }
    
    
    • 30並行的壓測結果如下,和使用虛擬執行緒並無區別

    • 300並行壓測結果如下,和使用虛擬執行緒並無區別

    • 5000並行壓測結果如下,和虛擬執行緒相比,平均等待時間略長,QPS略低,但是整體上差別不大

    小結

    • 在響應式web服務中,並且關聯的資料庫操作也是響應式的,相比傳統的執行緒池模型,虛擬執行緒並未帶來明顯收益

    • 不甘心啊,接下來就換成SpringBoot應用,模擬咱們日常開發最常見的資料庫存取場景,看看相比之下,差距有多大?

    停止quarkus應用

    • 由於打算將SpringBoot應用部署在quarkus應用所在電腦上,所以要先停掉quarkus應用,執行以下命令
    sudo docker stop quarkus
    

    啟動SpringBoot應用

    • 新建名為application.properties的組態檔,內容是資料庫相關的引數,如下所示
    spring.datasource.url=jdbc:postgresql://42.193.162.141:5432/quarkus_test
    spring.datasource.username=quarkus
    spring.datasource.password=123456
    spring.datasource.driver-class-name=org.postgresql.Driver
    
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
    spring.jpa.properties.hibernate.hbm2ddl.auto=update
    spring.datasource.hikari.maximum-pool-size=256
    
    • 我這裡,application.properties的路徑是:/home/lighthouse/config/springboot/application.properties

    • 檢查application.properties檔案的可讀性,執行以下命令設定

    sudo chmod a+r application.properties
    
    • 執行以下命令即可部署和啟動springboot應用,注意application.properties檔案的位置要換成您自己的組態檔路徑
    docker run -idt \
    --name springboot \
    -p 8080:8080 \
    -v /home/lighthouse/config/springboot/application.properties:/application/BOOT-INF/classes/application.properties \
    bolingcavalry/springboot-postgresql-demo:x64-0.0.3
    
    • 上述命令中,springboot應用的映象bolingcavalry/springboot-postgresql-demo:x64-0.0.3是我提前準備好的,本篇只管使用即可,至於如何製作此映象,接下來的文章會有詳細說明

    • 看到以下紀錄檔,證明應用啟動成功

    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v2.7.4)
    
    2022-10-16 13:01:01.022  INFO 1 --- [           main] .b.s.SpringbootPostgresqlDemoApplication : Starting SpringbootPostgresqlDemoApplication v0.0.1-SNAPSHOT using Java 11.0.13 on 5c25db81639e with PID 1 (/application/BOOT-INF/classes started by root in /application)
    2022-10-16 13:01:01.025  INFO 1 --- [           main] .b.s.SpringbootPostgresqlDemoApplication : No active profile set, falling back to 1 default profile: "default"
    2022-10-16 13:01:01.795  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
    2022-10-16 13:01:01.857  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 47 ms. Found 1 JPA repository interfaces.
    2022-10-16 13:01:02.392  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
    2022-10-16 13:01:02.405  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2022-10-16 13:01:02.405  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
    2022-10-16 13:01:02.492  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2022-10-16 13:01:02.492  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1364 ms
    2022-10-16 13:01:02.701  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
    2022-10-16 13:01:02.741  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.11.Final
    2022-10-16 13:01:02.867  INFO 1 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
    2022-10-16 13:01:02.942  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
    2022-10-16 13:01:03.164  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
    2022-10-16 13:01:03.179  INFO 1 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
    2022-10-16 13:01:03.688  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
    2022-10-16 13:01:03.695  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
    

    壓測SpringBoot應用

    • 壓測SpringBoot應用的k6指令碼如下
    import http from 'k6/http';
    import { sleep, check } from 'k6';
    
    export let options = {
      vus: 30,
      duration: '60s',
    };
    
    export default function () {
      let r = Math.floor(Math.random() * 6) + 1;
      const res = http.get(`http://192.168.0.1:8080/springboot/persons/${r}`);
      check(res, {
        'is status 200': (res) => res.status === 200,
        'body size is > 0': (r) => r.body.length > 0,
      });
      sleep(1);
    }
    
    
    • 30並行壓測結果如下,這麼整齊,和quarkus的基本一致

    • 300並行壓測結果如下,幾個關鍵指標依舊和quarkus保持對齊,這讓我覺得很索然無味

    • 5000並行,終於有了點差距,但也不明顯,成功率91%,低於quarkus的95%,相比quarkus,SpringBoot的平均等待時間略高,QPS略低

    • 至此,咱們通過實際對比的方式,已體驗過quarkus使用虛擬執行緒的web服務,同在quarkus框架中,虛擬執行緒帶來的提升並不明顯,在對比未做優化的SpringBoot應用時,也沒有在吞吐量方面帶來提升,但這一切都不能立即下定論,也許是欣宸的用法有嚴重問題...如果真有問題,聰明的您,在下一篇文章《編碼實戰》中一定會發現,如果真有問題,不要可憐我,懟我,讓我懺悔...
    • 接下來的文章,咱們一起深入程式設計實戰,使用JDK19後,對JDK、maven、IDE、編譯構建、製作docker映象等方方面面相比以前都有變化,這些都會在文中逐一處理,咱們一起開發出使用虛擬執行緒的quarkus應

    歡迎關注部落格園:程式設計師欣宸

    學習路上,你不孤單,欣宸原創一路相伴...