性能文章>如何用Spring Boot打造一个高性能的电商平台:搭建过程、踩坑经历及实践经验分享>

如何用Spring Boot打造一个高性能的电商平台:搭建过程、踩坑经历及实践经验分享原创

1年前
425056

背景:
如何避免单点风险:基于实践经验分享服务拆分原则的一些思考

01

新项目需要的能力

02

如何拆出热点服务?

2.1 方案

方案1:删除项目中不需要的代码
方案2:搭建一个全新的项目

2.2 决策

使用了方案2。
原因有2:
1、老项目文件数较多。类数量46358,删除也花时间;不删耗资源、影响性能

2、老项目维护成本高。package层次划分不清晰、技术实现老旧

 

03

搭建新项目Step by Step

基于IDE创建项目

坑1:类文件具有错误的版本 61.0, 应为 52.0


jdk1.8+SpringBoot3.X时的报错

错误的类文件: /Users/cheng.tang/.m2/repository/org/springframework/boot/spring-boot/3.0.6/spring-boot-3.0.6.jar!/org/springframework/boot/SpringApplication.class     
类文件具有错误的版本 61.0, 应为 52.0     
请删除该文件或确保该文件位于正确的类路径子目录中。
JDK1.8+SpringBoot3.X撞车现场

✪ JDK version和class file version(Class编译版本号)的关系

JDK 19 = 63,
JDK 18 = 62,
JDK 17 = 61,
JDK 16 = 60
JDK 15 = 59,
JDK 14 = 58,
JDK 13 = 57,
JDK 12 = 56,
JDK 11 = 55,
JDK 10 = 54,
JDK 9 = 53,
JDK 8 = 52,
JDK 7 = 51,
JDK 6.0 = 50,
JDK 5.0 = 49,
JDK 1.4 = 48,
JDK 1.3 = 47,
JDK 1.2 = 46,
JDK 1.1 = 45.0-45.6
JDK version和class file version(Class编译版本号)对应关系

解决办法:IDEA中的JDK版本全部更新为1.8

1、检查SDK的配置


查看路径:在项目上点右键-->Open Module Settings【F4】-->Project

2、检查IDEA中对JDK的配置


查看路径:在项目上点右键-->Open Module Settings【F4】-->Modules

3、检查Java Compiler中bytecode的配置


查看路径:
IDEA菜单栏-->Preferences...【Command+,】-->
Build,Execution,Deployment-->Compiler-->Java Compilier

4、Java项目中指定源代码和Java字节码的版本
Maven,在pom.xml中指定

Gradle,在build.gradle中指定

 

坑2:Application failed to connect to Nacos server: ""

错误信息:

Exception encountered during context initialization - cancelling refresh attempt: 
org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'nacosContextRefresher' defined in class path resource 
[com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.class]: 
Bean instantiation via factory method failed; 
nested exception is org.springframework.beans.BeanInstantiationException: 
Failed to instantiate [com.alibaba.cloud.nacos.refresh.NacosContextRefresher]: 
Factory method 'nacosContextRefresher' threw exception; 
nested exception is com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureException: 
java.lang.reflect.InvocationTargetException

原因是缺少了依赖,因为java.lang.reflect.InvocationTargetException大多是这个原因。

✪ 解决办法:添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
✪ 原因 剖析:当前项目不是 spring-cloud-alibaba应用
出现这个错误的原因是项目不是spring-cloud-alibaba应用
需要特殊处理:添加依赖spring-cloud-starter-bootstrap

坑3:Spring Boot [2.7.11] is not compatible with this Spring Cloud release train

错误信息:Spring Boot版本与Spring Cloud版本不兼容
***************************
APPLICATION FAILED TO START
***************************

Description:

Your project setup is incompatible with our requirements due to following reasons:

- Spring Boot [2.7.11] is not compatible with this Spring Cloud release train

Action:
Consider applying the following actions:

