升级 JDK21、 Spring Boot 3.2 并开启 Virtual Thread、CRaC
背景
-
JDK21 已发布一段时间,是 JDK17 后的的又一个长期维护版本,支持了 Virtual Thread、CRaC 特性,并带来了新的分代 ZGC 算法
-
Spring Boot 3.2.1 (
Runtime efficiency with Spring (today and tomorrow) )版本发布后,框架层面原生的支持了 Virtual Thread、CRaC 特性
同时在 ops-job 上应用积累经验,可在其他项目如 Apollo 、xxljob 上继续落地
ps:本次升级项目原依赖是 JDK17,Spring Boot 2.6.5
关键结果(收益)
-
在不影响程序逻辑情况下,大幅缩短启动时间
-
内存使用降低,性能更好
-
CPU 资源使用率降低(因 GC 导致的 CPU 使用降低)
升级改动
Maven 依赖调整
调整 Spring Boot 的依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version> <relativePath/> <!-- lookup parent from repository --> </parent>
调整 Spring Cloud 、JDK 的依赖
<properties> <java.version>21</java.version> <spring-cloud.version>2021.0.1</spring-cloud.version> </properties>
代码兼容性改动
Spring Boot3 升级是个比较大的变动,有很多的不兼容性。这里只记录 ops-job 这个项目遇到的问题。更多问题参考:Spring Boot 3.0 Migration Guide (官网升级指南必看)
1、包名变动
javax 更名为 jakarta,相关的资源都需要改动。比如:
@PostConstruct 注解包名拜改变路径:javax.annotation.PostConstruct 变到 jakarta.annotation.PostConstruct
2、Spring 的 @Bean
注解只能用于有返回值的方法。
比如下面代码,想在 Bean 初始化生命周期中运行一些逻辑,升级后就不支持了。被 @Bean
注解的方法需要有返回值了
@Bean public void initSenTry() { String dsn = xx Sentry.init(options -> { options.setDsn(dsn); options.setEnvironment(env); }); SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(ctx); ThresholdFilter filter = new ThresholdFilter(); filter.setLevel(Level.ERROR.levelStr); filter.start(); sentryAppender.addFilter(filter); sentryAppender.start(); ctx.addTurboFilter(new TurboFilter() { @Override public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, Level level, String format, Object[] params, Throwable t) { logger.addAppender(sentryAppender); return FilterReply.NEUTRAL; } }); }
3、Spring 日志系统变化
slf4j 的 StaticLoggerBinder 类没有了,想要获取 LoggerContext 对象实现日志级别的动态调整,需要使用
LoggerFactory.getILoggerFactory 取而代之。比如:
private final LoggerContext ctx = (LoggerContext) LoggerFactory.getILoggerFactory();
4、Apollo 功能受限
Apollo 配置中心的 @ApolloConfig
注解失效了(内部配置加载逻辑是正常的,不影响应用启动)。比如:
@ApolloConfig private Config config;
从 Spring Boot 3 M5 开始,来自 spring.factories 文件的“org.springframework.boot.autoconfigure.EnableAutoConfiguration”自动配置注册不再起作用
基于此,预计有很多 start 都会受到影响,比如 mybatis-plus 、xxl-job
- 临时解决
在类路径下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,把 Apollo 的自动加载类放进去,如:
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration com.taptap.xxl.job.spring.XxlJobAutoConfiguration
其他有影响的框架,都可以复制下自动加载类,放到这个文件里
- 最终解决
可以尝试用最新版本,看官方是否解决了 Spring Boot3.x 的兼容性问题。
1、apollo-client : 升级到 2.1.0(apollo-client support spring boot 3.0 by nobodyiam · Pull Request #4 · apolloconfig/apollo-java )
2、xxl-job-spring-boot-starter : 更新到 2.3.2-183
问题记录
1、日志框架冲突
服务启动,会输出如下的日志
Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts
这个是 Spring Boot 的依赖冲突检查输出的,在 Maven 里排除 commons-logging.jar
就好了,如:
<dependency> <groupId>ru.yandex.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <version>0.3.2</version> <exclusions> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> </exclusions> </dependency>
2、Server VM warning
当加载 agent 时,会出现如下的警告日志。
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended [otel.javaagent 2024-01-23 17:38:45:581 +0800] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 1.12.1 OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
可通过 JVM 参数 -Xshare:off 关闭 CDS 相关的逻辑
大功告成
如果一切顺利,启动成功后会在控制台看到如下输出
开启 Virtual Thread
启用并验证
通过如下配置,可以一键开启 spring 的 Virtual Thread 特性。
spring.threads.virtual.enabled=true
验证是否开启了
@RequestMapping("/") @RestController public class VirtualController { @GetMapping("/test") public String virtual() { System.out.println(Thread.currentThread()); return "test"; } }
上面代码将会在控制台输出
VirtualThread[#70,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1
性能测试
环境:Redis 为本地的实例
@RequestMapping("/") @RestController public class VirtualController { final StringRedisTemplate redisTemplate; public VirtualController(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @GetMapping("/test") public String virtual() { redisTemplate.opsForValue().set("test", "test"); return redisTemplate.opsForValue().get("test"); } }
我在本地通过 wrk 压测上面的代码 (读写了下 Redis ),发现在不做任何参数调优的情况下,结果如下
tomcat 默认的线程池配置:
server.tomcat.threads.max=200 server.tomcat.threads.min-spare=10
-
wrk -t80 -c100 -d 10s --latency http://127.0.0.1:8040/test
-
wrk -t150 -c200 -d 10s --latency http://127.0.0.1:8040/test
从两次测试结果可以看出,在不做任何优化的前提下:
-
低负载(-t80 -c100):性能相当,差别不大
-
高负载(-t150 -c200):Virtual threads 依然保持高性能,Platform threads 出现了性能下降的问题。且两者 QPS 差距非常明显,Virtual threads 比 Platform threads 多 19%~29% 的性能。
在 Platform threads 模式下,尝试调大 tomcat 的线程参数
server.tomcat.threads.max=300 server.tomcat.threads.min-spare=50
分别使用
-
wrk -t150 -c200 -d 10s --latency http://127.0.0.1:8040/test
-
wrk -t80 -c100 -d 10s --latency http://127.0.0.1:8040/test
压 Platform threads 的服务。
从 tomcat 调参后的测试结果看,至少我本地这个环境,这个场景没法在通过加大 threads 数加大性能了。也在一次印证了 Platform threads 模式下,负载高过一个临界值后,性能会下降。
三方测试
-
三方测试参考:
虚拟线程原理及性能分析
开启 CRaC
说明(还不成熟)
CRaC 当前只是初步支持,有些场景:比如内存里维护了复杂状态的应用,可能会遇到问题,启用前请谨慎做好全场景的测试。
参考文档:Spring Boot Reference Documentation \
JVM Checkpoint Restore :: Spring Framework
启用并验证
集成步骤
-
1、JVM 层面(指定内存 dump 路径):启动时添加
-XX:CRaCCheckpointTo=PATH
参数,指定 CRaC 的输出加载路径 -
2、Spring 层面(找个合适的时机触发内存 dump):启动时添加
-Dspring.context.checkpoint=onRefresh
参数。该阶段启动时会自动创建检查点LifecycleProcessor.onRefresh
。此阶段完成后,所有非延迟初始化的单例都已实例化,并且InitializingBean#afterPropertiesSet
回调已被调用;但生命周期尚未开始,且ContextRefreshedEvent
尚未发布。 -
3、JVM层面(加载内存恢复状态):启动时添加
-XX:CRaCRestoreFrom=PATH
参数,指定加载的 CRaC 的路径
从集成步骤看,第 1、2 步应该都发生在 CI 阶段,且 Spring 的触发内存 dump 的意图很明显,等 onRefresh 完成后再出发,相当于初始 Bean 的时间就可以节省出来了。当然还可以使用 jcmd 指令触发 dump,比如:
jcmd target/example-spring-boot-0.0.1-SNAPSHOT.jar JDK.checkpoint
这个可以在任意时候触发,这就可以在 dump 前做完所有的预热逻辑,然后 dump 出来的状态就是性能峰值状态了
遇到的问题
1、JDK依赖问题
CRaC 特性依赖 JDK 特性支持,目前 openjdk 发行版只支持到 JDK17:Releases · CRaC/openjdk-builds 。如果在不支持的 JDK 下启用
CRaCCheckpointTo
,则会输出:
Unrecognized VM option 'CRaCCheckpointTo' Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.
CRaC 最早是 Azul 发起的一个项目,可以用 Azul 的社区发行版来验证 CRaC 特性,如:
docker image:azul/zulu-openjdk:21-jdk-crac
2、GC 算法问题
ZGC 算法下,不支持 CRaC,在 ZGC 启用时,会输出:
Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit. -XX:+UseZGC is currently unsupported for -XX:CRaCCheckpointTo.
为了验证 CRaC 功能,只好先移除 -XX:+UseZGC
3、打开的 FD & Socket 问题
CRaC 要求应用程序关闭所有打开的文件、网络连接等。在 Linux 上,这些内容表示为文件描述符。但是,可能很难更改应用程序以与检查点正确协调,例如,由于无法修改库中的代码。在这些情况下,CRaC 通过配置提供有限的处理。
理论上所有的资源都需要向 JVM 注册资源的 Checkpoint 前后的资源状态,Spring 内置的依赖都处理好了这一步,但是三方依赖,比如 Opentelemetry 、Apollo 等没有做这一步,就会出现一些异常。
- Opentelemetry 的问题
Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenFileException: FD fd=35 type=regular path=/tmp/opentelemetry-temp-jars2649059754140254392/jartqV3k80l.jar (deleted) at java.base/jdk.internal.crac.mirror.Core.translateJVMExceptions(Core.java:114) ~[na:na] at java.base/jdk.internal.crac.mirror.Core.checkpointRestore1(Core.java:188) ~[na:na] at java.base/jdk.internal.crac.mirror.Core.checkpointRestore(Core.java:286) ~[na:na] at java.base/jdk.internal.crac.mirror.Core.checkpointRestore(Core.java:265) ~[na:na] at jdk.crac/jdk.crac.Core.checkpointRestore(Core.java:72) ~[jdk.crac:na] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na] at org.crac.Core$Compat.checkpointRestore(Core.java:141) ~[crac-1.4.0.jar!/:na] ... 17 common frames omitted
- Apollo
Suppressed: java.nio.channels.IllegalSelectorException at java.base/sun.nio.ch.EPollSelectorImpl.beforeCheckpoint(EPollSelectorImpl.java:401) at java.base/jdk.internal.crac.mirror.impl.AbstractContext.invokeBeforeCheckpoint(AbstractContext.java:43) at java.base/jdk.internal.crac.mirror.impl.AbstractContext.beforeCheckpoint(AbstractContext.java:58) at java.base/jdk.internal.crac.mirror.impl.BlockingOrderedContext.beforeCheckpoint(BlockingOrderedContext.java:64) at java.base/jdk.internal.crac.mirror.impl.AbstractContext.invokeBeforeCheckpoint(AbstractContext.java:43) at java.base/jdk.internal.crac.mirror.impl.AbstractContext.beforeCheckpoint(AbstractContext.java:58) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore1(Core.java:153) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore(Core.java:286) at java.base/jdk.internal.crac.mirror.Core.checkpointRestoreInternal(Core.java:299) Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenSocketException: Socket[addr=apollo-config.dev.tapsvc.com/172.20.12.187,port=80,localport=47004] at java.base/jdk.internal.crac.JDKSocketResourceBase.lambda$beforeCheckpoint$0(JDKSocketResourceBase.java:68) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore1(Core.java:169) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore(Core.java:286) at java.base/jdk.internal.crac.mirror.Core.checkpointRestoreInternal(Core.java:299)
解决
可通过 File Descriptor Policies来有限的处理。
新建文件 crac.yaml ,配置内容如下:
type: socket localAddress: * action: ignore --- type: file path: /opt action: ignore --- type: file path: /tmp action: ignore --- type: pipe action: ignore
在 Java 应用启动系统参数里设置 -Djdk.crac.resource-policies = /{path}/crac.yaml
4、遗留的问题
遗留了一个问题,怎么都处理不了。刚好这个问题我认识的一个好友(Apollo 的作者)也遇到了,issue 如下:
Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenResourceException: FD fd=12 type=unknown path=anon_inode:[eventpoll] at java.base/jdk.internal.crac.mirror.Core.translateJVMExceptions(Core.java:117) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore1(Core.java:188) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore(Core.java:286) at java.base/jdk.internal.crac.mirror.Core.checkpointRestoreInternal(Core.java:299) Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenResourceException: FD fd=13 type=unknown path=anon_inode:[eventfd] at java.base/jdk.internal.crac.mirror.Core.translateJVMExceptions(Core.java:117) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore1(Core.java:188) at java.base/jdk.internal.crac.mirror.Core.checkpointRestore(Core.java:286) at java.base/jdk.internal.crac.mirror.Core.checkpointRestoreInternal(Core.java:299)
关于CRaC 的结论
基于如下:
-
Openjdk 发行版并未全部覆盖支持,目前支持的最高版本是 JDK17。更高版本的支持只能用 Azul 的 JDK
-
当前还有非常多的集成问题,且大量第三方包并没有做适配,Azul 官方给的 resource-policies 解决方案还属于初步阶段
-
Spring Boot 3.2 也是初步支持,Issue 区有大量集成的问题
综上,CRaC 距离生产可用还有很长的路要长,至少,当前解决应用启动问题,预热峰值问题,GraalVM 的 Native 方案比 CRaC 要更成熟。当然,未来哪个方向会成为标准还不好说,CRaC 的优势是保留了 JIT 的优化。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
GreatSQL 2023 年报
不知不觉 2023 年已经是过去式了,本文将从产品迭代、丰收收获、生态合作、社区活动 4 个方面带大家了解 GreatSQL 社区的 2023。 01 产品迭代 2023 年是发展的一年。在这一年里,GreatSQL 社区版发布了 3 个版本:8.0.25-17、8.0.32-24 以及 8.0.32-25。在最新发布的 8.0.32-25 版本中,GreatSQL 首次推出支持高性能的内存查询加速AP引擎,可将 GreatSQL 的数据分析性能提升几个数量级;同时大幅增加 Oracle 兼容特性,支持更多数据类型、SQL 语法、函数及存储过程等;支持异步删除 InnoDB 大表;支持在 MGR 只读节点绑定动态 VIP 以及主节点切换时主动断开当前连接,缩短应用端不可用时长。 此外,GreatSQL 8.0.32-25 基于 Percona Server for MySQL 8.0.32 版本,在 MySQL 8.0.32 基础上做了大量的改进和提升以及众多新特性。这其中包括线程池、审计、数据脱敏等 MySQL 企业版才有的特性,以及 performance_schema 提升、in...
- 下一篇
重庆警方破获“苹果ID贷”非法经营案,以“库克回租”为名非法放贷、暴力催收
据中新网报道,重庆巫溪县公安局获悉,该局成功侦破一起全国性“苹果ID贷”非法经营案,捣毁涉及21省(市)非法经营网络贷款犯罪团伙9个,抓获犯罪嫌疑人41人,涉及借款人员2万余名,涉案金额1.3亿元。 2023年5月,重庆巫溪县公安局凤凰派出所接到陶某报警称,自己的苹果手机(iPhone13)被人远程控制锁机,请求民警帮助。后经民警综合研判,一个以黄某为首的从事互联网非法放贷犯罪团伙逐渐浮出水面。 经查,自2022年8月以来,黄某等9人未经监管部门批准,以营利为目的,打着“库克回租”的幌子,通过平台投放“苹果ID贷”广告,招揽苹果手机用户并提供贷款,再通过远程控制手机应用,修改苹果 ID密码,威胁将手机锁机,并以拨打亲属电话骚扰、曝光个人信息等催收方式,让借款人超额还款。 该团伙累计非法放贷本金700余万元,非法获利100余万元。掌握相关证据后,专案组民警分赴陕西、四川等地,将该非法经营犯罪团伙成员全部抓获。 办案民警随后对该案扩线研判出另外8个涉及全国21省(市)与黄某团伙具有相同作案手法的非法经营犯罪团伙,梳理出借款人员2万余名。 巫溪县公安局在重庆市公安局经侦总队指导下,将该案报请...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8