欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):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

  • 实际上,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中运行应用

  • 在前文中,咱们是在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已在运行

  • 首先是常规maven编译打包(uber-jar表示生成的jar中包含了所有依赖库)

mvn clean package -U -DskipTests -Dquarkus.package.type=uber-jar
  • 构建docker镜像
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镜像的制作,最后,因为好奇,还关闭重要参数尝试了一下,一系列操作下来,相信您已经对基础开发了如指掌了

  • 最后,还剩下两个遗留问题,相信您也会有类似困惑

    1. 虚拟线程和常规子线程的区别,究竟能不能看出来?前文已经验证了性能上区别不大,那还有别的方式来观察和区分吗?
    2. 能不能稍微深入一点,仅凭一个@RunOnVirtualThread注解就强行写了两篇博客,实在是太忽悠人了
  • 以上问题会在接下来的《支持JDK19虚拟线程的web框架,终篇》得到解决,还是那句熟悉的广告词:欣宸原创,不辜负您的期待

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用的更多相关文章

  1. 支持JDK19虚拟线程的web框架之四:看源码,了解quarkus如何支持虚拟线程

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 前文链接 支持JDK19虚拟线程的web框架,之一:体 ...

  2. 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...

  3. 支持JDK19虚拟线程的web框架,之一:体验

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于虚拟线程 随着JDK19 GA版本的发布,虚拟线程 ...

  4. python 开发一个支持多用户在线的FTP

    ### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用 ...

  5. Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现

    作业: 开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...

  6. dotweb——go语言的一个微型web框架(二)启动dotweb

    以上的代码截图表示启动一个dotweb服务,在浏览器里输入127.0.0.1:8080,将会得到一个"index"的页面. app := dotweb.New() dotweb.N ...

  7. Python学习 - 编写一个简单的web框架(二)

    在上一篇日志中已经讨论和实现了根据url执行相应应用,在我阅读了bottle.py官方文档后,按照bottle的设计重写一遍,主要借鉴大牛们的设计思想. 一个bottle.py的简单实例 来看看bot ...

  8. web框架-(二)Django基础

    上面我们已经知道Python的WEB框架有Django.Tornado.Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM.模型绑定.模板引擎.缓存.Sessi ...

  9. 如何快速开发一个支持高效、高并发的分布式ID生成器

    ID生成器是指能产生不重复ID服务的程序,在后台开发过程中,尤其是分布式服务.微服务程序开发过程中,经常会用到,例如,为用户的每个请求产生一个唯一ID.为每个消息产生一个ID等等,ID生成器也是进行无 ...

随机推荐

  1. C++ 文件hash值 BT种子的hash值

    这个两个东东,是我在网上找到的.小小的修改了一下方便大家使用. 一个是 获取文件哈希值的,另外一个是获取torrent文件磁力链接的哈希值. 整理好的类下载地址: 文件hash值: http://pa ...

  2. Excelize 2.4.0 正式版发布, 新增 152 项公式函数支持

    Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准.可以使用它来读取.写入由 Microsoft Exc ...

  3. BeanUtils.copyProperties的使用方法

    BeanUtils.copyProperties的使用方法 1.使用的是springframe包下的,BeanUtils.copyProperties(a,b) 把a属性拷贝给b属性 2.注意事项: ...

  4. 拥挤的奶牛题解---队列优化DP---DD(XYX)​​​​​​​的博客

    拥挤的奶牛 时间限制: 1 Sec  内存限制: 128 MB 题目描述 FJ的n头奶牛(1<=n<=50000)在被放养在一维的牧场.第i头奶牛站在位置x(i),并且x(i)处有一个高度 ...

  5. ipv6临时地址

    关闭ipv6临时地址 win+r输入cmd进入后输入代码 netsh interface ipv6 setprivacy state=disable 控制面板中找到网卡禁用后在启用 win+r输入cm ...

  6. 解决CDH 访问权限问题

    CDH 6.2 安装好以后,直接使用root 或者 其他账号执行spark-shell 会报权限错误 22/01/04 17:46:28 ERROR spark.SparkContext: Error ...

  7. .NET 反向代理-YARP

    什么是 YARP YARP (另一个反向代理) 设计为一个库,提供核心代理功能,你可以根据应用程序的特定需求进行自定义. YARP 是使用 .NET的基础架构构建在 .NET上的.YARP 的主要不同 ...

  8. HBase原理深入

    HBase 读写数据流程 Hbase 读数据流程 首先从 zk 找到 meta 表的 region 位置,然后读取 meta 表中的数据,meta 表中存储了用户表的 region 信息 根据要查询的 ...

  9. Scrum五大会议要怎么开?

    在Scrum框架中,我们对Scrum的五个会议一定都不陌生,但如何组织这五个会议,才能让Scrum团队真正积极.主动地参与进项目管理中呢?接下来我们会以一个Sprint为周期,详细介绍一下Sprint ...

  10. MinIO Docker 快速入门

    官方文档地址:http://docs.minio.org.cn/docs/master/minio-docker-quickstart-guide 在Docker中运行MinIO单点模式 MinIO ...