支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用()

  本篇文章为你整理了支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用()的详细内容,包含有 支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用,希望能帮助你了解 支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用。

   本篇咱们从零开发一个quarkus应用,支持虚拟线程响应web服务,响应式操作postgresql数据库,并且在quarkus官方还未支持的情况下,率先并将其制作成docker镜像

  
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

  本篇是《支持JDK19虚拟线程的web框架》系列的第二篇,前文咱们体验了有虚拟线程支持的web服务,经过测试,发现性能上它与其他两种常见web架构并无明显区别,既然如此,还有必要研究和学习吗?

  当然有必要,而且还要通过实战更深入了解虚拟线程与常规线程的区别,在各大框架和库广泛支持虚拟线程之前,打好理论和实践基础,这才是本系列的目标

  为了接下来的深入了解,咱们先在本篇打好基础:详细说明前文的web功能是如何开发出来的

  为了突出重点,这里先提前剧透,从编码的角度说清楚如何开启虚拟线程支持,其实非常简单,如下图,左侧是quarkus框架下的一个普通web服务,每收到一个web请求,是由线程池中的线程负责响应的,右侧的web服务多了个@RunOnVirtualThread注解,就变成了由新建的虚拟线程去处理web请求,没错,在quarkus框架下使用虚拟线程就是这么简单

  在前文中,我们通过返回值也看到了上述两个web服务中,负责web响应的线程的不同,如下所示,从线程名称上很容易看出线程池和虚拟线程的区别

  
看到这里,您可能会说:就这?一个注解就搞定的事情,你还要写一篇文章?这不是在浪费作者你自己和各位读者的时间吗?

  
确实,开启虚拟线程,编码只要一行,然而就目前而言,虚拟线程是JDK19专属,而且还只是预览功能,要想在实际运行的时候真正开启并不容易,需要从JDK、maven、IDE等方方面面都要做相关设置,而且如果要做成前文那样的docker镜像,一行docker run命令就能开启虚拟线程,还要在Dockerfile上做点事情(quarkus提供的基础镜像中没有JDK19版本,另外启动命令也要调整)

  
上述这些都是本文的重点,欣宸已经将这些梳理清楚了,接下来咱们一起实战吧,让前文体验过的web从无到有,再到顺利运行,达到预期

  
 

  开发电脑:MacBook Pro M1,macOS Monterey 12.6

  IDE:IntelliJ IDEA 2022.3 EAP (Ultimate Edition) (即未发布前的早期预览版)

  另外,M1芯片的电脑上开发和运行JDK19应用,与普通的X86相比感受不到任何变化,只有一点要注意:上传docker镜像到hub.docker.com时,镜像的系统架构是ARM的,这样的镜像在X86电脑上下载下来后不能运行

  下载JDK19

  下载jdk19,由于电脑是M1芯片,我选择的jdk是azul版本,地址是:https://www.azul.com/downloads/?package=jdk#download-openjdk

  使用azul的jdk和之前的oracle版本并无区别,至少在开发环境感受不到,来看下azul官方的说法

  实际上,azul的jdk很全面,x86芯片的各平台版本安装包都提供了,您可以根据自己电脑环境选择下载,下面是我选择的适合M1芯片的版本

  下载完成后双击安装即可

  修改maven的配置

  我这里使用的是本地maven,其对应的JDK也要改成19,修改方法是调整环境变量JAVA_HOME,令其指向JDK19目录(在我的电脑上,环境变量是在~/.zshrc里面)

  修改后令环境变量生效,然后执行一下命令确认已经使用了JDK19

  

