title: 如何优化Spring Boot应用以处理每秒百万请求 author: Gamehu tags:
在当今高流量的互联网环境中,应用程序需要处理的请求量正在迅速增长。作为Java开发者,我们经常面临如何扩展Spring Boot应用以处理大量并发请求的挑战。本文将探讨如何将Spring Boot应用从处理每秒5万请求优化到每秒100万请求的实用策略,并提供详细的代码示例和实施步骤。
想象一下这样的场景:你的团队被告知应用需要在三个月内实现20倍的流量增长,从每秒5万请求提升到每秒100万请求,而且硬件预算有限。这听起来几乎是不可能完成的任务。然而,通过深入分析性能瓶颈和应用一系列优化技术,我们可以达成这一目标。
在开始优化之前,我们需要了解系统的现状:
在开始优化过程之前,了解我们的目标至关重要。成功优化后的应用应该达到以下性能指标:
最有影响力的变化是采用Spring WebFlux进行响应式编程。这不仅仅是简单的替换,而是需要重新思考应用程序的结构。首先,更新项目依赖:
<!-- 将spring-boot-starter-web替换为spring-boot-starter-webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.7.5</version>
</dependency>
<!-- 使用Netty作为服务器 -->
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>1.0.24</version>
</dependency>
<!-- 引入Reactor测试支持 -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
然后,改变控制器实现方式:
// 传统Spring MVC控制器
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
List<Product> products = productService.getAllProducts();
return ResponseEntity.ok(products);
}
}
// 响应式WebFlux控制器
@RestController
@RequestMapping("/api/products")
public class ReactiveProductController {
@Autowired
private ReactiveProductService productService;
@GetMapping("/{id}")
public Mono<ResponseEntity<Product>> getProduct(@PathVariable Long id) {
return productService.getProductById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@GetMapping
public Mono<ResponseEntity<List<Product>>> getAllProducts() {
return productService.getAllProducts()
.collectList()
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}
服务层也需要相应改变:
// 传统的服务实现
@Service
public class ProductService {
@Autowired
private ProductRepository repository;
public Product getProductById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
public List<Product> getAllProducts() {
return repository.findAll();
}
}
// 响应式服务实现
@Service
public class ReactiveProductService {
@Autowired
private ReactiveProductRepository repository;
public Mono<Product> getProductById(Long id) {
return repository.findById(id)
.switchIfEmpty(Mono.error(new ProductNotFoundException(id)));
}
public Flux<Product> getAllProducts() {
return repository.findAll();
}
}
数据库通常是系统的主要瓶颈。以下是一些优化数据库访问的详细策略:
将传统的JDBC驱动替换为响应式数据库驱动:
<!-- 添加R2DBC依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<!-- MySQL的R2DBC驱动 -->
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>0.8.2.RELEASE</version>
</dependency>
配置R2DBC连接:
@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:
public interface ReactiveProductRepository extends ReactiveCrudRepository<Product, Long> {
Flux<Product> findByCategory(String category);
Mono<Product> findByName(String name);
@Query("SELECT * FROM products WHERE price > :minPrice")
Flux<Product> findExpensiveProducts(BigDecimal minPrice);
}
引入Redis缓存:
<!-- Redis响应式支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置Redis缓存:
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory("localhost", 6379);
}
@Bean
public ReactiveRedisTemplate<String, Product> reactiveRedisTemplate(
ReactiveRedisConnectionFactory factory) {
StringRedisSerializer keySerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Product> valueSerializer =
new Jackson2JsonRedisSerializer<>(Product.class);
RedisSerializationContext.RedisSerializationContextBuilder<String, Product> builder =
RedisSerializationContext.newSerializationContext(keySerializer);
RedisSerializationContext<String, Product> context =
builder.value(valueSerializer).build();
return new ReactiveRedisTemplate<>(factory, context);
}
}
在服务层实现缓存:
@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<String, Product> redisTemplate;
public Mono<Product> 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<Product> getAllProducts() {
// 对于列表类型的数据,可以使用不同的缓存策略
return repository.findAll();
}
// 清除缓存的方法,在更新产品时调用
public Mono<Void> invalidateCache(Long id) {
String cacheKey = CACHE_KEY_PREFIX + id;
return redisTemplate.delete(cacheKey);
}
}
使用索引优化:
-- 为常用查询字段添加索引
CREATE INDEX idx_product_category ON products(category);
CREATE INDEX idx_product_price ON products(price);
CREATE INDEX idx_product_name ON products(name);
优化查询方法:
// 避免使用count(*)
public Mono<Boolean> existsByCategory(String category) {
return repository.findFirstByCategory(category)
.map(product -> true)
.defaultIfEmpty(false);
}
// 使用分页减少数据传输量
public Flux<Product> 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<ProductSummary> findProductSummariesByCategory(String category);
配置主从数据源:
@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:
@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<Product> save(Product product) {
return masterTemplate.insert(Product.class)
.into("products")
.using(product);
}
// 读操作使用从库
public Mono<Product> findById(Long id) {
return slaveTemplate.select(Product.class)
.from("products")
.matching(Query.query(Criteria.where("id").is(id)))
.one();
}
public Flux<Product> findAll() {
return slaveTemplate.select(Product.class)
.from("products")
.all();
}
}
性能提升往往来自于正确的配置调整。以下是详细的配置优化步骤:
在application.yml中配置Netty参数:
spring:
webflux:
base-path: /api
netty:
connection:
timeout: 5000 # 连接超时时间(毫秒)
worker:
count: 16 # worker线程数量, 建议设置为CPU核心数的2倍
boss:
count: 2 # boss线程数量
buffer:
size: 32768 # 缓冲区大小(字节)
在Java代码中自定义Netty配置:
@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;
}
}
在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
配置R2DBC连接池:
@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连接池:
@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);
}
}
配置WebClient高性能连接池:
@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();
}
}
不是所有的端点都需要改为响应式的。下面是混合架构的详细实现:
使用Spring的路由器函数:
@Configuration
public class RouterConfig {
// 高流量端点使用响应式处理
@Bean
public RouterFunction<ServerResponse> 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");
}
};
}
}
@Component
public class ReactiveProductHandler {
private final ReactiveProductService productService;
public ReactiveProductHandler(ReactiveProductService productService) {
this.productService = productService;
}
public Mono<ServerResponse> 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<ServerResponse> getAllProducts(ServerRequest request) {
return productService.getAllProducts()
.collectList()
.flatMap(products ->
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.bodyValue(products))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> 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());
}
}
创建适配器以支持新旧代码互操作:
@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<Product> getProductReactive(Long id) {
return Mono.fromCallable(() -> legacyService.getProductById(id))
.subscribeOn(Schedulers.boundedElastic());
}
// 批量操作适配
public List<Product> getAllProductsSync() {
return reactiveService.getAllProducts().collectList().block();
}
public Flux<Product> getAllProductsReactive() {
return Flux.defer(() -> Flux.fromIterable(legacyService.getAllProducts()))
.subscribeOn(Schedulers.boundedElastic());
}
}
在优化应用程序之后,可以通过Kubernetes进行智能水平扩展。以下是详细的配置和实现:
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"]
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"
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
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
在应用程序中添加断路器模式:
<!-- 添加Resilience4j依赖 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
<version>1.7.1</version>
</dependency>
配置断路器:
@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.