Spring AI MCP Server分布式翻车现场:Streamable协议的甜蜜与危险,以及无状态救赎
版权声明本文为博主原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接和本声明。手打不易如果转摘请注明出处本文链接https://zhangxiaofan.blog.csdn.net/article/details/161204324目录问题现象MCP协议简介环境Maven配置问题分析初步猜测网络调用链问题解决方法一ALB开启会话粘滞会话保持方法二实现基于Redis的分布式Session共享方法三换 MCP 协议为 STATELESS 无状态协议问题总结结论参考资料问题现象Spring AI项目开发了mcp接口http://api.test.com/mcp/timetool协议配置的是streamable。本地和测试环境调用都正常但是一旦部署到生产后通过postman请求MCP接口http://api.test.com/mcp/timetool偶尔成功偶尔失败失败信息如下{ cause: null, jsonRpcError: null, message: Session not found: bdc41b7a-9e25-4481-8f1b-fdcd939e3e17, suppressed: [], localizedMessage: Session not found: bdc41b7a-9e25-4481-8f1b-fdcd939e3e17 }MCP协议简介问题分析前我们有必要先了解下MCPMCPModel Context Protocol是Anthropic提出的标准化协议旨在为AI模型建立统一的上下文交互接口使其能够安全、可控地连接外部工具、数据源及服务。Spring AI 对该协议提供了完整的服务端实现并针对不同场景抽象出三种传输协议以适应多样化的部署和交互需求。特性SSESTREAMABLESTATELESS通信模式单向推送服务端→客户端客户端发起请求 服务端流式响应请求-响应双向一次性连接类型持久长连接短期长连接请求生命周期内短连接会话状态需要 Session需要 Session无 Session客户端请求方式GET建立连接 额外 POSTPOST带请求体流式接收POST同步返回实时推送能力✅ 支持✅ 支持❌ 不支持部署复杂度需保持会话粘滞需保持会话粘滞无状态易于水平扩展适合的调用模式持续监听、事件驱动流式交互、AI 流式生成一次性工具调用、查询配置示例# SSE协议传统 spring.ai.mcp.server.protocolSSE # Streamable HTTP协议推荐 spring.ai.mcp.server.protocolSTREAMABLE # Stateless协议云原生首选 spring.ai.mcp.server.protocolSTATELESS环境JDK21、SpringBoot 3.4服务部署微服务、多实例分布式部署网络双层ALB架构请求先通过第一层ALB负载均衡轮询到2个后端集群每个后端集群又有一层ALB轮询请求到5个pod合计10个pod。Maven配置MCP服务器maven配置parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.6/version /parent dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.1.2/version /dependencyapplication.yaml配置spring: ai: mcp: server: name: streamable-mcp-server # MCP 服务器名称 version: 1.0.0 # 服务器版本 type: ASYNC # SYNC:客户端发起请求后线程会阻塞等待工具执行完成,ASYNC:不会阻塞当前线程,执行完成后异步响应 request-timeout: 20s # 请求超时默认 20s protocol: STREAMABLE # 协议类型简单理解就是 STREAMABLE 是 SSE 的升级版,可选SSE, STREAMABLE,STATELESS streamable-http: mcp-endpoint: /mcp/timetool # MCP 端点路径 keep-alive-interval: 30s # 保持连接间隔 disallow-delete: false # 是否禁用 DELETE 方法问题分析初步猜测现象有个特点偶现那我们可以大概猜测问题的原因在于三个因素首先STREAMABLE协议是有状态的需要服务端维护Session其次两层ALB采用轮询策略缺乏会话保持机制最后Spring AI MCP Server默认将Session存储在Pod本地内存中。当同一Session的后续请求被路由到其他Pod时目标Pod既无Session元数据也无对应的SSE连接导致Session not found错误。网络调用链整个网络调用链路如下客户端请求先抵达第一层 ALB通过轮询分发至集群 A / 集群 B的第二层 ALB第二层 ALB 再次通过轮询将请求分发至集群内 5 个 Pod 中的任意一个正常情况同一会话的所有请求命中同一个 PodPod 本地存在 Session请求成功异常情况同一会话的请求被轮询到其他 Pod目标 Pod 无该 Session直接抛错。最终通过本地启动2个MCP Server实例端口8080、8081模拟证实是该原因导致。问题解决方法一ALB开启会话粘滞会话保持强制同一个客户端的所有请求始终路由到同一个 Pod保证 Session 不跨节点。关闭两层的ALB的轮询策略开启基于Cookie或其他字段的会话保持。阿里云、华为云、腾讯云等配置大同小异大概步骤如下粘滞方式基于 Cookie 粘滞优先/ 源 IP 粘滞粘滞超时时间≥MCP 的keep-alive-interval你配置的 30s建议设为 60s禁用 ALB 的连接复用 / 强制轮询保证会话绑定。该方法无需改动代码仅配置ALB即可。方法二实现基于Redis的分布式Session共享将会话信息从 Pod 内存移至Redis让所有 Pod 共享会话任意 Pod 都能处理同一 Session 的请求。考虑到需要一定开发量而且STREAMABLE协议下 SSE 连接与底层流绑定仅仅存储会话元数据可能不够还需要处理流的多播。即使Session元数据共享了后续请求到达其他Pod时那个Pod可能没有对应的SSE连接来推送结果。因此除了共享Session之外还需要实现SSE消息的多播机制如通过Redis Pub/Sub替代SSE以支持双向通信。该方案对代码改造较大大家可参考官方MCP文档实现https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html方法三换 MCP 协议为 STATELESS 无状态协议作者写的测试工具TimeTool工具是无会话状态依赖纯无状态接口直接将协议改为STATELESS就可以抛弃 Session 。spring: ai: mcp: server: protocol: STATELESS # 不再产生服务端会话每个请求独立处理作者最终采用的方法三因为demo中开发的MCP工具仅为一次性请求-响应根本不需要保持长连接和流式推送。问题总结初期想法是用Java开发一个MCP工具心想MCP工具要支持流式理所应当的认为应该设置spring.ai.mcp.server.protocol STREAMABLE实际上我把MCP工具本身 和 server 支持流式弄混淆了这是两个不同维度的对象它们其实是2个概念。一个是MCP工具本身一个是服务server支持的MCP协议。当大模型决定调用一个 MCP 工具时过程通常是模型暂停文本生成发出一个tool_use指令。客户端或服务端执行该工具等待完整的工具结果。将完整结果作为一条tool_result消息塞回对话上下文。对工具这一层而言结果是一次性的针对当前MCP工具本身来说即使MCP工具支持流式对AI来说据了解目前主流框架也是要等待完整结果。那么MCP 协议支持SSE、STREAMABLE 意义是什么主要有如下作用非阻塞响应如果使用简单的 HTTP 一问一答客户端在发起工具调用后必须一直阻塞等待执行完毕期间不能做任何其他事除非使用回调或轮询。而有了 SSE 通道客户端发出调用请求后可立即释放连接继续处理其他任务比如同时触发多个工具等结果到来时通过 SSE 事件自然获得通知。这极大地提高了并发和资源利用率。服务端主动推送其他事件工具并不是 MCP 的全部。MCP 还涉及资源resources、提示模板prompts等的动态更新。通过 SSE服务端可以随时向客户端推送资源变更、工具状态变化等通知客户端无需轮询。这对于构建动态工具生态至关重要。长时运行工具的进度反馈未来可能支持虽然当前 MCP 规范并未强制要求工具结果必须以流式分块返回但 SSE 通道天然支持服务端在执行工具时不断推送“中间日志”、“进度百分比”等事件只要 MCP 社区后续扩展协议支持。这正是之前代码中Flux想达到的效果但需要协议层面约定好。一旦支持你的流式时间输出就能真正逐条推送给客户端而客户端可以缓存这些中间数据待完成后再组装完整结果交给模型。这种能力完全依赖于 SSE 这类持久化连接。跨网络防火墙与长连接保持SSE 基于 HTTP易于通过防火墙和负载均衡器同时可以自动重连适合云端部署。相较于需要双向长连接的 WebSocketSSE 更轻量且足以满足 MCP 非对称通信的需求结论经过分析Spring AI MCP Server在分布式环境下的Session丢失问题明确问题根因是有状态协议与无会话保持负载均衡的组合冲突。针对不同业务场景一般有三种解决方案ALB会话粘滞适合快速解决无需代码修改Redis分布式Session适合需要Session持久化的场景STATELESS协议适合无状态工具是最简洁的方案对于本文案例的TimeTool工具工具本身无需支持有状态那么采用STATELESS协议是最优解仅需修改一行配置即可彻底解决问题。建议开发者在设计MCP工具时优先评估工具的状态依赖特性选择合适的协议类型避免在分布式部署时遇到类似问题。参考资料Anthropic MCP SpecificationSpring AI MCP Server DocumentationServer-Sent Events (SSE) Specification