➜ ~ mvn -version

 

  Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)

  Maven home: /Users/zhaoqin/software/apache-maven-3.8.5

  Java version: 19, vendor: Azul Systems, Inc., runtime: /Library/Java/JavaVirtualMachines/zulu-19.jdk/Contents/Home

  Default locale: zh_CN_#Hans, platform encoding: UTF-8

  OS name: "mac os x", version: "12.6", arch: "aarch64", family: "mac"

  

 

  创建Quarkus项目

  打开IDEA,新建项目,选择Quarkus项目

  接下来选择要用到的扩展包(其实就是在图形化页面添加jar依赖),这里的选择如下图:Reactive PostgreSQL client和RESTEasy Reactive Jackson

  点击上图右下角的Create按钮后项目开始创建,稍作等待,项目创建完成,如下图,此刻只能感慨:quarkus太贴心,不但有demo源码,还有各种版本的Dockerfile文件,而且git相关的配置也有,甚至README.md都写得那么详细,我是不是可以点击运行按钮直接把程序run起来了

  IDEA设置

  由于要用到JDK19,下面几项设置需要检查并确认

  首先是Project设置,如下图

  其次是Modules设置,先配置Sources这个tab页

  接下来是Dependencies这个tab页

  进入IDEA系统设置菜单

  如下图,三个位置需要设置

  设置完成了,接下来开始编码

  首先确认pom.xml,这是IDEA帮我们创建的,内容如下,有两处改动稍后会说到

  

 ?xml version="1.0"? 

 

   project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"

   xmlns="http://maven.apache.org/POM/4.0.0"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   modelVersion 4.0.0 /modelVersion

   groupId com.bolingcavalry /groupId

   artifactId quarkus-virual-threads-demo /artifactId

   version 1.0-SNAPSHOT /version

   properties

   compiler-plugin.version 3.8.1 /compiler-plugin.version

   maven.compiler.release 19 /maven.compiler.release

   maven.compiler.source 19 /maven.compiler.source

   maven.compiler.target 19 /maven.compiler.target

   project.build.sourceEncoding UTF-8 /project.build.sourceEncoding

   project.reporting.outputEncoding UTF-8 /project.reporting.outputEncoding

   quarkus.platform.artifact-id quarkus-bom /quarkus.platform.artifact-id

   quarkus.platform.group-id io.quarkus.platform /quarkus.platform.group-id

   quarkus.platform.version 2.13.2.Final /quarkus.platform.version

   skipITs true /skipITs

   surefire-plugin.version 3.0.0-M7 /surefire-plugin.version

   /properties

   dependencyManagement

   dependencies

   dependency

   groupId ${quarkus.platform.group-id} /groupId

   artifactId ${quarkus.platform.artifact-id} /artifactId

   version ${quarkus.platform.version} /version

   type pom /type

   scope import /scope

   /dependency

   /dependencies

   /dependencyManagement

   dependencies

   dependency

   groupId io.quarkus /groupId

   artifactId quarkus-resteasy-reactive-jackson /artifactId

   /dependency

   dependency

   groupId io.quarkus /groupId

   artifactId quarkus-reactive-pg-client /artifactId

   /dependency

   dependency

   groupId io.quarkus /groupId

   artifactId quarkus-arc /artifactId

   /dependency

   dependency

   groupId io.quarkus /groupId

   artifactId quarkus-resteasy-reactive /artifactId

   /dependency

   !-- 生成测试数据 --

   dependency

   groupId net.datafaker /groupId

   artifactId datafaker /artifactId

   version 1.6.0 /version

   /dependency

   dependency

   groupId io.quarkus /groupId

   artifactId quarkus-junit5 /artifactId

   scope test /scope

   /dependency

   dependency

   groupId io.rest-assured /groupId

   artifactId rest-assured /artifactId

   scope test /scope

   /dependency

   /dependencies

   build

   plugins

   plugin

   groupId ${quarkus.platform.group-id} /groupId

   artifactId quarkus-maven-plugin /artifactId

   version ${quarkus.platform.version} /version

   extensions true /extensions

   executions

   execution

   goals

   goal build /goal

   goal generate-code /goal

   goal generate-code-tests /goal

   /goals

   /execution

   /executions

   !-- 这里是新增的虚拟线程相关特性,start --

   configuration

   source 19 /source

   target 19 /target

   compilerArgs

   arg --enable-preview /arg

   /compilerArgs

   jvmArgs --enable-preview --add-opens java.base/java.lang=ALL-UNNAMED /jvmArgs

   /configuration

   !-- 这里是新增的虚拟线程相关特性,end --

   /plugin

   plugin

   artifactId maven-compiler-plugin /artifactId

   version ${compiler-plugin.version} /version

   configuration

   compilerArgs

   arg -parameters /arg

   /compilerArgs

   /configuration

   /plugin

   plugin

   artifactId maven-surefire-plugin /artifactId

   version ${surefire-plugin.version} /version

   configuration

   systemPropertyVariables

   java.util.logging.manager org.jboss.logmanager.LogManager /java.util.logging.manager

   maven.home ${maven.home} /maven.home

   /systemPropertyVariables

   /configuration

   /plugin

   plugin

   artifactId maven-failsafe-plugin /artifactId

   version ${surefire-plugin.version} /version

   executions

   execution

   goals

   goal integration-test /goal

   goal verify /goal

   /goals

   configuration

   systemPropertyVariables

   native.image.path ${project.build.directory}/${project.build.finalName}-runner

   /native.image.path

   java.util.logging.manager org.jboss.logmanager.LogManager /java.util.logging.manager

   maven.home ${maven.home} /maven.home

   /systemPropertyVariables

   /configuration

   /execution

   /executions

   /plugin

   /plugins

   /build

   profiles

   profile

   id native /id

   activation

   property

   name native /name

   /property

   /activation

   properties

   skipITs false /skipITs

   quarkus.package.type native /quarkus.package.type

   /properties

   /profile

   /profiles

   /project

  

 

  pom.xml的第一处改动如下图,要确保全部是19

  第二处改动,是在quarkus-maven-plugin插件中增加额外的配置参数,如下图红框

  接下来新增配置文件application.properties,在resources目录下

  

