版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
手打不易,如果转摘,请注明出处!
本文链接:
https://zhangxiaofan.blog.csdn.net/article/details/161204324
目录
[方法三:换 MCP 协议为 STATELESS 无状态协议](#方法三:换 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协议简介
问题分析前,我们有必要先了解下MCP,MCP(Model Context Protocol) 是Anthropic提出的标准化协议,旨在为AI模型建立统一的上下文交互接口,使其能够安全、可控地连接外部工具、数据源及服务。Spring AI 对该协议提供了完整的服务端实现,并针对不同场景抽象出三种传输协议,以适应多样化的部署和交互需求。
| 特性 | SSE | STREAMABLE | STATELESS |
|---|---|---|---|
| 通信模式 | 单向推送(服务端→客户端) | 客户端发起请求 + 服务端流式响应 | 请求-响应(双向一次性) |
| 连接类型 | 持久长连接 | 短期长连接(请求生命周期内) | 短连接 |
| 会话状态 | 需要 Session | 需要 Session | 无 Session |
| 客户端请求方式 | GET(建立连接)+ 额外 POST | POST(带请求体,流式接收) | POST(同步返回) |
| 实时推送能力 | ✅ 支持 | ✅ 支持 | ❌ 不支持 |
| 部署复杂度 | 需保持会话粘滞 | 需保持会话粘滞 | 无状态,易于水平扩展 |
| 适合的调用模式 | 持续监听、事件驱动 | 流式交互、AI 流式生成 | 一次性工具调用、查询 |
配置示例:
# SSE协议(传统)
spring.ai.mcp.server.protocol=SSE
# Streamable HTTP协议(推荐)
spring.ai.mcp.server.protocol=STREAMABLE
# Stateless协议(云原生首选)
spring.ai.mcp.server.protocol=STATELESS
环境
JDK:21、SpringBoot 3.4
服务部署:微服务、多实例分布式部署
网络:双层ALB架构
请求先通过第一层ALB负载均衡,轮询到2个后端集群,每个后端集群又有一层ALB,轮询请求到5个pod,合计10个pod。
Maven配置
MCP服务器maven配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.6</version>
</parent>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>1.1.2</version>
</dependency>
application.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 中的任意一个;
- 正常情况:同一会话的所有请求命中同一个 Pod,Pod 本地存在 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,易于通过防火墙和负载均衡器,同时可以自动重连,适合云端部署。相较于需要双向长连接的 WebSocket,SSE 更轻量,且足以满足 MCP 非对称通信的需求
结论
经过分析Spring AI MCP Server在分布式环境下的Session丢失问题,明确问题根因是有状态协议与无会话保持负载均衡的组合冲突。针对不同业务场景,一般有三种解决方案:
-
ALB会话粘滞:适合快速解决,无需代码修改
-
Redis分布式Session:适合需要Session持久化的场景
-
STATELESS协议:适合无状态工具,是最简洁的方案
对于本文案例的TimeTool工具,工具本身无需支持有状态,那么采用STATELESS协议是最优解,仅需修改一行配置即可彻底解决问题。
建议开发者在设计MCP工具时,优先评估工具的状态依赖特性,选择合适的协议类型,避免在分布式部署时遇到类似问题。