ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring AI 生产环境配置清单与容错最佳实践

Spring AI 生产环境配置清单与容错最佳实践 Spring AI 生产环境配置清单与容错最佳实践把大模型接口接入业务系统并不难写个 Controller 调一下 SDK 半天就能上线。但一旦业务流量上来模型端偶尔抖动、网络偶发丢包、上下文超长触发限流或者下游供应商突发宕机原先写得轻巧的代码就会迅速引发连接池耗尽、线程打满甚至直接拖垮核心业务。在生产环境中落地 Spring AI不能当作普通 HTTP 客户端来对待。大模型调用兼具高延迟、高消耗、响应体积大以及流式长连接等特征。这里把我们在生产环境摸爬滚打总结出来的核心配置清单与容错方案做一次完整梳理。一、HTTP 传输层与客户端选型Spring AI 默认支持通过 RestClient 或 WebClient 与大模型服务交互。很多同学在本地测试使用默认配置进入生产后立刻遇到了连接泄漏与超时失控的问题。1. 放弃默认 SimpleClientHttpRequestFactory默认的 JDK 原生 HttpURLConnection 缺少连接池复用与细粒度的超时控制在并发场景下会频繁创建 TCP 连接导致 TIME_WAIT 堆积。推荐切换到底层由 Apache HttpClient 5 或 OkHttp 支持的 ClientHttpRequestFactory。2. 超时参数必须分级配置大模型的首字延迟TTFT通常在几百毫秒到数秒不等完整内容生成可能长达几十秒。必须将“建连超时”、“读取超时”以及“连接池借出超时”明确拆分spring: ai: openai: api-key: ${AI_OPENAI_API_KEY} base-url: ${AI_GATEWAY_URL:https://api.openai.com} chat: options: model: gpt-4o-mini temperature: 0.3 max-tokens: 2048 # 底层 HTTP 连接池优化 http: client: max-total-connections: 200 max-per-route: 50 connect-timeout-ms: 3000 socket-timeout-ms: 60000 connection-request-timeout-ms: 2000在配置类中定制 RestClient.Builder避免全局共享线程阻塞package com.yali.ai.config; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.core5.util.Timeout; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestClient; import java.util.concurrent.TimeUnit; Configuration public class AiHttpClientConfig { Bean public RestClientCustomizer restClientCustomizer() { PoolingHttpClientConnectionManager connectionManager new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(200); connectionManager.setDefaultMaxPerRoute(50); RequestConfig requestConfig RequestConfig.custom() .setConnectTimeout(Timeout.of(3, TimeUnit.SECONDS)) .setResponseTimeout(Timeout.of(60, TimeUnit.SECONDS)) .setConnectionRequestTimeout(Timeout.of(2, TimeUnit.SECONDS)) .build(); CloseableHttpClient httpClient HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(requestConfig) .evictExpiredConnections() .evictIdleConnections(Timeout.of(30, TimeUnit.SECONDS)) .build(); return restClientBuilder - restClientBuilder .requestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)); } }二、容错体系重试风暴拦截与断路降级大模型服务遇到 429Rate Limit或 503Service Unavailable属于家常便饭。盲目重试非但救不回请求反而会成倍放大上游压力。1. 智能退避重试Exponential Backoff重试逻辑必须满足三个条件只对特定状态码429、500、502、503、504和网络 I/O 超时进行重试引入指数退避加抖动Jitter防止集群多节点同时重试形成共振单个请求的最大重试次数严控在 2 次以内。结合 Resilience4j 实现声明式保护resilience4j: retry: instances: aiChatService: max-attempts: 3 wait-duration: 1000ms exponential-backoff-multiplier: 2.0 randomize-wait: true retry-exceptions: - org.springframework.web.client.ResourceAccessException - org.springframework.web.client.HttpServerErrorException ignore-exceptions: - org.springframework.web.client.HttpClientErrorException.BadRequest - org.springframework.web.client.HttpClientErrorException.Unauthorized circuitbreaker: instances: aiChatService: sliding-window-type: COUNT_BASED sliding-window-size: 20 minimum-number-of-calls: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 15s permitted-number-of-calls-in-half-open-state: 3 slow-call-duration-threshold: 8000ms slow-call-rate-threshold: 702. 双模型主备无缝降级当主模型服务商响应超时或进入熔断状态时业务层不能直接对用户抛出错误弹窗应当优雅切换到备用模型或离线规则缓存。package com.yali.ai.service; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.ChatClient; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; Service public class RobustAiChatService { private static final Logger log LoggerFactory.getLogger(RobustAiChatService.class); private final ChatClient primaryChatClient; private final ChatClient backupChatClient; public RobustAiChatService( Qualifier(primaryChatClient) ChatClient primaryChatClient, Qualifier(backupChatClient) ChatClient backupChatClient) { this.primaryChatClient primaryChatClient; this.backupChatClient backupChatClient; } Retry(name aiChatService) CircuitBreaker(name aiChatService, fallbackMethod fallbackToBackupModel) public String generateContent(String systemPrompt, String userMessage) { return primaryChatClient.prompt() .system(systemPrompt) .user(userMessage) .call() .content(); } public String fallbackToBackupModel(String systemPrompt, String userMessage, Throwable throwable) { log.warn(主模型调用失败触发备用模型降级。原因: {}, throwable.getMessage()); try { return backupChatClient.prompt() .system(systemPrompt) .user(userMessage) .call() .content(); } catch (Exception ex) { log.error(备用模型亦调用失败返回兜底话术, ex); return 当前智能助手正处于用量高峰请稍后重试或联系人工客服。; } } }三、流式输出与背压控制SSE / Flux很多业务场景使用 SSEServer-Sent Events向前端推流。这里存在一个经典的生产隐患如果客户端网络变慢或主动断开连接后端的模型请求是否会继续空跑并白白消耗 Token答案是如果不做处理Spring AI 仍会接收完整个 HTTP 响应。必须在流式管道中显式处理生命周期取消信号package com.yali.ai.controller; import com.yali.ai.service.StreamAiService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; RestController public class StreamChatController { private static final Logger log LoggerFactory.getLogger(StreamChatController.class); private final StreamAiService streamAiService; public StreamChatController(StreamAiService streamAiService) { this.streamAiService streamAiService; } GetMapping(value /api/chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString streamChat(RequestParam String prompt) { return streamAiService.streamPrompt(prompt) .doOnCancel(() - log.info(客户端主动断开连接已停止下游模型消费)) .doOnError(e - log.error(流式推送发生异常: {}, e.getMessage())) .onErrorResume(e - Flux.just([服务异常生成中断])); } }四、生产可观测性与 Token 消耗审计调用外部 AI 接口不仅关系到可用性更关系到真金白银的账单。在 Spring AI 中每次交互都必须精确记录 Prompt Tokens、Completion Tokens 以及耗时指标。推荐通过ChatModelObservationConvention或自定义拦截器将关键指标上报至 Prometheus / Micrometerpackage com.yali.ai.advisor; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; import org.springframework.ai.chat.metadata.Usage; import org.springframework.stereotype.Component; Component public class TokenMetricsAdvisor implements CallAroundAdvisor { private final Counter promptTokenCounter; private final Counter completionTokenCounter; public TokenMetricsAdvisor(MeterRegistry registry) { this.promptTokenCounter registry.counter(ai.tokens.prompt.total); this.completionTokenCounter registry.counter(ai.tokens.completion.total); } Override public String getName() { return TokenMetricsAdvisor; } Override public int getOrder() { return 0; } Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { AdvisedResponse response chain.nextAroundCall(advisedRequest); if (response.response() ! null response.response().getMetadata() ! null) { Usage usage response.response().getMetadata().getUsage(); if (usage ! null) { promptTokenCounter.increment(usage.getPromptTokens()); completionTokenCounter.increment(usage.getGenerationTokens()); } } return response; } }五、落地总结与上线检查清单在正式将 Spring AI 业务推向生产环境前建议按照以下五项核心检查点逐一对账连接池与生命周期是否剔除了默认简单 HTTP 工厂并配置了空闲连接定期回收策略超时梯度隔离建连超时是否控制在 3 秒以内单次模型交互是否设置了合理的上限避免慢调用占满 Worker 线程熔断与主备切换是否配置了 Resilience4j 状态熔断且定义了兜底模型或离线文案前端断连保护流式推送在用户关掉页面时是否能及时传播 Cancel 信号终止云端 Token 计费精细化审计打点是否记录了租户维度的 Token 用量分布与延迟直方图便于做容量规划与异常排查。把这些基础设施层面的护栏打扎实业务层的 Prompt 编排与 Agent 流程才能真正稳当运行。
返回列表