quarkus.datasource.db-kind=postgresql

 

  quarkus.datasource.jdbc.max-size=8

  quarkus.datasource.jdbc.min-size=2

  quarkus.datasource.username=quarkus

  quarkus.datasource.password=123456

  quarkus.datasource.reactive.url=postgresql://192.168.0.1:5432/quarkus_test

  

 

  开始写java代码了,首先是启动类VirtualThreadsDemoApp.java

  

package com.bolingcavalry;

 

  import io.quarkus.runtime.Quarkus;

  import io.quarkus.runtime.annotations.QuarkusMain;

  @QuarkusMain

  public class VirtualThreadsDemoApp {

   public static void main(String... args) {

   Quarkus.run(args);

  

 

  数据库对应的model类有两个,第一个是gender字段的枚举

  

package com.bolingcavalry.model;

 

  public enum Gender {

   MALE, FEMALE;

  

 

  表对应的实体类

  

package com.bolingcavalry.model;

 

  import io.vertx.mutiny.sqlclient.Row;

  public class Person {

   private Long id;

   private String name;

   private int age;

   private Gender gender;

   private Integer externalId;

   public String getThreadInfo() {

   return threadInfo;

   public void setThreadInfo(String threadInfo) {

   this.threadInfo = threadInfo;

   private String threadInfo;

   public Person() {

   public Person(Long id, String name, int age, Gender gender, Integer externalId) {

   this.id = id;

   this.name = name;

   this.age = age;

   this.gender = gender;

   this.externalId = externalId;

   this.threadInfo = Thread.currentThread().toString();

   public Long getId() {

   return id;

   public void setId(Long id) {

   this.id = id;

   public String getName() {

   return name;

   public void setName(String name) {

   this.name = name;

   public int getAge() {

   return age;

   public void setAge(int age) {

   this.age = age;

   public Gender getGender() {

   return gender;

   public void setGender(Gender gender) {

   this.gender = gender;

   public Integer getExternalId() {

   return externalId;

   public void setExternalId(Integer externalId) {

   this.externalId = externalId;

   public static Person from(Row row) {

   return new Person(

   row.getLong("id"),

   row.getString("name"),

   row.getInteger("age"),

   Gender.valueOf(row.getString("gender")),

   row.getInteger("external_id"));

  

 

  接下来是操作数据库的dao类,可见使用操作方式还是很原始的,还要在代码中手写SQL,取出也要逐个字段匹配,其实quarkus也支持JPA,只不过本篇使用的是响应式数据库驱动,所以选用的是Vert.x生成的连接池PgPool

  

package com.bolingcavalry.repository;

 

  import com.bolingcavalry.model.Person;

  import io.vertx.mutiny.pgclient.PgPool;

  import io.vertx.mutiny.sqlclient.Row;

  import io.vertx.mutiny.sqlclient.RowSet;

  import io.vertx.mutiny.sqlclient.Tuple;

  import javax.enterprise.context.ApplicationScoped;

  import javax.inject.Inject;

  import java.util.ArrayList;

  import java.util.List;

  @ApplicationScoped

  public class PersonRepositoryAsyncAwait {

   @Inject

   PgPool pgPool;

   public Person findById(Long id) {

   RowSet Row rowSet = pgPool

   .preparedQuery("SELECT id, name, age, gender, external_id FROM person WHERE id = $1")

   .executeAndAwait(Tuple.of(id));

   List Person persons = iterateAndCreate(rowSet);

   return persons.size() == 0 ? null : persons.get(0);

   private List Person iterateAndCreate(RowSet Row rowSet) {

   List Person persons = new ArrayList ();

   for (Row row : rowSet) {

   persons.add(Person.from(row));

   return persons;

  

 

  接下来就是前面截图看到的web服务类VTPersonResource.java,它被注解@RunOnVirtualThread修饰,表示收到web请求在虚拟线程中执行响应代码

  

package com.bolingcavalry.resource;

 

  import com.bolingcavalry.model.Person;

  import com.bolingcavalry.repository.PersonRepositoryAsyncAwait;

  import io.smallrye.common.annotation.RunOnVirtualThread;

  import javax.inject.Inject;

  import javax.ws.rs.GET;

  import javax.ws.rs.Path;

  import javax.ws.rs.PathParam;

  @Path("/vt/persons")

  @RunOnVirtualThread

  public class VTPersonResource {

   @Inject

   PersonRepositoryAsyncAwait personRepository;

   @GET

   @Path("/{id}")

   public Person getPersonById(@PathParam("id") Long id) {

   return personRepository.findById(id);

  

 

  最后是用于对比的常规web服务类PoolPersonResource.java,这个就是中规中矩的在线程池中取一个线程来执行响应代码

  

package com.bolingcavalry.resource;

 

  import com.bolingcavalry.model.Person;

  import com.bolingcavalry.repository.PersonRepositoryAsyncAwait;

  import io.smallrye.common.annotation.RunOnVirtualThread;

  import javax.inject.Inject;

  import javax.ws.rs.GET;

  import javax.ws.rs.Path;

  import javax.ws.rs.PathParam;

  @Path("/pool/persons")

  public class PoolPersonResource {

   @Inject

   PersonRepositoryAsyncAwait personRepository;

   @GET

   @Path("/{id}")

   public Person getPersonById(@PathParam("id") Long id) {

   return personRepository.findById(id);

  

 

  至此,编码完成

  IDEA启动设置

  编码完成后,在IDEA上启动应用做本地调试是咱们的基本操作,所以IDEA运行环境也要设置成支持JDK19的预览特性

  打开入口类,点击main方法前面的绿色箭头,在弹出的菜单上选择Modify Run Configuration

  在运行应用的设置页面,如下操作

  选中Add VM options

  填入下图箭头所指的内容

  终于,设置完成,接下来要启动应用了

  启动和验证

  启动应用之前,请确认postgresql数据库已启动,并且数据已经导入,具体启动和导入方法请参考前文

  点击下图红色箭头中指向的按钮,即可在IDEA中运行应用

  浏览器访问地址:http://localhost:8080/vt/persons/1 ,如下图,符合预期

  在前文中,咱们是在docker上运行应用的,另外在实际场景中应用运行在docker或者k8s环境也是普遍情况,所以接下来一起实战将用做成docker镜像并验证

  在创建工程的时候,IDEA就用quarkus模板自动创建了多个Dockerfile文件,下图红框中全是

  如果当前应用的JDK不是19,而是11或者17,那么上图红框中的Dockerfile文件就能直接使用了,然而,由于今天咱们应用的JDK必须是19,就无法使用这些Dockerfile了,必须自己写一个,原因很简单,打开Dockerfile.jvm,如下图红色箭头所示,基础镜像是jdk17,而这个仓库中并没有JDK19,也就是说quarkus还没有发布JDK19版本的基础镜像,咱们要自己找一个,另外,容器启动命令也要调整,需要加入--enable-preview才能开启JVM的虚拟线程

  自己写的Dockerile文件名为Dockerfile.19,内容如下,可见非常简单:先换基础镜像,再把mvn构建结果复制过去,最后加个启动命令就完事儿了(远不如官方的分层构建节省空间,然而在官方的JDK19镜像方案出来之前,先用下面这个将就着用吧)

  

FROM openjdk:19

 

  ENV LANGUAGE=en_US:en

  # 执行工作目录

  WORKDIR application

  COPY --chown=185 target/*.jar ./

  RUN mkdir config

  EXPOSE 8080

  USER 185

  ENTRYPOINT ["java", "-jar", "--enable-preview", "quarkus-virual-threads-demo-1.0-SNAPSHOT-runner.jar"]

  

 

  


docker build -f src/main/docker/Dockerfile.19 -t bolingcavalry/quarkus-virual-threads-demo:0.0.2 .

 

  

 

  镜像制作成功,控制台输出如下图

  如果您有hub.docker.com的账号,也可以像我一样推送到公共仓库,方便大家使用

  异常测试(没有enable-preview参数会怎么样?)

  回顾Dockerfile中启动应用的命令,由于虚拟线程是JDK19的预览功能,因此必须添加下图红色箭头所指的--enable-preview参数才能让虚拟线程功能生效

  于是我就在想:不加这个参数会咋样?也就是不开启虚拟线程,但是代码中却要用它,那么真正运行的时候会如何呢?

  瞎猜是没用的,还是试试吧,在启动参数中删除--enable-preview,如下图,再重新构建镜像

  像前文那样运行容器(再次提醒,确保数据库是正常的),再在浏览器访问http://localhost:8080/vt/persons/1,页面正常显示了,看来功能是不受影响的

  再用docker logs命令查看后台日志,如下图箭头所示,quarkus给出了WARN级别的提示:由于当前虚拟机不支持虚拟线程,改为使用默认的阻塞来执行业务逻辑

  小结:在不支持虚拟线程的环境强行使用虚拟线程,quarkus会选择兼容的方式继续完成任务

  小结和展望

  
至此,一个完整的quarkus应用已开发完成,该应用使用虚拟线程来响应web请求,而且在quarkus官方还没有提供方案的前提下,咱们依旧完成了docker镜像的制作,最后,因为好奇,还关闭重要参数尝试了一下,一系列操作下来,相信您已经对基础开发了如指掌了

  
虚拟线程和常规子线程的区别,究竟能不能看出来?前文已经验证了性能上区别不大,那还有别的方式来观察和区分吗?

  能不能稍微深入一点,仅凭一个@RunOnVirtualThread注解就强行写了两篇博客,实在是太忽悠人了

  
以上问题会在接下来的《支持JDK19虚拟线程的web框架,终篇》得到解决,还是那句熟悉的广告词:欣宸原创,不辜负您的期待

  以上就是支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用()的详细内容,想要了解更多 支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: