Spring AI性能调优:并发调用·连接池·超时配置实战
2026/4/30大约 7 分钟
Spring AI性能调优:并发调用·连接池·超时配置实战
适读人群:有1-5年Java开发经验,想向AI工程师方向转型的开发者 阅读时长:约17分钟 文章价值:
- 掌握Spring AI高并发场景下的性能调优策略
- 学会连接池、超时、重试的最佳配置方案
- 通过实际压测数据指导生产配置决策
高峰期崩了
某家教育平台,他们的AI辅导系统在平日用得好好的,但是每当有大促或者开学季,并发一上来系统就垮。
症状很典型:请求堆积、超时、部分用户得到500错误。有时候服务重启能解决,有时候重启也没用。
我去帮他们看的时候,第一眼就发现了问题:他们的应用用的是默认的Spring Boot Tomcat配置,最大线程数200,但是AI接口的平均响应时间是3秒,高峰期QPS是100,简单算一下:100 QPS × 3s = 300个并发线程需求,而线程池上限是200,直接溢出。
更要命的是,他们没有任何超时设置,LLM偶尔抖一下响应5秒、8秒,那200个线程就被"钉死"在那里了,新的请求全部拒绝。
这是AI服务特有的性能挑战——响应时间长、变化大,对线程模型和超时策略的要求和普通微服务完全不同。
AI服务的性能模型
线程模型优化
方案一:开启虚拟线程(Java 21+,推荐)
# application.yml
spring:
threads:
virtual:
enabled: true # Java 21+, Spring Boot 3.2+
# 开启后Tomcat/Undertow自动使用虚拟线程处理请求
# AI接口阻塞等待LLM时,虚拟线程挂起不占OS线程
# 理论上可以支持数万并发请求// 验证虚拟线程是否生效
@RestController
public class ThreadInfoController {
@GetMapping("/thread-info")
public Map<String, Object> threadInfo() {
Thread current = Thread.currentThread();
return Map.of(
"threadName", current.getName(),
"isVirtual", current.isVirtual(),
"threadId", current.threadId()
);
}
}方案二:使用WebFlux响应式编程
如果团队熟悉响应式编程,WebFlux配合Spring AI的Reactive API是最高效的方案:
/**
* 响应式AI控制器
* 使用WebFlux处理大量并发长连接
*/
@RestController
@RequestMapping("/ai/reactive")
@RequiredArgsConstructor
public class ReactiveAIController {
private final ChatClient chatClient;
/**
* 流式响应(SSE)
* 每生成一个token就立刻发给前端,用户体验好
* 且服务器不需要等待全部生成完才释放连接
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.stream()
.content()
// 添加超时,防止长时间没有数据
.timeout(Duration.ofSeconds(30))
// 错误处理:降级到错误提示
.onErrorReturn("AI服务暂时不可用,请稍后重试");
}
/**
* 批量并行处理(适合后台批处理任务)
* 同时发起N个请求,全部完成后返回
*/
@PostMapping("/batch")
public Flux<BatchResult> batchProcess(@RequestBody List<String> questions) {
// 控制并发数,避免同时发起过多请求打垮LLM服务
return Flux.fromIterable(questions)
.flatMap(question ->
Mono.fromCallable(() -> processQuestion(question))
.subscribeOn(Schedulers.boundedElastic()),
5 // 最大并发数5(根据Rate Limit调整)
);
}
private BatchResult processQuestion(String question) {
String answer = chatClient.prompt()
.user(question)
.call()
.content();
return new BatchResult(question, answer);
}
public record BatchResult(String question, String answer) {}
}连接池深度调优
package com.laozhang.ai.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import java.time.Duration;
/**
* Spring AI HTTP客户端连接池精细配置
*
* 关键参数说明:
* - maxConnections: 最大连接数,设置过小会排队,过大会耗尽资源
* - maxIdleTime: 空闲连接超时,LLM服务可能有空闲连接超时设置
* - maxLifeTime: 连接最大存活时间
* - pendingAcquireMaxCount: 等待获取连接的最大请求数
*/
@Configuration
@Slf4j
public class AIClientConfig {
@Value("${app.ai.http.max-connections:50}")
private int maxConnections;
@Value("${app.ai.http.connect-timeout-ms:10000}")
private int connectTimeoutMs;
@Value("${app.ai.http.read-timeout-s:120}")
private int readTimeoutSeconds;
/**
* 配置高性能HTTP连接池
*/
@Bean
public WebClient.Builder aiWebClientBuilder() {
// 连接池配置
ConnectionProvider connectionProvider = ConnectionProvider.builder("ai-pool")
// 最大连接数(根据QPS和平均响应时间调整)
// 公式:maxConnections >= 峰值QPS × 平均响应时间(s)
.maxConnections(maxConnections)
// 等待获取连接的最大请求数(超过则直接报错,快速失败)
.pendingAcquireMaxCount(100)
// 等待获取连接的最大时间(如果超时还没拿到连接则失败)
.pendingAcquireTimeout(Duration.ofSeconds(5))
// 空闲连接超时:30秒没用就释放(OpenAI服务端也有类似超时)
.maxIdleTime(Duration.ofSeconds(30))
// 连接最大存活时间:10分钟(防止使用过期连接)
.maxLifeTime(Duration.ofMinutes(10))
// 定期清理空闲连接
.evictInBackground(Duration.ofSeconds(60))
// 连接池指标(集成到Micrometer)
.metrics(true)
.build();
HttpClient httpClient = HttpClient.create(connectionProvider)
// 连接超时
.responseTimeout(Duration.ofSeconds(readTimeoutSeconds))
.option(io.netty.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS,
connectTimeoutMs);
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient));
}
}超时分层配置策略
AI接口的超时配置是个难题:设太短容易误判超时,设太长线程堆积。
我的分层超时策略:
# application.yml 完整超时配置
spring:
ai:
openai:
# 第一层:连接建立超时(TCP握手)
# 设为5-10秒,网络正常不会超时
connect-timeout: 10s
# 第二层:读取超时(等待第一个响应字节)
# 这是最关键的:LLM开始生成需要时间
# 非流式:设为60-90秒
# 流式:可以更长,因为会陆续收到数据
read-timeout: 90s
chat:
options:
model: gpt-4o-mini
# 第三层:LLM层的超时(通过max_tokens间接控制)
# max_tokens控制最大输出,输出越少越快
max-tokens: 2000
# 应用层超时(Spring MVC请求超时)
server:
tomcat:
# Tomcat接受连接的超时
connection-timeout: 120000 # 120秒(需要比read-timeout长)/**
* 按场景配置不同的超时策略
* 不同业务场景的响应时间期望不同,应该分别配置
*/
@Configuration
public class AITimeoutConfig {
/**
* 快速问答场景:严格超时(用于实时对话)
* 超过5秒直接失败,用户体验要求高
*/
@Bean
@Qualifier("fastChat")
public ChatClient fastChatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultOptions(OpenAiChatOptions.builder()
.withModel("gpt-4o-mini")
.withMaxTokens(500) // 限制输出,加快速度
.withTemperature(0.3f)
.build())
.build();
}
/**
* 长文档处理场景:宽松超时
* 允许更长时间,但输出也可以更多
*/
@Bean
@Qualifier("longDocument")
public ChatClient longDocumentClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultOptions(OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withMaxTokens(4000)
.build())
.build();
}
}重试与熔断配置
package com.laozhang.ai.resilience;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.RetryRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.function.Supplier;
/**
* AI服务弹性层
* 集成重试 + 熔断,防止LLM抖动影响整体服务
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class AIResilienceService {
private final ChatClient chatClient;
// 熔断器:连续5次失败则熔断,30秒后半开尝试恢复
private final CircuitBreaker circuitBreaker = CircuitBreaker.of(
"ai-service",
CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50%失败率触发熔断
.slowCallRateThreshold(80) // 80%慢调用触发熔断
.slowCallDurationThreshold(Duration.ofSeconds(10)) // 10秒算慢调用
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断30秒
.minimumNumberOfCalls(10) // 至少10次调用才统计
.permittedNumberOfCallsInHalfOpenState(3) // 半开状态允许3次
.build()
);
// 重试器:最多重试2次,仅对特定错误重试
private final Retry retry = Retry.of(
"ai-service",
RetryConfig.custom()
.maxAttempts(3) // 总共3次(1次原始 + 2次重试)
.waitDuration(Duration.ofMillis(500)) // 重试间隔500ms
// 只对Rate Limit错误重试(429),服务器错误不重试
.retryOnException(e -> e.getMessage() != null &&
e.getMessage().contains("429"))
.build()
);
/**
* 带熔断和重试的AI调用
*/
public String callWithResilience(String prompt, String fallbackMessage) {
Supplier<String> aiCall = CircuitBreaker.decorateSupplier(
circuitBreaker,
Retry.decorateSupplier(retry, () -> {
String result = chatClient.prompt()
.user(prompt)
.call()
.content();
log.debug("AI调用成功,结果长度: {}", result.length());
return result;
})
);
try {
return aiCall.get();
} catch (Exception e) {
log.warn("AI调用失败(熔断/重试耗尽): {}", e.getMessage());
return fallbackMessage;
}
}
/**
* 获取熔断器状态(用于监控和告警)
*/
public CircuitBreaker.State getCircuitBreakerState() {
return circuitBreaker.getState();
}
}压测数据与调优结论
我在一台4核8G的服务器上对Spring AI应用进行了压测,记录不同配置下的性能表现:
| 配置方案 | 并发数 | 平均延迟 | P99延迟 | 错误率 | 吞吐量(RPS) |
|---|---|---|---|---|---|
| 默认配置(200线程) | 200 | 3200ms | 8500ms | 12% | 45 |
| 增加线程池(500线程) | 300 | 3100ms | 7800ms | 5% | 68 |
| 开启虚拟线程 | 1000 | 2800ms | 6200ms | 0.5% | 210 |
| 虚拟线程+连接池调优 | 1000 | 2600ms | 5500ms | 0.2% | 245 |
| 虚拟线程+连接池+熔断 | 1000 | 2600ms | 5600ms | 0% | 243 |
关键发现:
- 开启虚拟线程是最大的单项优化,吞吐量提升3-4倍
- 连接池调优在高并发下非常关键
- 熔断器能彻底消除错误,但对正常请求影响极小
生产配置推荐值
# 生产环境推荐配置(4核8G服务器,预期峰值QPS 100-200)
spring:
threads:
virtual:
enabled: true # Java 21 必须开启
server:
tomcat:
threads:
max: 200 # 虚拟线程下可以设高一点,实际不会占OS线程
min-spare: 20
connection-timeout: 180000 # 3分钟,覆盖最慢的LLM响应
app:
ai:
http:
max-connections: 50 # OpenAI的Rate Limit约束下50个连接足够
connect-timeout-ms: 10000
read-timeout-s: 120