本文主要记载自己的担任的微服务集群从JDK8晋级到JDK17过程中遇到过的一些问题。

SpringBoot3+的改变

因为原先的项目是采用的SpringBoot容器去进行运作的,随着JDK版别的提升,SpringBoot也要晋级为了3+的版别,以下是我晋级后的项目所运用的SpringBoot父依靠:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.4</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

包地址的改变

Spring Boot 3.0 最低要求 Java 17,并向上兼容支持 Java 19。

所以,假如你想晋级 Spring Boot 3.0,请保证你的 JDK 版别是否符合要求,毕竟现在大部分人仍是用的 Java 8,晋级 JDK 版别不是一件小事,虽然现在 Java 17+ 是免费运用的,但不确定哪个时间点会收费,也能够转战 OpenJDK 或者其他开源的 JDK 版别。

别的,Spring Boot 3.0 已将一切底层依靠项从 Java EE 搬迁到了 Jakarta EE API,根据 Jakarta EE 9 并尽可能地兼容 Jakarta EE 10。因为早在几年前 Java EE 现已正式更名为 Jakarta,所以,一切相关的称号都变了,包括包名,所以运用了 Java EE 的运用改动也不小。

干货!微服务全量升级JDK17,踩坑笔记整理

spi文件的改变

SpringBoot3之后的spi文件改变为了以下文件名:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

可是文件内部的spi编写方法和早期版别的spring.factories编写方法基本相同

干货!微服务全量升级JDK17,踩坑笔记整理

更多关于SpringBoot3之后的改变,能够参阅以下文档: github.com/spring-proj…

Dubbo的改变

因为JDK版别发生了改变,因为许多结构的版别也要做提升,这儿Dubbo我所运用的版别如下:

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.2.0-beta.3</version>
</dependency>

SpirngCloudeAlibaba的版别晋级

关于SpringCloudeAlibaba的版别晋级改变如下:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2022.0.0.0-RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

基本上SpringCloudAlibaba引进的版别正常之后,其他相关的依靠如Nacos,就能够直接引进了:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

Nacos的晋级

因为咱们的SpringCloudeAlibaba做了调整,所以它对接运用的Nacos版别也最好做下同步晋级,这儿我推荐运用Nacos2.2.1版别,自己在运用它的时分还算正常。

ShardingJdbc版别设计

关于ShardingJDBC的版别晋级,这个最为费劲,官网文档上也没有很明确的说明解说,下边这个版别和装备文件是自己踩了许多坑之后才得到的一份成果。 首要ShardingJDBC需求兼容JDK17的话,需求引进以下内容,要注意,这儿引进的依靠坐标和4.x版别的坐标名字是不一样的,这一点必定要注意。

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>5.3.2</version>
</dependency>

别的一旦ShardingJDBC晋级到了5.x版别之后,它的装备文件规矩也会发生改变: 需求以一个类似驱动包的方法去指定sharding-jdbc的详细装备:例如下图

干货!微服务全量升级JDK17,踩坑笔记整理

这段装备截图中的url特点里边有一个qiyu-live-db-sharding.yaml文件名,这份文件才是ShardingJDBC的详细内容,在这份详细装备中能够去编写关于数据库操作的一些内容:

干货!微服务全量升级JDK17,踩坑笔记整理

这儿我将自己的一份装备文件贴出来给咱们参阅,防止咱们踩坑:


dataSources:
  user_master:  ##新表,重建的分表
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://cloud.db:8808/qiyu_live_user?useUnicode=true&characterEncoding=utf8
    username: root
    password: root
  user_slave0:  ##新表,重建的分表
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://cloud.db:8809/qiyu_live_user?useUnicode=true&characterEncoding=utf8
    username: root
    password: root
rules:
    - !READWRITE_SPLITTING
      dataSources:
        user_ds:
         staticStrategy:
           writeDataSourceName: user_master
           readDataSourceNames:
              - user_slave0
    - !SINGLE
      defaultDataSource: user_ds ## 不分表分分库的默许数据源
    - !SHARDING
      tables:
        t_user:
          actualDataNodes: user_ds.t_user_${(0..99).collect(){it.toString().padLeft(2,'0')}}
          tableStrategy:
            standard:
              shardingColumn: user_id
              shardingAlgorithmName: t_user-inline
        t_user_tag:
          actualDataNodes: user_ds.t_user_tag_${(0..99).collect(){it.toString().padLeft(2,'0')}}
          tableStrategy:
            standard:
              shardingColumn: user_id
              shardingAlgorithmName: t_user_tag-inline
      shardingAlgorithms:
        t_user-inline:
          type: INLINE
          props:
            algorithm-expression: t_user_${(user_id % 100).toString().padLeft(2,'0')}
        t_user_tag-inline:
          type: INLINE
          props:
            algorithm-expression: t_user_tag_${(user_id % 100).toString().padLeft(2,'0')}
props:
  sql-show: true
  max-connections-size-per-query: 3

ShardingJdbc引进Nacos装备中心

