环境: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秒直接失败)
这使我们能够高效处理流量高峰,同时保持弹性。