环境:SpringBoot3.4.2
1. 简介
在实施任何优化前,我首先明确了性能基准。这一步至关重要——若不清楚起点,便无法衡量进展,也无法定位最关键的改进方向。以下是我们的初始性能指标概况:
最大吞吐量:50,000 次请求/秒
平均响应时间:350 毫秒
95 分位响应时间:850 毫秒
峰值时段 CPU 使用率:85%-95%
内存占用:堆内存使用达可用空间的 75%
数据库连接:频繁达到连接池上限(100 )
线程池饱和:线程池资源经常耗尽
以上指标通过如下的工具进行收集所得:
- JMeter用于负载测试,确定基础吞吐量数值
- Micrometer + Prometheus + Grafana实现实时监控与可视化
- JProfiler深入分析代码中的性能热点
- 火焰图(Flame graphs)定位 CPU 密集型方法
根据上面的指标总结如下性能瓶颈:
- 线程池饱和默认的 Tomcat 连接器已达到性能上限
- 数据库连接争用HikariCP 连接池配置未针对实际负载优化
- 序列化效率低下Jackson 在请求/响应处理中消耗大量 CPU 资源
- 阻塞式 I/O 操作尤其在调用外部服务时表现明显
- 内存压力过度对象创建导致频繁的 GC 停顿
接下来,我们将逐一的解决上面的问题。
2. 性能优化
2.1 使用响应式编程
阻塞方式:
复制@Service
public class ProductService {
private final ProductRepository productRepository ;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository ;
}
public Product getProductById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id)) ;
}
}基于响应式改造:
复制@Service
public class ProductService {
private final ProductRepository productRepository ;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository ;
}
public Product getProductById(Long id) {
public Mono<Product> getProductById(Long id) {
return productRepository.findById(id)
.switchIfEmpty(Mono.error(new ProductNotFoundException(id)));
}
}
}同时Controller层也需要改造
复制@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService ;
}
@GetMapping("/{id}")
public Mono<ResponseEntity<Product>> getProduct(@PathVariable Long id) {
return service.getProductById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}注意,对应依赖方面你需要引入如下相关的依赖:
复制<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <!--基于响应式的mysql驱动包--> <dependency> <groupId>com.github.jasync-sql</groupId> <artifactId>jasync-r2dbc-mysql</artifactId> <version>2.1.24</version> </dependency>
总结:仅这一项改动便使吞吐量翻倍,其核心在于更高效地利用线程资源。WebFlux 不再为每个请求分配独立线程,而是通过少量线程处理海量并发请求。
有关响应式编程,请查看下面文章:
新一代WebFlux框架核心技术Reactor响应式编程基本用法
响应式编程引领未来:WebFlux与R2DBC的完美结合实现数据库编程
SpringBoot3虚拟线程 & 反应式(WebFlux) & 传统Tomcat线程池 性能对比
新一代web框架WebFlux到底要不要学?
2.2 数据库优化
数据库交互成为下一个关键性能瓶颈。我采用了三管齐下的优化策略:
- 查询优化
我使用 Spring Data 的 @Query 注解取代了低效的自动生成查询:
优化前:
复制List<Order> findByUserIdAndStatusAndCreateTimeBetween(
Long userId, OrderStatus status,
LocalDate start, LocalDate end) ;优化后:
复制@Query("SELECT o FROM Order o WHERE o.userId = :userId " +
"AND o.status = :status " +
"AND o.createdDate BETWEEN :start AND :end " +
"ORDER BY o.createdDate DESC")
List<Order> findUserOrdersInDateRange(
@Param("userId") Long userId,
@Param("status") OrderStatus status,
@Param("start") LocalDate start,
@Param("end") LocalDate end) ;使用 Hibernate 的 @BatchSize 优化 N+1 查询:
复制@Entity
@Table(name = "t_order")
public class Order {
// ...
@OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
// 批量抓取数据
@BatchSize(size = 30)
private Set<OrderItem> items ;
}- 连接池优化
HikariCP 的默认设置造成了连接争用。经过大量测试,我得出了这样的配置(实际要根据自己的环境):
复制spring:
datasource:
hikari:
maximum-pool-size: 100
minimum-idle: 50
idle-timeout: 30000
connection-timeout: 2000
max-lifetime: 1800000关键的一点是,连接数并不总是越多越好;这里的hikari可不支持响应式。所以,我们应该吧响应式与阻塞式2种方式进行分开处理。
基于响应式数据库的配置如下:
复制spring:
r2dbc:
pool:
initialSize: 30
maxSize: 10
max-acquire-time: 30s
max-idle-time: 30m- 使用缓存
对于频繁访问的数据添加了 Redis 缓存。
复制// 开启
@Configuration
@EnableCaching
public class CacheConfig {
}
// 使用缓存
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Mono<Product> getProductById(Long id) {
return repository.findById(id)
.switchIfEmpty(Mono.error(new ProductNotFoundException(id)));
}
@CacheEvict(value = "products", key = "#product.id")
public Mono<Product> updateProduct(Product product) {
return repository.save(product) ;
}
}配置缓存:
复制spring:
cache:
type: redis
redis:
cache-null-values: false
time-to-live: 120m需要更多个性化配置,可以自定义RedisCacheManager。
2.3 序列化优化
通过优化jackson序列化,可以明显减少CPU的占用。
复制@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper() ;
// 启用 Afterburner 模块以加速序列化
mapper.registerModule(new AfterburnerModule()) ;
// 仅仅序列化不为空的字段
mapper.setSerializationInclusion(Include.NON_NULL) ;
// 禁用不需要的功能
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) ;
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) ;
return mapper ;
}
}如果对部分接口要求非常高,那么可以采用Protocol Buffers。
关于Protocol Buffers的使用,请查看下面文章:
接口优化!Spring Boot 集成 Protobuf 并使用 RestTemplate 实现微服务间通信
基于 Spring Boot 实现自定义二进制数据传输协议
2.4 线程池&连接优化
有了 WebFlux,我们需要调整 Netty 的事件循环设置:
复制spring:
reactor:
netty:
worker:
count: 32 #工作线程数(2 x CPU cores)
connection:
provider:
pool:
max-connections: 10000
acquire-timeout: 5000对于使用 Spring MVC 的,调整 Tomcat 连接器:
复制server:
tomcat:
threads:
max: 200
min-spare: 50
max-connections: 8192
accept-count: 100
connection-timeout: 5000这些设置使我们能够以较少的资源处理更多的并发连接。
2.5 基于 Kubernetes 的横向扩展:终极解决方案
通过横向扩展提升系统容量。将应用容器化后部署至 Kubernetes 集群。
复制FROM openjdk:17-slim COPY target/product-app.jar app.jar ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+ParallelRefProcEnabled" ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar
然后根据 CPU 利用率配置自动缩放:
复制apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: product-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
#目标 Deployment 的名称(即需要被扩缩容的应用)
name: product-app
#副本数范围限制5~20
minReplicas: 5
maxReplicas: 20
#定义触发扩缩容的指标规则
metrics:
- type: Resource #使用资源指标(如 CPU、内存)
resource:
name: cpu #监控 CPU 资源使用率
target:
type: Utilization #指标类型为“利用率百分比”
#当持续超过 70% 时触发扩缩容
averageUtilization: 70利用 Istio 实施服务网格功能,以实现更好的流量管理:
复制apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: product-vs
spec:
hosts:
- product-service # 目标服务名(需与 Istio 服务网格中注册的名称一致)
http: # 定义 HTTP 协议相关的流量规则(支持路由、重试、超时等策略)
- route: # 配置流量路由规则
- destination: # 指定流量的实际目的地
host: product-service # 目标服务名
retries: # 设置请求失败时的重试策略
attempts: 3 # 最大重试次数(首次请求 + 3次重试 = 最多4次尝试)
perTryTimeout: 2s # 单次请求(含重试)的超时时间(2秒无响应则中断)
timeout: 5s # 整个请求(所有重试累计)的全局超时时间(超过5秒直接失败)这使我们能够高效处理流量高峰,同时保持弹性。