- Change Spring Boot version to one of the following versions [2.4.x, 2.5.x] .
You can find the latest Spring Boot versions here [https://spring.io/projects/spring-boot#learn]. 
If you want to learn more about the Spring Cloud Release train compatibility, 
you can visit this page 
[https://spring.io/projects/spring-cloud#overview] 
and check the [Release Trains] section.
If you want to disable this check, just set the property 
[spring.cloud.compatibility-verifier.enabled=false]

Process finished with exit code 1

解决办法1:根据【Release Trains】来找匹配的版本

按提示信息来"check the [Release Trains] section."


SpringBoot 2.7.X匹配的SpringCloud版本是2021.0.3+

上面的报错,是因为当前项目中使用的SpringCloud2020.0.3与SpringBoot 2.7.X不匹配

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2020.0.3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

解决办法:把2020.0.3升级为2021.0.3+

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2021.0.6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

新项目最终使用SpringCloud2021.0.6

补充:spring-cloud-dependencies当前的版本


https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies

 

✪ 解决办法2:https://start.spring.io/生成的示例中找

使用https://start.spring.io/生成一个demo项目,然后查看SpringCloud项目的版本

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.11</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demo</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2021.0.6</spring-cloud.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

spring initializr"Generate"的demo项目中的pom.xml
可以看到使用的Spring Cloud版本是2021.0.6

解决办法:把SpringCloud版本从2020.0.3升级到2021.0.6
补充:使用IDEA新建项目时,如果引入SpringCloud也会自动导入合适的SpringCloud版本。

坑4:项目无法启动。也没有报错


卡在这了:原因是数据库连接配错了。配的是内网地址,但本地与数据库网络不通
解决办法:将数据库连接更改为本地网络相通的外网地址
补充:mysql驱动坐标(GAV)的变化

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

Spring Boot 2.7.11时默认使用这个mysql-connector-j

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

之前的版本使用的这个驱动mysql-connector-java

mysql-connector-j 和 mysql-connector-java 都是MySQL官方提供的Java连接器,
用于在Java应用程序中连接和操作MySQL数据库。
它们之间没有本质区别,只是命名方式不同。
在早期版本的MySQL中,Java连接器的名称为 mysql-connector-java。
随着时间的推移,MySQL的开发者改变了Java连接器的名称,
从而产生了新名称 mysql-connector-j。

因此,当你在使用MySQL时,这两个名称可以互相替换。
但是需要注意的是,不同版本的驱动之间可能存在一些细微的差异,
所以在使用时应该根据具体情况选择合适的版本。
mysql-connector-j与mysql-connector-java的区别
当前的MySQL Connector/J版本可以在MySQL官方网站(https://dev.mysql.com/downloads/connector/j/)上找到。
以下是一些当前可用的版本:
8.0.27
8.0.26
8.0.25
8.0.24
8.0.23
8.0.22
每个版本都有 .zip 和 .tar.gz 文件可供下载, 以适应不同的操作系统和环境。
根据你的需要选择合适的版本即可。
ChatGPT-3.5

坑5:.properties文件中的“中文乱码”

✪ 解决办法

变更的配置项
查看路径:菜单栏IDEA-->Preferences...【Command+,】-->Editor-->File Encodings


最终的配置

坑6:Lettuce第一次执行redis命令时耗时较长

 

这个问题还没解决,目前可以使用预热的临时方案解决。先埋个坑

数据库连接池经典实践

druid 1.2.17

Druid基于MySQL的一个经典配置:

这些是Spring Boot应用程序中用于配置Druid连接池的属性。

#是否异步初始化数据源,默认值为false。
#initial-size值比较大时设置为true时,可以加快启动速度
#druid 1.1.4+生效
spring.datasource.druid.async-init=true

#初始化时建立物理连接的个数,默认值为0。
#com.alibaba.druid.pool.DruidAbstractDataSource#DEFAULT_INITIAL_SIZE=0
spring.datasource.druid.initial-size=2

#最小空闲连接数,默认为0
#com.alibaba.druid.pool.DruidAbstractDataSource#DEFAULT_MIN_IDLE=0
#这个值可以大于cpuCoreSize*2,
#大于等于数据库QPS
spring.datasource.druid.min-idle=20

#这个值可以大于cpuCoreSize*2,大于等于数据库的QPS*2
#如果有慢SQL,这个值要大于数据库最大慢SQL耗时的秒数*数据库QPS*2
#最大活动连接数,默认8
#com.alibaba.druid.pool.DruidAbstractDataSource#DEFAULT_MAX_ACTIVE_SIZE=8
spring.datasource.druid.max-active=500
#连接在池中保持空闲而不被删除之前的最小时间,以毫秒为单位,默认值为1min。
#com.alibaba.druid.pool.DruidAbstractDataSource#DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS=60 * 1000L
spring.datasource.druid.time-between-eviction-runs-millis=60000

#连接在池中保持空闲而不被删除的最大时间,以毫秒为单位,默认值为30min。
#com.alibaba.druid.pool.DruidAbstractDataSource#DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS=1000L * 60L * 30L
spring.datasource.druid.min-evictable-idle-time-millis=1000L * 60L * 30L

#连接在池中保持空闲而不被删除之前的最大时间,以毫秒为单位,默认值为7h
#com.alibaba.druid.pool.DruidAbstractDataSource#DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS=1000L * 60L * 60L * 7;
spring.datasource.druid.max-evictable-idle-time-millis=1000L * 60L * 60L * 7

#是否开启tcp心跳检测,默认值为false。
#开启idle线程收回时,如果keep-alive=false在空闲时可用线程数会回收到0
#keepAliveBetweenTimeMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS * 2;
spring.datasource.druid.keep-alive=true

#获取连接时的最大等待时间,单位为毫秒,
#默认值为-1。最大的等待时间为无限制,即如果当前没有可用的数据库连接,
#那么请求将一直被阻塞,直到有可用的数据库连接。
#对于大多数应用程序来说,将max-wait设置为较小的值(例如30秒)可能已经足够满足其需求
#譬如此处配置60s
spring.datasource.druid.max-wait=60000

#缓存PreparedStatement,默认值为false。
#是否缓存preparedStatement,也就是PSCache。
#PSCache对支持游标的数据库性能提升巨大,比如说oracle。
#在mysql下建议关闭。default=false
spring.datasource.druid.pool-prepared-statements=false

以上这些属性可以满足大多数连接池的需求。
可以根据自己的需求配置这些属性,以便在使用连接池时获得更好的性能和稳定性
✪ 数据库连接创建、应用获取连接的配置
spring.datasource.druid.async-init=2
spring.datasource.druid.min-idle=20
spring.datasource.druid.max-active=500
spring.datasource.druid.max-wait=60000
上面4行配置:
1、在连接池初始化时,会预先创建2个数据库连接。
2、当数据库的请求每秒达到20次(qps=20)时,连接池会创建新的数据库连接,直到达到最小空闲连接数(min-idle),在此配置中为20。
3、当数据库请求qps超过20后,如果当前没有可用的数据库连接,则会创建新的数据库连接,直到连接池达到最大连接数(max-active),在此配置中为500。
4、如果仍需要获取数据库连接且连接池已满,则请求会被阻塞等待,最长等待时间为60s(max-wait=60000ms)。如果等待时间超过60s仍无法获得可用连接,则会抛出com.alibaba.druid.pool.GetConnectionTimeoutException异常。
@see com.alibaba.druid.pool.DruidDataSource#getConnectionDirect
if (maxActive <= 0) {
    throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (maxActive < minIdle) {
    throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (getInitialSize() > maxActive) {
    throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive);
}
com.alibaba.druid.pool.DruidDataSource#ini

✪ Druid数据库连接池回收连接的配置

spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=1000L * 60L * 30L
spring.datasource.druid.max-evictable-idle-time-millis=1000L * 60L * 60L * 7
spring.datasource.druid.keep-alive=true
Druid数据库连接回收线程会每隔time-between-eviction-runs-millis遍历连接池中的所有数据库连接,检查是否具体回收条件。
/**
 * 启动回收数据库连接池的定时任务
 * 在Druid init时执行
 */
protected void createAndStartDestroyThread() {
    destroyTask = new DestroyTask();

    if (destroyScheduler != null) {
        // 回收任务执行的时间间隔
        long period = timeBetweenEvictionRunsMillis;
        if (period <= 0) {
            period = 1000;
        }
        destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyTask, period, period,
                TimeUnit.MILLISECONDS);
        initedLatch.countDown();
        return;
    }
    String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
    destroyConnectionThread = new DestroyConnectionThread(threadName);
    destroyConnectionThread.start();
}
com.alibaba.druid.pool.DruidDataSource#createAndStartDestroyThread

当前线程空闲时间大于min-evictable-idle-time-millis,如果keep-alive为true则不会回收这个数据库连接;如果keep-alive为false,空闲时会将数据库连接池中的连接全部回收

Mybatis相关的配置

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

✪ 经典实践

mybatis-plus.configuration.default-fetch-size=100
mybatis-plus.configuration.default-statement-timeout=30
mybatis-plus.configuration.map-underscore-to-camel-case=true

这是Mybatis-Plus的一些配置参数,下面是对这些参数的涵义解释:

1、mybatis-plus.configuration.default-fetch-size=100 表示Mybatis的默认Fetch Size大小为100。Fetch Size是指从底层数据库获取数据时,在一次网络调用中获取的数据记录条数。通过调整Fetch Size的大小,可以控制在单个查询中返回的数据记录数目,从而避免因内存不足或网络带宽限制而导致查询性能下降。

2、mybatis-plus.configuration.default-statement-timeout=30 表示Mybatis的默认Statement超时时间为30秒。Statement Timeout是指执行SQL语句的最大等待时间,如果SQL语句在规定的时间内没有执行完成,则会抛出异常。通过设置Statement Timeout,可以避免因某些耗时操作(如大量数据处理、死锁等)而导致的应用程序阻塞甚至崩溃。

3、mybatis-plus.configuration.map-underscore-to-camel-case=true 表示开启Mybatis的下划线转驼峰功能。开启该配置后,Mybatis将自动将下划线分隔的列名或属性名转换为驼峰命名方式,例如将 "user_name" 转换为 "userName",以方便Java对象和数据库表字段的映射。

通过合理配置Mybatis-Plus的参数,可以提高系统的性能和稳定性,同时简化开发并提高代码的可读性。但需要注意,不同的应用程序和场景可能需要不同的参数配置,需要根据实际情况进行调整。

Redis连接池调优

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

✪ 经典实践

#lettuce配置
spring.redis.client-type=lettuce
spring.redis.client-name=sp
spring.redis.lettuce.pool.enabled=true
spring.redis.lettuce.pool.max-idle=2
spring.redis.lettuce.pool.min-idle=2
spring.redis.lettuce.pool.max-active=100
spring.redis.lettuce.pool.max-wait=1000
spring.redis.lettuce.pool.time-between-eviction-runs=1000

这段代码是Spring Boot中使用Lettuce客户端连接Redis时的一些配置参数,下面是各个参数的含义:

1、spring.redis.client-type=lettuce 表示使用Lettuce作为Redis客户端。

2、spring.redis.client-name=sp 表示设置Redis的客户端名称为 "sp"。

3、spring.redis.lettuce.pool.enabled=true 表示启用Lettuce连接池功能。

4、spring.redis.lettuce.pool.max-idle=2 表示最大空闲连接数为2。如果连接池中的空闲连接数超过该值,则会被移除并关闭。

5、spring.redis.lettuce.pool.min-idle=2 表示最小连接数为2。即使连接池中没有Redis请求,也至少保持2个常驻连接。

6、spring.redis.lettuce.pool.max-active=100 表示最大可用活跃连接数为100。连接池中最多可以存在100个活跃连接。

7、spring.redis.lettuce.pool.max-wait=1000 表示连接池在等待连接返回时的最大等待时间为1秒。如果连接池中没有可用连接,则请求会在等待1秒后抛出异常。
8
、spring.redis.lettuce.pool.time-between-eviction-runs=1000 表示连接回收线程的执行频次为1秒。每隔1秒自动检查连接池中的连接是否过期,并关闭过期连接。

通过合理配置上述参数,可以提高Redis连接的效率和性能,同时避免因连接不足或过期而导致应用程序出现错误或崩溃。需要根据实际情况调整参数值,并且建议使用Redis官方推荐的最佳实践进行优化。

最后一步:git push

19:20:10.030: [MetaData] git -c credential.helper= -c core.quotepath=false -c log.showSignature=false push --progress --porcelain origin refs/heads/main:refs/heads/main --set-upstream
Enumerating objects: 52, done.
Counting objects:   1% (1/52)
Counting objects: 100% (52/52), done.
Delta compression using up to 12 threads
Compressing objects:   2% (1/37)
Compressing objects:  97% (36/37)
Compressing objects: 100% (37/37)
Compressing objects: 100% (37/37), done.
Writing objects:   1% (1/52)
Writing objects: 100% (52/52)
Writing objects: 100% (52/52), 77.40 KiB | 3.69 MiB/s, done.
Total 52 (delta 4), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-6.4]
remote: Create a pull request for 'main' on Gitee by visiting:        
remote:     https://gitee.com/baidumap/meta-data/pull/new/baidumap:main...baidumap:master        
To https://gitee.com/baidumap/meta-data.git
*  refs/heads/main:refs/heads/main  [new branch]
branch 'main' set up to track 'origin/main'.
Done

git merge --allow-unrelated-histories origin/main

至此,新项目搭建完成。

04

小结&&思考

使用最新的组件搭建一套脚手架,还是挺花时间的。但一直使用老的版本来迭代会产生“技术债”。
从长远看,是不利于公司建立技术优势的。
下一步考虑如何把目前的成果抽取成一个基础设施。


参考&&资源

https://blog.csdn.net/lzhfdxhxm/article/details/117018384
https://help.aliyun.com/document_detail/312705.html
https://mvnrepository.com
https://start.spring.io/
https://spring.io/projects/spring-cloud#overview
https://baomidou.com/pages/226c21/#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B
https://dev.mysql.com/downloads/connector/j/
https://www.cnblogs.com/softidea/p/9180189.html
https://blog.csdn.net/zzy7075/article/details/121631974
https://www.cnblogs.com/softidea/p/5444987.html
一文搞懂技术债
https://gitee.com/baidumap/meta-data

 

点赞收藏
bit01
请先登录,查看5条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

随机一门技术分享之Netty

随机一门技术分享之Netty

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

6
5