性能加速包: SpringBoot 2.7&JDK 17,你敢尝一尝吗 | 京东物流技术团队
前言
众所周知,SpringBoot3.0迎来了全面支持JDK17的局面,且最低支持版本就是JDK17,这就意味着,Spring社区将完全抛弃JDK8,全面转战JDK17。作为JAVA开源生态里的扛把子,Spring可以说是整个JAVA生态的风向标,可以说,当Spring转战JDK17,会很快带领JAVA生态全面的跟进JDK17。而我本篇文章重点讲述Spring版本和JDK17升级中的实践整理。
为什么是Spring Boot 2.7
Spring Boot 3.0是全面放弃JDK8,而Spring社区当然不会把事情做的那么决绝,在推出3.0之前,Spring就开始着手布局JDK17升级。而2.7版本,就是Spring社区为了升级JDK17而推出的过渡版本,具体包括一以下几个方面的升级和改进:
总之,使用Spring Boot 2.7可以更好地利用JDK 17的特性,提高应用程序的性能和响应速度,同时还可以获得更好的兼容性和安全性。所以通过Spring Boot 2.7过渡升级JDK17,是一种更为温和方式,且遇到的兼容性问题最小。当然这不全是我自顾自说,Spring官方给出了这样的说明:
If you’re currently running with an earlier version of Spring Boot, we strongly recommend that you upgrade to Spring Boot 2.7 before migrating to Spring Boot 3.0.
为什么是JDK17
关于JDK17的新特性和优势,对于它支持的新语法和编程特性,我在此不再赘述,因为网上有很多文章介绍。
对于我落地JDK17的动力主要源于两个方面:一是更为安全的语言特性,二是更加优异的垃圾回收器和性能提升。
a. 安全的语言特性
JDK 17对反射进行了优化,主要表现在对反射调用进行了权限控制。具体来说,它通过setAccessible()方法启动或禁止访问安全检查开关。当参数值为true时,反射的对象在使用时取消安全检查,提高反射的效率;当参数值为false时,反射的对象执行安全检查。这样的优化使得在处理反射调用时,可以更加灵活地控制访问权限。
b. 垃圾收集器
JDK17引入了ZGC作为垃圾收集器,此处引用一下京东科技同事做的关于不同垃圾收集器在不同JDK版本下的压测结果:
压测服务背景:
DOS平台上选择了不同配置的机器(2C4G、4C8G、8C16G),并分别使用JDK8、JDK11和JDK17进行部署和压测。整个压测过程限时60分钟,用180个虚拟用户并发请求一个接口,每次接口请求都创建512Kb的数据。最终产出不同GC回收器的各项指标数据,来分析GC的性能提升效果。
以上的压测结果对于我们来说非常诱人,ZGC配合JDK17的性能对于其他JDK和垃圾收集器组合来说是碾压获胜,且不论任何机器配置下,都推荐使用ZGC,ZGC的停顿时间达到亚毫秒级,吞吐量也比较高。这个对于一个高并发业务场景下,对于资源的优化是非常客观的。
c. OpenJDK17下载地址
提供了一个下载地址: https://adoptium.net/zh-cn/temurin/releases/?version=17&os=linux&arch=x64
行云部署上的实践方案
a. Spring Boot 2.7
1. pom.xml版本依赖
实践的版本选择上我选择了2.7大版本下的最新小版本,即Spring Boot 2.7.17版本。pom.xml依赖如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jd.magnus</groupId> <artifactId>magnus-multi-ddd</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.17</version> </parent> <modules> <module>magnus-multi-ddd-adapter</module> <module>magnus-multi-ddd-application</module> <module>magnus-multi-ddd-domain</module> <module>magnus-multi-ddd-infrastructure</module> <module>magnus-multi-ddd-client</module> <module>magnus-multi-ddd-worker</module> </modules> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jsf-lite.version>1.0.0-HOTFIX-T2</jsf-lite.version> <ump.version>20221231.1</ump.version> </properties> </project>
2. 动态配置
Spring Boot 2.7对动态配置进行了更新。具体来说,Spring Boot 2.7更改了自动配置注册文件的路径和格式,从META-INF/spring.factories变更为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。
同时,Spring Boot 2.7还引入了新的注解@SpringBootApplication,该注解包含了@EnableAutoConfiguration和@ComponentScan等注解,使得配置更加简洁和方便。此外,Spring Boot 2.7还更新了一些自动配置的类和方法,以支持新版本的Spring Framework和Java。
SpringBootServletInitializer:在Spring Boot 2.7中,该类已经被移除,建议使用SpringBootServletWebServerApplicationContext来代替。 ServletWebServerFactoryCustomizer:这个接口已经从Spring Boot 2.7中移除,可以使用WebServerFactoryCustomizer来代替。 BasicErrorController:这个类已经从Spring Boot 2.7中移除,可以使用ErrorController接口来代替。 ContentNegotiationStrategy:这个接口已经从Spring Boot 2.7中移除,可以使用RequestMappingHandlerMapping的setContentTypeResolver(ContentTypeResolver)方法来代替。 HttpMessageConverters:这个接口已经从Spring Boot 2.7中移除,可以使用HttpMessageConvertingComparator来代替。 HttpMessageConvertingComparator:这个类已经从Spring Boot 2.7中移除,可以使用ComparatorChain来代替。 ServletWebServerFactoryCustomizerBeanPostProcessor:这个类已经从Spring Boot 2.7中移除。 SpringBootApplicationContextLoader:这个类已经从Spring Boot 2.7中移除,可以使用SpringApplicationWebApplicationContext来代替。 SpringBootServletInitializerAutoConfiguration:这个类已经从Spring Boot 2.7中移除,可以使用SpringBootServletWebServerApplicationContextAutoConfiguration来代替。
此外,还有一些被移除的配置属性,例如spring.http.converters.preferred-json-mapper、spring.jackson.serialization-features.default-pretty-print-xml、spring.jackson.serialization-features.sort-property-names-by-default等。其他信息大家可以看Spring的版本更新说明:
github上的release版本说明:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes
3. 单元测试升级
在Spring Boot 2.7版本,已经不再依赖JUnit4, 而是将Test换成了 JUnit Jupiter, 这也导致之前单元测试使用的方法和注解会产生变化。
常用的一些方法和注解变化如下:
变更项 | JUnit4 | JUnit Jupiter |
@Test注解 | 包路径: org.junit.Test | 包路径: org.junit.jupiter.api.Test |
断言 | 类:org.junit.Assert | 类:rg.junit.jupiter.api.Assertions ,提供了更简洁的断言方法 |
@RunWith | 需要使用@RunWith注解来指定测试运行器 | @RunWith移除,不再需要,单侧只需要@SpringBootTest即可 |
参数化接口测试 | @RunWith配合@Parameters实现参数化 | @ParameterizedTest和@ValueSource注解配合使用 |
4. hibernate-validator包依赖问题
Springboot从2.3以后,spring-boot-starter-web中不再引入hibernate-validator,需要手动引入。此处可以直接引用spring-boot-starter-validation的包,里面会间接引用hibernate-validator的包,且版本号可以被spring boot parent统一管理。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
4. 诊断升级兼容性方法
如果是老项目版本升级,Spring Boot 提供了一种在启动时分析应用程序环境并打印诊断信息的方法,而且还可以在运行时为您临时迁移属性。要启用该功能,请将以下依赖项添加到您的项目中:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-properties-migrator</artifactId> <scope>runtime</scope> </dependency>
b. 行云部署配置
1. 镜像配置
首先需要新的基础镜像包,包括OpenJDK17的。之前行云的镜像市场上是没有相关的镜像的,后来联系科技运维同事帮忙制作了新的镜像,新的镜像包是基于Tomcat应用类型的,大家可以在jdos的中国站的景象市场搜索到。镜像名如下,:
base_tomcat/java-jd-centos7-jdk17-tomcat8.5.42-ngx197:latest
2. 编译配置
编译配置中,需要选择的JDK版本为17,同时Maven的版本也可以尽量选高一些。因为按照惯例,maven的版本会对JDK的版本兼容性有所不同,一般越是高版本的Maven对JDK17兼容性更好。虽然官方没有明确说明Maven版本支持情况,但我们选择高版本的Maven是比较稳妥的选择,所以在JDOS上我们选择maven-3.9.0版本比较好。
3. JVM参数配置
然后就是配置JVM启动参数,我们需要开启ZGC.具体启动参数以4C8G的资源为例,配置参数如下:
-Xms5324m -Xmx5324m -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m -XX:MaxDirectMemorySize=983m -Djava.library.path=/usr/local/lib -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/Logs -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300 -XX:+UseZGC
此处需要注意,ZGC不要配置并行GC线程的数量,并发标记线程数等信息,配置了反而会出现启动报错情况。
c. 兼容性问题说明
关于兼容性问题之前一篇文章里详细介绍了,包括如何兼容京东的UMP, DUCC等。这些中间件的兼容性问题产生主要由于JDK17中对于反射和扫描的安全性检查导致的,一个简单的解决办法是将没开放的module强制对外开放。所以需要一些额外配置。
先整理结论,额外配置集合如下,该集合可以配置在VM 启动参数之中:
--add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.management/java.lang.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.net.util=ALL-UNNAMED
1. SGM依赖需要加入
--add-opens java.management/java.lang.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED
2. R2M需要加入
--add-opens java.base/java.time=ALL-UNNAMED
3. DUCC依赖需要加入
--add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED
4. AKS依赖需要加入
--add-exports java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED
5. Pfinder依赖需要加入
--add-opens java.base/sun.net.util=ALL-UNNAMED
6. Swagger兼容性配置
分两步:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
/** * 增加如下配置可解决Spring Boot 2.7.15 与Swagger 3.0.0 不兼容问题 **/ @Bean public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { return new BeanPostProcessor() { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); } return bean; } private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) { List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList()); mappings.clear(); mappings.addAll(copy); } @SuppressWarnings("unchecked") private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { try { Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); field.setAccessible(true); return (List<RequestMappingInfoHandlerMapping>) field.get(bean); } catch (IllegalArgumentException | IllegalAccessException e) { throw new IllegalStateException(e); } } }; }
7. JDK维度兼容性问题(只挑我遇到的问题重点说)
以下列举一下javafx.util下的一些常用工具类(项目中尽量不要再用):
类名 | 方法说明 |
javafx.util.Pair | getKey():获取 Pair 对象的键。 getValue():获取 Pair 对象的值。 setKey(K key):设置 Pair 对象的键。 setValue(V value):设置 Pair 对象的值。 |
avafx.util.Duration | toSeconds():将持续时间转换为秒。 toMillis():将持续时间转换为毫秒。 toNanos():将持续时间转换为纳秒。 add(Duration other):将另一个持续时间添加到当前持续时间。 subtract(Duration other):从当前持续时间中减去另一个持续时间。 |
javafx.util.converter | fromString(String value):将字符串值转换为目标类型。 toString(T value):将目标类型的值转换为字符串。 |
javafx.util.StringConverter | fromString(String value):将字符串值转换为目标类型。 toString(T value):将目标类型的值转换为字符串。 |
因此有一些包名路径变更,为了兼容JSF,需要手动引入一些JAR包。但由于我们部署环境采用的是外置的Tomcat8,所以还是包含java EE的相关包。不需要额外加入,但本地debug时,需要加入。
尽管 Jakarta EE 是 Java EE 的继任者,但为了保持向后兼容性,许多 Java EE 规范和 API 在 Jakarta EE 中仍然存在,并且在 Jakarta EE 中的命名空间从 javax 变为 jakarta。且一些命名空间也改变了,比如javax包下的方法和属性都不能再试用,例如: javax.xml.bind.*
更改为jakarta.xml.bind.*
。以下有一个该问题引起的JSF报错修复:
关于JSF启动有报错信息:运行时找不到 javax.xml.bind.JAXBException 类。在 JDK 9 及更高版本中,javax.xml.bind 包被移除了,并且不再包含在标准的 Java SE 中。 如果您的项目依赖于 JAXB API,您可以尝试以下解决方法之一: 如果您使用的是 JDK 8 或更早版本,请确保您的项目使用的是兼容的 JDK 版本。 如果您使用的是 JDK 9 或更高版本,并且需要使用 JAXB API,您可以添加以下依赖项来解决该问题: <dependency> <groupId>jakarta.xml.bind</groupId> <artifactId>jakarta.xml.bind-api</artifactId> <version>3.0.1</version> <!-- 根据您的需求选择合适的版本 --> </dependency>
以下贴一个报名改动对比图:
module | packages | replacement groupId | replacement artifactId |
java.activation | javax.activation | com.sun.activation | jakarta.activation |
java.xml.ws.annotation | java.annotation | jakarta.annotation | jakarta.annotation-api |
java.xml.bind | javax.xml.bind.* | jakarta.xml.bind.com.sun.xml.bind | jakarta.xml.bind-apijaxb-impl |
垃圾回收器的话,从JDK14开始,已经删除了CMS,所以在JDK17下,只建议使用ZGC。
还有一个最大的变化是之前的--illegal-access
参数不在可用,如果在java 17使用这个参数访问受限的api则会报出InaccessibleObjectException
,大多数情况下只要升级了依赖项是不会碰到这个情况的,但如果出现问题,则可以使用--add-opens
来对不可访问的api授权。以上的SGM,R2M,DUCC,AKS,Pfinder的兼容性问题都是因为这个特性变化引起的。
脚手架支持
目前最的DDD脚手架已经支持Spring Boot 2.7.17 和JDK17 ,下载脚本如下:
mvn archetype:generate \ -DarchetypeGroupId=com.jd.magnus \ -DarchetypeArtifactId=magnus-multi-ddd-archetype \ -DarchetypeVersion=1.0.0-SNAPSHOT \ -DinteractiveMode=false \ -DarchetypeCatalog=remote \ -Dversion=1.0.0-SNAPSHOT \ -DgroupId=com.jdl.sps \ -DartifactId=bff-demo1
该脚手架以在京东内部申请为开源项目,开源项目地址如下:
http://xingyun.jd.com/shendeng/openSource/detail/793
IDE配置注意事项
modules也需要更改Language level, 截图如下:
当本地debug时,需要配置debug的启动参数,配置VM options,并将上面【行云部署上的实践方案-c.兼容性问题说明】章节中所说的兼容性问题配置添加到该处。如下图所示:
此方法也适用于本地debug JUnit Test单元测试本地调试时用。当然,如果为了避免每个单元测试都要手动配置,可以点击图中的Edit configuration templates,配置模板,做到一劳永逸。具体配置如下图所示:
同理,在使用maven命令对工程进行test命令时,也需要额外配置启动参数,来兼容JDK17订单安全性检查,需要在pom文件中的maven-surefire-plugin增加如下配置:
总结
目前,将部门内的京旗API服务, 发货平台BFF服务,物流发货商家基础信息服务作为试点,已经在行云测试环境上用JDK17+Spring Boot2.7版本进行试运行,京东三方依赖包括JSF lite版本,ducc, easyJob,jmq, 云redis, ump, pfinder。经测试,兼容性没有太大问题,服务可用。后续还会进一步观察和测试。
据我了解,现在开源社区里,以apache为代表的大型开源项目都对JDK17有了不错的兼容, 未来可以逐步再从Spring Boot 2.7升级到Spring Boot 3.0。
作者:京东物流 赵勇萍
来源:京东物流 自猿其说 Tech 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
GaussDB SQL基础语法-变量&常量
一、前言 SQL是用于访问和处理数据库的标准计算机语言。GaussDB支持SQL标准(默认支持SQL2、SQL3和SQL4的主要特性)。 本系列将以《云数据库GaussDB—SQL参考》在线文档为主线进行介绍。 二、GaussDB数据库中的常量和变量的基本概述及语法定义 数据库中的变量和常量是两种重要的数据使用类型。变量是可以变化和被修改的,而常量则是固定不变,不能被修改的。 1、变量定义 在GaussDB中,变量是用于存储可变值的数据类型。变量通常在程序中定义,并在执行期间可以更改其值。在GaussDB中,可以使用以下语法来定义变量: DECLARE variable_name data_type; 其中,variable_name 是变量的名称,data_type 是变量的数据类型。例如,要定义一个名为 my_variable 的整数变量,可以使用以下语句: DECLARE my_variable INT; 2、常量定义 在GaussDB中,常量用于存储固定值的数据类型。常量在程序中定义后,其值不能被修改。在GaussDB中,可以使用以下语法来定义常量: DECLARE const...
- 下一篇
Kafka 分级存储在腾讯云的实践与演进
导语 腾讯云消息队列Kafka内核负责人鲁仕林为大家带来了《Kafka分级存储在腾讯云的实践与演进》的精彩分享,从Kafka架构遇到的问题与挑战、Kafka弹性架构方案类比、Kafka分级存储架构及原理以及腾讯云的落地与实践四个方面详细分享了Kafka分级存储在腾讯云的实践与演进。 Kafka架构遇到的问题与挑战 Kafka架构 上图是Kafka目前本身的架构。腾讯云在线上环境部署Kafka集群的时候,都是基于Zookeeper或者Kraft作为元数据存储,然后使用物理机或者VM作为计算资源,本地磁盘作为存储介质来构建集群。 但这种部署模式有以下几个问题: 1. 本地状态比较重,因为数据都是存在本地的,任何的运维操作都需要进行数据搬迁,运维复杂度较高。 2. 这种部署模式,资源是Broker维度,所以在进行线上运维的时候,扩缩容都是以Broker节点维度进行,但是节点维度的资源分为CPU、带宽、磁盘等,直接以Broker节点维度处理会造成资源浪费。 3. 在线上服务过程中,会有故障恢复或者大量历史数据处理等场景,处理历史数据会有历史数据回溯的问题,造成pagecache的污染,会影响集...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- Linux系统CentOS6、CentOS7手动修改IP地址
- 2048小游戏-低调大师作品
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案