title: 如何优化Spring Boot应用以处理每秒百万请求 author: Gamehu tags: - Spring Boot categories: - 工作 date: 2025-05-13 22:31:00 --- # 如何优化Spring Boot应用以处理每秒百万请求 在当今高流量的互联网环境中,应用程序需要处理的请求量正在迅速增长。作为Java开发者,我们经常面临如何扩展Spring Boot应用以处理大量并发请求的挑战。本文将探讨如何将Spring Boot应用从处理每秒5万请求优化到每秒100万请求的实用策略,并提供详细的代码示例和实施步骤。 ## 面临的挑战 想象一下这样的场景:你的团队被告知应用需要在三个月内实现20倍的流量增长,从每秒5万请求提升到每秒100万请求,而且硬件预算有限。这听起来几乎是不可能完成的任务。然而,通过深入分析性能瓶颈和应用一系列优化技术,我们可以达成这一目标。 在开始优化之前,我们需要了解系统的现状: - 基于Spring Boot 2.x的传统MVC架构 - 使用内嵌的Tomcat服务器 - 采用JPA进行数据库访问 - 典型的N层架构(Controller > Service > Repository) - 高峰期出现频繁的GC暂停和超时错误 ## 关键性能指标 在开始优化过程之前,了解我们的目标至关重要。成功优化后的应用应该达到以下性能指标: - 最大吞吐量:每秒120万请求 - 平均响应时间:85毫秒(优化前为350毫秒) - 95%响应时间:120毫秒(优化前为850毫秒) - 峰值CPU使用率:60-70%(优化前为85-95%) - 内存使用:可用堆的50%(优化前为75%) - 数据库查询:通过缓存减少70% ## 优化策略 ### 1. 采用响应式编程模型(Spring WebFlux) 最有影响力的变化是采用Spring WebFlux进行响应式编程。这不仅仅是简单的替换,而是需要重新思考应用程序的结构。首先,更新项目依赖: ```xml org.springframework.boot spring-boot-starter-webflux 2.7.5 io.projectreactor.netty reactor-netty 1.0.24 io.projectreactor reactor-test test ``` 然后,改变控制器实现方式: ```java // 传统Spring MVC控制器 @RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService productService; @GetMapping("/{id}") public ResponseEntity getProduct(@PathVariable Long id) { Product product = productService.getProductById(id); return ResponseEntity.ok(product); } @GetMapping public ResponseEntity> getAllProducts() { List products = productService.getAllProducts(); return ResponseEntity.ok(products); } } // 响应式WebFlux控制器 @RestController @RequestMapping("/api/products") public class ReactiveProductController { @Autowired private ReactiveProductService productService; @GetMapping("/{id}") public Mono> getProduct(@PathVariable Long id) { return productService.getProductById(id) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } @GetMapping public Mono>> getAllProducts() { return productService.getAllProducts() .collectList() .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } } ``` 服务层也需要相应改变: ```java // 传统的服务实现 @Service public class ProductService { @Autowired private ProductRepository repository; public Product getProductById(Long id) { return repository.findById(id) .orElseThrow(() -> new ProductNotFoundException(id)); } public List getAllProducts() { return repository.findAll(); } } // 响应式服务实现 @Service public class ReactiveProductService { @Autowired private ReactiveProductRepository repository; public Mono getProductById(Long id) { return repository.findById(id) .switchIfEmpty(Mono.error(new ProductNotFoundException(id))); } public Flux getAllProducts() { return repository.findAll(); } } ``` ### 2. 优化数据库访问 数据库通常是系统的主要瓶颈。以下是一些优化数据库访问的详细策略: #### 2.1 采用响应式数据库驱动 将传统的JDBC驱动替换为响应式数据库驱动: ```xml org.springframework.boot spring-boot-starter-data-r2dbc dev.miku r2dbc-mysql 0.8.2.RELEASE ``` 配置R2DBC连接: ```java @Configuration public class R2dbcConfig { @Bean public ConnectionFactory connectionFactory() { return MySqlConnectionFactory.from( MySqlConnectionConfiguration.builder() .host("localhost") .port(3306) .username("root") .password("password") .database("reactive_demo") .build() ); } @Bean public R2dbcEntityTemplate r2dbcEntityTemplate(ConnectionFactory connectionFactory) { return new R2dbcEntityTemplate(connectionFactory); } } ``` 创建响应式Repository: ```java public interface ReactiveProductRepository extends ReactiveCrudRepository { Flux findByCategory(String category); Mono findByName(String name); @Query("SELECT * FROM products WHERE price > :minPrice") Flux findExpensiveProducts(BigDecimal minPrice); } ``` #### 2.2 实施多级缓存策略 引入Redis缓存: ```xml org.springframework.boot spring-boot-starter-data-redis-reactive ``` 配置Redis缓存: ```java @Configuration @EnableRedisRepositories public class RedisConfig { @Bean public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() { return new LettuceConnectionFactory("localhost", 6379); } @Bean public ReactiveRedisTemplate reactiveRedisTemplate( ReactiveRedisConnectionFactory factory) { StringRedisSerializer keySerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer<>(Product.class); RedisSerializationContext.RedisSerializationContextBuilder builder = RedisSerializationContext.newSerializationContext(keySerializer); RedisSerializationContext context = builder.value(valueSerializer).build(); return new ReactiveRedisTemplate<>(factory, context); } } ``` 在服务层实现缓存: ```java @Service public class CachedProductService { private static final String CACHE_KEY_PREFIX = "product:"; private static final Duration CACHE_TTL = Duration.ofMinutes(10); @Autowired private ReactiveProductRepository repository; @Autowired private ReactiveRedisTemplate redisTemplate; public Mono getProductById(Long id) { String cacheKey = CACHE_KEY_PREFIX + id; // 先尝试从缓存获取 return redisTemplate.opsForValue().get(cacheKey) .switchIfEmpty( // 缓存未命中,从数据库加载 repository.findById(id) .flatMap(product -> { // 将结果存入缓存 return redisTemplate.opsForValue() .set(cacheKey, product, CACHE_TTL) .thenReturn(product); }) ); } public Flux getAllProducts() { // 对于列表类型的数据,可以使用不同的缓存策略 return repository.findAll(); } // 清除缓存的方法,在更新产品时调用 public Mono invalidateCache(Long id) { String cacheKey = CACHE_KEY_PREFIX + id; return redisTemplate.delete(cacheKey); } } ``` #### 2.3 优化SQL查询 使用索引优化: ```sql -- 为常用查询字段添加索引 CREATE INDEX idx_product_category ON products(category); CREATE INDEX idx_product_price ON products(price); CREATE INDEX idx_product_name ON products(name); ``` 优化查询方法: ```java // 避免使用count(*) public Mono existsByCategory(String category) { return repository.findFirstByCategory(category) .map(product -> true) .defaultIfEmpty(false); } // 使用分页减少数据传输量 public Flux getProductsByCategory(String category, int page, int size) { return repository.findByCategory(category, PageRequest.of(page, size, Sort.by("name").ascending())); } // 使用投影只返回需要的字段 @Query("SELECT id, name, price FROM products WHERE category = :category") Flux findProductSummariesByCategory(String category); ``` #### 2.4 实现读写分离 配置主从数据源: ```java @Configuration public class DatabaseConfig { @Bean @Primary @Qualifier("masterConnectionFactory") public ConnectionFactory masterConnectionFactory() { return MySqlConnectionFactory.from( MySqlConnectionConfiguration.builder() .host("master-db.example.com") .port(3306) .username("master_user") .password("master_pass") .database("products") .build() ); } @Bean @Qualifier("slaveConnectionFactory") public ConnectionFactory slaveConnectionFactory() { return MySqlConnectionFactory.from( MySqlConnectionConfiguration.builder() .host("slave-db.example.com") .port(3306) .username("read_user") .password("read_pass") .database("products") .build() ); } @Bean public TransactionManager transactionManager( @Qualifier("masterConnectionFactory") ConnectionFactory connectionFactory) { return new R2dbcTransactionManager(connectionFactory); } } ``` 创建读写分离的Repository: ```java @Repository public class ReadWriteProductRepository { private final R2dbcEntityTemplate masterTemplate; private final R2dbcEntityTemplate slaveTemplate; public ReadWriteProductRepository( @Qualifier("masterConnectionFactory") ConnectionFactory masterFactory, @Qualifier("slaveConnectionFactory") ConnectionFactory slaveFactory) { this.masterTemplate = new R2dbcEntityTemplate(masterFactory); this.slaveTemplate = new R2dbcEntityTemplate(slaveFactory); } // 写操作使用主库 @Transactional public Mono save(Product product) { return masterTemplate.insert(Product.class) .into("products") .using(product); } // 读操作使用从库 public Mono findById(Long id) { return slaveTemplate.select(Product.class) .from("products") .matching(Query.query(Criteria.where("id").is(id))) .one(); } public Flux findAll() { return slaveTemplate.select(Product.class) .from("products") .all(); } } ``` ### 3. 配置调优 性能提升往往来自于正确的配置调整。以下是详细的配置优化步骤: #### 3.1 调整Netty服务器参数 在`application.yml`中配置Netty参数: ```yaml spring: webflux: base-path: /api netty: connection: timeout: 5000 # 连接超时时间(毫秒) worker: count: 16 # worker线程数量, 建议设置为CPU核心数的2倍 boss: count: 2 # boss线程数量 buffer: size: 32768 # 缓冲区大小(字节) ``` 在Java代码中自定义Netty配置: ```java @Configuration public class NettyConfig { @Bean public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() { NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(); factory.addServerCustomizers(server -> { HttpServer httpServer = (HttpServer) server; // 设置更大的接收缓冲区大小 httpServer.tcpConfiguration(tcpServer -> tcpServer.option(ChannelOption.SO_RCVBUF, 128 * 1024) .option(ChannelOption.SO_SNDBUF, 128 * 1024) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.TCP_NODELAY, true)); return httpServer; }); return factory; } } ``` #### 3.2 优化JVM堆和垃圾收集器 在`application.properties`中添加JVM参数,或者直接在启动脚本中配置: ``` # 堆内存设置 -Xms4g -Xmx4g # 年轻代大小,建议为堆的1/3到1/2 -Xmn2g # GC相关设置,使用G1收集器 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=2 -XX:InitiatingHeapOccupancyPercent=70 # 禁用类元数据的GC,防止Full GC -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m # 优化内存分配 -XX:+AlwaysPreTouch -XX:+DisableExplicitGC # 开启GC日志记录 -Xlog:gc*:file=logs/gc-%t.log:time,uptime,level,tags:filecount=5,filesize=100m ``` #### 3.3 连接池优化 配置R2DBC连接池: ```java @Configuration public class ConnectionPoolConfig { @Bean public ConnectionFactory connectionFactory() { ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder() .connectionFactory(createConnectionFactory()) .maxIdleTime(Duration.ofMinutes(30)) .maxLifeTime(Duration.ofHours(2)) .maxAcquireTime(Duration.ofSeconds(3)) .initialSize(10) .maxSize(50) // 根据负载调整 .minIdle(10) .build(); return new ConnectionPool(configuration); } private ConnectionFactory createConnectionFactory() { return MySqlConnectionFactory.from( MySqlConnectionConfiguration.builder() .host("localhost") .port(3306) .username("root") .password("password") .database("reactive_demo") .connectTimeout(Duration.ofSeconds(3)) .build() ); } } ``` 配置Redis连接池: ```java @Configuration public class RedisConnectionConfig { @Bean public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() { LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .commandTimeout(Duration.ofMillis(100)) // 命令超时 .shutdownTimeout(Duration.ZERO) // 关闭超时 .clientOptions(ClientOptions.builder() .socketOptions(SocketOptions.builder() .connectTimeout(Duration.ofMillis(100)) // 连接超时 .keepAlive(true) .build()) .build()) .clientResources(DefaultClientResources.builder() .ioThreadPoolSize(4) // IO线程池大小 .computationThreadPoolSize(4) // 计算线程池大小 .build()) .build(); RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(); serverConfig.setHostName("localhost"); serverConfig.setPort(6379); return new LettuceConnectionFactory(serverConfig, clientConfig); } } ``` #### 3.4 HTTP客户端调优 配置WebClient高性能连接池: ```java @Configuration public class WebClientConfig { @Bean public WebClient webClient() { HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(10)) .addHandlerLast(new WriteTimeoutHandler(10))) .responseTimeout(Duration.ofSeconds(5)) .compress(true) .wiretap(true) // 开发环境调试使用,生产环境应关闭 .keepAlive(true) .poolResources(ConnectionProvider.builder("custom") .maxConnections(500) .pendingAcquireTimeout(Duration.ofMillis(5000)) .pendingAcquireMaxCount(1000) .maxIdleTime(Duration.ofMillis(8000)) .build()); return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)) .build(); } } ``` ### 4. 混合架构方法 不是所有的端点都需要改为响应式的。下面是混合架构的详细实现: #### 4.1 定义混合架构路由 使用Spring的路由器函数: ```java @Configuration public class RouterConfig { // 高流量端点使用响应式处理 @Bean public RouterFunction highTrafficRoutes( ReactiveProductHandler productHandler) { return RouterFunctions .route(GET("/api/products").and(accept(APPLICATION_JSON)), productHandler::getAllProducts) .andRoute(GET("/api/products/{id}").and(accept(APPLICATION_JSON)), productHandler::getProduct) .andRoute(GET("/api/products/category/{category}").and(accept(APPLICATION_JSON)), productHandler::getProductsByCategory); } // 低流量的管理端点保留传统MVC @Bean public WebMvcConfigurer mvcConfigurer() { return new WebMvcConfigurer() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/admin").setViewName("admin"); registry.addViewController("/dashboard").setViewName("dashboard"); } }; } } ``` #### 4.2 响应式处理器实现 ``` @Component public class ReactiveProductHandler { private final ReactiveProductService productService; public ReactiveProductHandler(ReactiveProductService productService) { this.productService = productService; } public Mono getProduct(ServerRequest request) { Long productId = Long.valueOf(request.pathVariable("id")); return productService.getProductById(productId) .flatMap(product -> ServerResponse.ok() .contentType(APPLICATION_JSON) .bodyValue(product)) .switchIfEmpty(ServerResponse.notFound().build()); } public Mono getAllProducts(ServerRequest request) { return productService.getAllProducts() .collectList() .flatMap(products -> ServerResponse.ok() .contentType(APPLICATION_JSON) .bodyValue(products)) .switchIfEmpty(ServerResponse.notFound().build()); } public Mono getProductsByCategory(ServerRequest request) { String category = request.pathVariable("category"); return productService.getProductsByCategory(category) .collectList() .flatMap(products -> ServerResponse.ok() .contentType(APPLICATION_JSON) .bodyValue(products)) .switchIfEmpty(ServerResponse.notFound().build()); } } ``` #### 4.3 实现逐步迁移策略 创建适配器以支持新旧代码互操作: ``` @Component public class ProductServiceAdapter { private final ReactiveProductService reactiveService; private final LegacyProductService legacyService; @Autowired public ProductServiceAdapter( ReactiveProductService reactiveService, LegacyProductService legacyService) { this.reactiveService = reactiveService; this.legacyService = legacyService; } // 将响应式服务适配到传统服务 public Product getProductSync(Long id) { return reactiveService.getProductById(id).block(); } // 将传统服务适配到响应式服务 public Mono getProductReactive(Long id) { return Mono.fromCallable(() -> legacyService.getProductById(id)) .subscribeOn(Schedulers.boundedElastic()); } // 批量操作适配 public List getAllProductsSync() { return reactiveService.getAllProducts().collectList().block(); } public Flux getAllProductsReactive() { return Flux.defer(() -> Flux.fromIterable(legacyService.getAllProducts())) .subscribeOn(Schedulers.boundedElastic()); } } ``` ### 5. 水平扩展与Kubernetes 在优化应用程序之后,可以通过Kubernetes进行智能水平扩展。以下是详细的配置和实现: #### 5.1 创建Dockerfile ```dockerfile FROM openjdk:17-jdk-slim WORKDIR /app COPY target/reactive-product-service-0.0.1-SNAPSHOT.jar app.jar # 设置JVM参数 ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Xms1g -Xmx1g -XX:+AlwaysPreTouch" EXPOSE 8080 # 健康检查 HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl -f http://localhost:8080/actuator/health || exit 1 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] ``` #### 5.2 创建Kubernetes部署配置 ``` apiVersion: apps/v1 kind: Deployment metadata: name: product-service namespace: ecommerce spec: replicas: 3 selector: matchLabels: app: product-service strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 0 type: RollingUpdate template: metadata: labels: app: product-service spec: containers: - name: product-service image: my-registry/product-service:latest imagePullPolicy: Always ports: - containerPort: 8080 resources: requests: cpu: "500m" memory: "1Gi" limits: cpu: "1000m" memory: "2Gi" readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 20 timeoutSeconds: 5 failureThreshold: 3 env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: SERVER_PORT value: "8080" - name: JAVA_OPTS value: "-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Xms1g -Xmx1g -XX:+AlwaysPreTouch" ``` #### 5.3 创建服务和入口配置 ``` apiVersion: v1 kind: Service metadata: name: product-service namespace: ecommerce spec: selector: app: product-service ports: - port: 80 targetPort: 8080 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: product-service-ingress namespace: ecommerce annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "10m" nginx.ingress.kubernetes.io/proxy-read-timeout: "60" nginx.ingress.kubernetes.io/proxy-send-timeout: "60" spec: rules: - host: api.example.com http: paths: - path: /api/products pathType: Prefix backend: service: name: product-service port: number: 80 ``` #### 5.4 配置自动扩缩容 ``` apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: product-service-hpa namespace: ecommerce spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: product-service minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 behavior: scaleUp: stabilizationWindowSeconds: 30 policies: - type: Percent value: 100 periodSeconds: 15 scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 20 periodSeconds: 60 ``` #### 5.5 实现优雅降级机制 在应用程序中添加断路器模式: ``` io.github.resilience4j resilience4j-spring-boot2 1.7.1 io.github.resilience4j resilience4j-reactor 1.7.1 ``` 配置断路器: ``` @Configuration public class ResilienceConfig { @Bean public CircuitBreakerRegistry circuitBreakerRegistry() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .failureRateThreshold(50) .slowCallRateThreshold(50) .slowCallDurationThreshold(Duration.ofSeconds(1)) .permittedNumberOfCallsInHalfOpenState(10) .minimumNumberOfCalls(20) .slidingWindowType(CircuitBreakerConfig.SlidingWindowType. ``` # 参考 https://medium.com/javarevisited/how-i-optimized-a-spring-boot-application-to-handle-1m-requests-second-0cbb2f2823ed