AI在线 AI在线

性能优化!七个策略,让Spring Boot 处理每秒百万请求

环境:SpringBoot3.4.21. 简介在实施任何优化前,我首先明确了性能基准。 这一步至关重要——若不清楚起点,便无法衡量进展,也无法定位最关键的改进方向。

环境: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秒直接失败)

这使我们能够高效处理流量高峰,同时保持弹性。

相关资讯

LLM幻觉,竟因知识「以大欺小」!华人团队祭出对数线性定律与CoDA策略

大语言模型(LLMs)已经彻底改变了AI,但「幻觉」问题如影随从,堪称LLM癌症。 LLM会一本正经、义正辞严的捏造事实,「脸不红,心不跳」地说谎。 「幻觉」被普遍认为与训练数据相关。
4/8/2025 2:22:00 AM
新智元

大模型重复生成内容:根因剖析与优化策略

前言最近在调试大模型应用过程中,遇到了如下问题:复制大模型首次生成内容与「重新生成」两次返回的内容近乎完全相同,几乎没有体现出任何差异性。 面对这种情况,造成大模型输出高度相似的原因是什么呢? 我们又该采取怎样的调整策略,才能使重新生成的内容与前次存在明显差异,提升输出的多样性呢?
4/28/2025 2:22:00 AM
张张

通过奖励随机化发现多智能体游戏中多样性策略行为,清华、UC伯克利等研究者提出全新算法RPG

在这篇论文中,研究者提出了一个在 reward-space 进行探索的新算法 RPG(Reward-Randomized Policy Gradient),并且在存在多个纳什均衡 (Nash Equilibrium, NE) 的挑战性的多智能任务中进行了实验验证,实验结果表明,RPG 的表现显著优于经典的 policy/action-space 探索的算法,并且发现了很多有趣的、人类可以理解的智能体行为策略。除此之外,论文进一步提出了 RPG 算法的扩展:利用 RR 得到的多样性策略池训练一个新的具备自适应能力的策
3/11/2021 2:46:00 PM
机器之心
  • 1