在5.3.x的版别中,ShardingJdbc是没有开发接入Nacos完成装备化的功用的,这块需求进行二次开发才能够进行完成。这儿给咱们一个思路,咱们能够去看下ShardingJDBC的以下接口: org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider ShardingJdbc底层是根据这个接口去界说了装备的读取规矩,咱们能够根据这个接口做一个自界说的扩展类,然后经过ShardingJdbc的spi文件将它注入到咱们的运用上下文中,从而完成Nacos读取ShardingJdbc装备的功用。

下边是一段我自己进行二次开发之后的读取类,供咱们参阅


import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.utils.StringUtils;
import org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
/**
 * @Author idea
 * @Date: Created in 20:51 2023/6/4
 * @Description
 */
public class NacosDriverURLProvider implements ShardingSphereDriverURLProvider {
    private static Logger logger = LoggerFactory.getLogger(NacosDriverURLProvider.class);
    private static final String NACOS_TYPE = "nacos:";
    private static final String GROUP = "DEFAULT_GROUP";
    /**
     * @param url the driver url
     * (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test)
     * @return
     */
    @Override
    public boolean accept(String url) {
        return url != null && url.contains(NACOS_TYPE);
    }
    /**
     * 从url中获取到nacos的衔接装备信息
     *
     * @param url (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test)
     * @return
     */
    @Override
    public byte[] getContent(final String url) {
        if (StringUtils.isEmpty(url)) {
            return null;
        }
        //得到例如:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test 格局的url
        String nacosUrl = url.substring(url.lastIndexOf(NACOS_TYPE) + NACOS_TYPE.length());
        /**
         * 得到三个字符串,分别是:
         * qiyu.nacos.com
         * 8848
         * qiyu-live-user-shardingjdbc.yaml
         */
        String nacosStr[] = nacosUrl.split(":");
        String nacosFileStr = nacosStr[2];
        /**
         * 得到两个字符串
         * qiyu-live-user-shardingjdbc.yaml
         * username=qiyu&&password=qiyu&&namespace=qiyu-live-test
         */
        String nacosFileProp[] = nacosFileStr.split("\?");
        String dataId = nacosFileProp[0];
        String acceptProp[] = nacosFileProp[1].split("&&");
        //这儿获取到
        Properties properties = new Properties();
        properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosStr[0] + ":" + nacosStr[1]);
        for (String propertyName : acceptProp) {
            String[] propertyItem = propertyName.split("=");
            String key = propertyItem[0];
            String value = propertyItem[1];
            if ("username".equals(key)) {
                properties.setProperty(PropertyKeyConst.USERNAME, value);
            } else if ("password".equals(key)) {
                properties.setProperty(PropertyKeyConst.PASSWORD, value);
            } else if ("namespace".equals(key)) {
                properties.setProperty(PropertyKeyConst.NAMESPACE, value);
            }
        }
        ConfigService configService = null;
        try {
            configService = NacosFactory.createConfigService(properties);
            String content = configService.getConfig(dataId, GROUP, 6000);
            logger.info(content);
            return content.getBytes();
        } catch (NacosException e) {
            throw new RuntimeException(e);
        }
    }
}

最终记得引进满意ShardingJdbc的spi装备文件:

干货!微服务全量升级JDK17,踩坑笔记整理

关于引进ShardingJdbc+HikariDataSource衔接池后无法自发动初始化数据源问题

这个问题是出现在,我引进了ShardingJdbc+HikariDataSource配合运用的数据库衔接时功用之后才发现的,发现当服务发动的时分,ShardingJdbc的衔接池没有进行初始化,这样会导致假如请求量忽然上来的话,衔接建立过慢,从而导致功能跟不上。 这儿我也在网上查了许多材料,后来在github上看到了相关的issue提示,说是一个官方的bug,不过能够经过以下方法进行弥补:(issue地址:github.com/spring-proj…)

@Configuration
public class ShardingJdbcDatasourceAutoInitConnectionConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(ShardingJdbcDatasourceAutoInitConnectionConfig.class);
    @Bean
    public ApplicationRunner runner(DataSource dataSource) {
        return args -> {
            LOGGER.info("dataSource: {}", dataSource);
            //手动触发下衔接池的衔接创建
            Connection connection = dataSource.getConnection();
        };
    }
}

在服务发动的初始化阶段中,触发下getConnection的操作即可。

关于MyBatis-Plus版别的晋级

因为晋级大了 JDK17,所以连咱们的ORM结构MyBatis-Plus也是需求进行调整的。详细只需求晋级下Maven依靠即可:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>

Java反射机制的调整

相信刚入坑JDK17的朋友,大多数都会在服务运行的时分看到这么一条提示:

java.base does not “opens java.util“ to unnamed module

经过反射访问JDK模块内类的私有方法或特点,且当时模块并未敞开指定类用于反射访问,就会出现以上告警。处理方法也必须运用模块化相关知识,能够运用遵从模块化之间的访问规矩,也能够经过设置 –add-opens java.base/java.lang = ALL-UNNNAMED 损坏模块的封装性方法暂时处理;