如何用Spring Boot打造一个高性能的电商平台:搭建过程、踩坑经历及实践经验分享原创
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.6JDK version和class file version(Class编译版本号)对应关系
✪ 解决办法:IDEA中的JDK版本全部更新为1.8
查看路径:在项目上点右键-->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-starter-bootstrap
坑3:Spring Boot [2.7.11] is not compatible with this Spring Cloud release train
***************************
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:项目无法启动。也没有报错
卡在这了:原因是数据库连接配错了。配的是内网地址,但本地与数据库网络不通
解决办法:将数据库连接更改为本地网络相通的外网地址
<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
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 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://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