实战干货:我是如何解决 Spring 调用 MCP 服务响应慢这一痛点的

张开发
2026/4/7 14:42:30 15 分钟阅读

分享文章

实战干货:我是如何解决 Spring 调用 MCP 服务响应慢这一痛点的
一、问题现象我们使用 Spring Boot 2.7 RestTemplate 调用下游 MCP 服务HTTP JSON。压测时发现平均响应时间320ms正常应在 100ms 内P99 响应时间2100ms服务端 CPU 和内存均正常无慢 SQL 或 GC 问题调用方Spring线程大量处于 TIMED_WAITING 状态初步判断瓶颈不在服务端而在客户端调用链路。二、定位过程1. 确认网络延迟用 curl 直接调用 MCP 服务耗时仅 45ms。说明网络和服务端处理没有问题问题出在 Spring 调用封装层。2. 检查 RestTemplate 配置发现用的是默认的 RestTemplate没有配置连接池和超时。默认使用 SimpleClientHttpRequestFactory每个请求都会新建连接且没有连接复用。3. 抓包分析用 Wireshark 看到大量 TCP 三次握手和四次挥手每个请求都重新建立连接TCP 握手耗时 TLS 握手如果是 HTTPS 占了大头。4. 日志分析打印调用耗时发现 RestTemplate.exchange() 之前有一段莫名其妙的延迟~150ms后来定位到是因为在过滤器里做了同步的用户信息解析阻塞了 IO。三、解决方案汇总针对定位到的几个问题我逐一做了优化下面给出核心代码实现。四、核心优化代码1. 配置带连接池的 RestTemplatejavaConfigurationpublic class RestTemplateConfig {Beanpublic RestTemplate restTemplate() {// 使用 HttpClient 连接池PoolingHttpClientConnectionManager connectionManager new PoolingHttpClientConnectionManager();connectionManager.setMaxTotal(200); // 最大连接数connectionManager.setDefaultMaxPerRoute(50); // 每个路由最大并发RequestConfig requestConfig RequestConfig.custom().setConnectTimeout(3000) // 连接超时 3s.setSocketTimeout(5000) // 读取超时 5s.setConnectionRequestTimeout(2000) // 从连接池获取连接超时.build();CloseableHttpClient httpClient HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE) // 保持长连接.build();HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(httpClient);return new RestTemplate(factory);}}2. 改用 WebClient 实现异步非阻塞调用javaConfigurationpublic class WebClientConfig {Beanpublic WebClient webClient(WebClient.Builder builder) {return builder.baseUrl(http://mcp-service).defaultHeader(Content-Type, application/json).clientConnector(new ReactorClientHttpConnector(HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000).responseTimeout(Duration.ofSeconds(5)).doOnConnected(conn -conn.addHandlerLast(new ReadTimeoutHandler(5)).addHandlerLast(new WriteTimeoutHandler(3))).compress(true) // 开启压缩)).build();}}调用示例javaServicepublic class McpService {private final WebClient webClient;public McpService(WebClient webClient) {this.webClient webClient;}public MonoMcpResponse callMcp(McpRequest request) {return webClient.post().uri(/api/endpoint).bodyValue(request).retrieve().bodyToMono(McpResponse.class).timeout(Duration.ofSeconds(5)).retryWhen(Retry.backoff(2, Duration.ofMillis(100))); // 重试策略}}在 Controller 中返回 Mono充分利用 Spring WebFlux 的非阻塞特性。3. 使用本地缓存减少重复调用对于不经常变化的数据如用户权限、配置信息引入 Caffeine 缓存javaConfigurationpublic class CacheConfig {Beanpublic CacheString, UserAuth userAuthCache() {return Caffeine.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES).recordStats().build();}}4. 开启 HTTP 压缩服务端和客户端在 MCP 服务端如 Tomcat配置压缩propertiesserver.compression.enabledtrueserver.compression.mime-typesapplication/json,application/xmlserver.compression.min-response-size1024客户端自动处理 Accept-Encoding: gzipRestTemplate 和 WebClient 默认支持无需额外配置。五、优化效果压测结果100 并发持续 5 分钟指标 优化前 优化后 提升平均响应时间 320 ms 62 ms 81% ↓P99 响应时间 2100 ms 145 ms 93% ↓吞吐量 (TPS) 280 1520 5.4 倍 ↑调用线程阻塞数 经常满 (200) 10 极大改善同时服务端 CPU 使用率也从 45% 下降到 22%因为减少了大量无用的连接建立和销毁开销。六、经验总结永远不要用默认的 RestTemplate 去做生产环境的高频调用连接池是标配。超时必须设置否则一个慢服务会拖垮整个调用链。异步化是提升吞吐量的利器Spring WebFlux WebClient 非常适合 IO 密集型场景。性能问题先从客户端找原因不要一上来就怀疑服务端。善用工具Wireshark网络、Arthas线程栈、JMeter压测、SkyWalking链路追踪。最后如果你的 MCP 服务不是 HTTP 协议而是基于 TCP 或 gRPC优化思路类似连接池、多路复用、异步、超时、压缩、缓存 六大法宝。希望这篇文章能帮到你。如果你也有类似的踩坑经验欢迎在评论区交流Spring AI 实战视频课程地址https://edu.csdn.net/course/detail/41074

更多文章