尧图精选

ai-agent框架spring ai alibaba (三)外部调用II-2 分布式MCP:Nacos注册中心下的多节点MCP服务发现与调用

🕒 发布时间:2026/10/1 7:07:56 📁 来源:尧图网络
1. 多节点 MCP Server 注册到 Nacos 后Agent 侧到底发生了什么如果你已经跑通过单机版的 Spring AI Alibaba MCP 示例接下来大概率会撞上同一个问题MCP Server 只有一个进程Agent 一重启它就断流量一上来它就顶不住改个工具描述还得把整个服务停掉重发。分布式 MCP 要解决的就是这件事——把 MCP Server 做成多实例注册到 NacosAgent 侧通过服务发现动态拿到实例列表调用时按负载均衡策略路由某个实例挂了自动摘除剩下的实例继续扛。这篇聚焦 Spring AI Alibaba 分布式 MCP 的落地链路Nacos 注册配置、MCP 客户端负载均衡参数、多实例启停脚本以及一次真实的故障摘除验证。核心检索词是 spring ai alibaba 分布式 MCP 服务发现与调用适合已经在用 Spring AI Alibaba 写 Agent、准备把 MCP 工具从单机推向多节点的 Java 开发者。读完你能自己搭出「两个 MCP Server 实例 一个 Agent 客户端」的最小集群并亲眼看到杀掉一个实例后调用自动切到另一个。先说清楚一个前提MCP 协议本身没有定义分布式规范它只管单条连接的会话、能力协商和工具调用。分布式这层是 Java 微服务体系补上来的——Nacos 负责注册发现和变更通知Spring AI Alibaba 的分布式 MCP Client 负责维护实例列表、做负载均衡、感知上下线。所以你会看到DistributedSyncMcpClient这类接口它内部持有一个McpSyncClient列表每次调用从列表里选一个可用实例。理解这一点后面的配置和排障就顺了。我试过把两个 MCP Server 实例注册到同一个 Nacos 命名空间Agent 侧只配一个服务名启动后日志里能看到它拉到了两个实例调用时轮询分发。下面按「前置准备 → 配置 → 验证 → 排障」的顺序展开每一步都给可复制的片段。2. Nacos 注册中心与 MCP Server 多实例前置配置2.1 版本对齐与依赖引入Spring AI Alibaba 的分布式 MCP 能力对版本比较敏感本文基于 spring-ai-alibaba v1.1.2.2、spring-ai v1.1.2、MCP Java SDK 0.17.0。Nacos 侧建议用 v3因为它原生支持 MCP 服务注册和发现AI 注册中心里能直接看到 MCP 服务类型和实例。版本错配最常见的表现是NacosMcpOperationService初始化失败或者注册上去的服务在控制台看不到 MCP 元数据。MCP Server 端引入注册器依赖MCP Client 端引入分布式客户端依赖。以 Maven 为例Server 端核心是NacosStatelessMcpRegisterAutoConfiguration所在的 starterClient 端是NacosMcpStreamableClientAutoConfiguration和NacosMcpToolCallbackAutoConfiguration所在的 starter。实际项目里通常一个模块同时引靠配置区分角色。!-- MCP Server 端注册到 Nacos -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-mcp-nacos-server/artifactId version1.1.2.2/version /dependency !-- MCP Client 端分布式发现与调用 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-mcp-nacos-client/artifactId version1.1.2.2/version /dependency2.2 Nacos 连接与命名空间配置Nacos 的连接信息走标准配置。这里有个容易踩的坑MCP 服务注册和普通微服务注册共用同一个 Nacos 地址但建议给 MCP 单独开一个命名空间或分组避免和业务微服务混在一起排查时清爽很多。分组名在 Server 和 Client 两侧必须一致否则 Client 订阅不到实例。spring: ai: alibaba: mcp: nacos: server-addr: 127.0.0.1:8848 namespace: mcp-dev group: MCP_GROUP username: nacos password: nacosserver-addr是 Nacos 地址namespace用命名空间 ID 而不是名称group是服务分组。Client 侧订阅时用的服务名就是 MCP Server 注册时上报的服务名默认取 MCP Server 的server.name配置。两侧对不上Client 拿到的实例列表就是空的。2.3 MCP Server 注册信息构成MCP Server 启动后注册器会做三件事兼容性检查、注册服务类型、注册服务实例。服务类型注册的 key 是「服务名 版本」如果已有相同服务注册会走兼容性检查比对工具规格是否一致。服务实例注册在onApplicationEvent里触发目的是过滤掉 management web serverSpring Boot Actuator 的端口拿到真正的业务端口。如果你的应用同时开了 actuator 和业务端口注册上去的实例端口可能是错的调用直接连不上。实例注册信息和普通微服务一样包含服务名、分组名、Instance 对象IP、端口、健康状态等。多个 MCP Server 实例用同一个服务名注册就自然组成了集群。Nacos 控制台的「AI 注册中心 / MCP 管理」里能看到服务类型和实例列表这是验证注册是否成功最直接的地方。注意多个 MCP Server 同时启动时服务类型注册可能出现碰撞——第一个注册成功后后面的会失败。NacosStatelessMcpRegister提供了容错但没有随机延时防碰撞机制也没有专用异常类型。生产环境建议错峰启动或者接受「类型注册失败但实例注册成功」的情况因为实例注册才是负载均衡真正依赖的。3. 可复制的分布式 MCP 客户端负载均衡配置3.1 客户端自动配置链路Client 侧的自动配置分两层。NacosMcpAutoConfiguration构建NacosMcpOperationService负责 Nacos 的读写订阅NacosMcpStreamableClientAutoConfiguration构建DistributedSyncMcpClient集合内部维护McpSyncClient列表并订阅实例变更NacosMcpToolCallbackAutoConfiguration构建DistributedSyncMcpToolCallbackProvider把分布式客户端包装成 Agent 能用的ToolCallback。应用层只需要注入ToolCallbackProvider取出ToolCallback集合后续像调用普通 Agent 工具一样调用 MCP 工具。分布式这层对上层是透明的——这是设计上比较舒服的地方Agent 代码不用改换配置就能从单机切到集群。3.2 负载均衡参数与轮询策略当前版本的负载均衡是轮询核心逻辑就一行int currentIndex index.getAndUpdate(i - (i 1) % syncClients.size());index是AtomicInteger每次调用自增并对实例数取模拿到下标后从syncClients列表里取对应实例。这个策略简单、无状态、分布均匀适合实例性能相近的场景。如果实例配置差异大轮询会把请求平均分给慢实例这时候需要加权策略——但当前版本没有暴露策略接口负载均衡是硬编码在实现类里的。从软件工程角度看这里有两个可以改进的点一是传输协议和分布式逻辑耦合SseWebFluxDistributedSyncMcpClient和StreamWebFluxDistributedSyncMcpClient代码相似度高达 97%只在与协议相关的少数几处不同二是负载均衡策略硬编码没有抽象成LoadBalanceStrategy接口。如果你要改造框架这两处是优先动刀的地方——把分布式代理逻辑和传输协议解耦把轮询、随机、加权抽成策略接口注入。3.3 客户端关键配置片段Client 侧除了 Nacos 连接还要配 MCP 服务名和协议类型。协议分 SSE 和 Streamable 两种对应不同的DistributedSyncMcpClient实现。Streamable 是较新的传输方式推荐优先用。spring: ai: alibaba: mcp: nacos: server-addr: 127.0.0.1:8848 namespace: mcp-dev group: MCP_GROUP client: enabled: true service-name: weather-mcp-server protocol: STREAMABLE request-timeout: 30sservice-name必须和 Server 注册的服务名一致protocol要和 Server 实际使用的协议匹配。request-timeout是单次工具调用的超时分布式场景下建议设短一点比如 30 秒避免某个慢实例拖住整个调用链。3.4 多实例启停脚本本地验证多实例最省事的方式是用不同端口起两个进程。下面这个脚本用--server.port覆盖端口两个实例注册同一个服务名。#!/bin/bash # start-mcp-cluster.sh JARweather-mcp-server.jar GROUPMCP_GROUP start_instance() { local port$1 nohup java -jar $JAR \ --server.port$port \ --spring.ai.alibaba.mcp.nacos.group$GROUP \ mcp-$port.log 21 echo started instance on port $port, pid$! } stop_instance() { local port$1 pid$(ps -ef | grep server.port$port | grep -v grep | awk {print $2}) if [ -n $pid ]; then kill -9 $pid echo stopped instance on port $port, pid$pid fi } case $1 in start) start_instance 8081; start_instance 8082 ;; stop) stop_instance 8081; stop_instance 8082 ;; stop-one) stop_instance $2 ;; *) echo usage: $0 {start|stop|stop-one port} ;; esacstart起两个实例stop全停stop-one 8081只停一个——这个命令就是后面验证故障摘除用的。脚本里用nohup后台运行日志分别写到mcp-8081.log和mcp-8082.log方便对照。4. 验证请求与故障摘除自动切换4.1 启动集群并确认注册先起 Nacos再执行./start-mcp-cluster.sh start。等十几秒去 Nacos 控制台的 AI 注册中心看weather-mcp-server这个服务实例数应该是 2两个实例的 IP 相同、端口分别是 8081 和 8082健康状态都是健康。如果只看到一个实例检查两个进程的server.port是否真的不同以及日志里有没有注册失败的异常。Client 侧启动后日志里会打印订阅到的实例列表。搜DistributedSyncMcpClient或syncClients关键字能看到类似「discovered 2 instances for weather-mcp-server」的输出。这一步确认 Client 确实拿到了两个实例而不是只拿到一个或者零个。4.2 发起调用观察轮询写一个最简单的 Agent 调用让模型调用 MCP 工具查天气。连续调用四次观察两个实例的日志。轮询策略下请求应该交替出现在 8081 和 8082 的日志里各两次。如果四次都打到同一个实例说明实例列表里只有一个或者轮询的index没有正确自增。RestController public class AgentController { private final ChatClient chatClient; public AgentController(ChatClient.Builder builder, ToolCallbackProvider toolCallbackProvider) { this.chatClient builder .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks()) .build(); } GetMapping(/ask) public String ask(RequestParam String q) { return chatClient.prompt(q).call().content(); } }ToolCallbackProvider由自动配置注入getToolCallbacks()返回的就是分布式 MCP 工具集合。Agent 侧完全感知不到背后是两个实例它只看到一组工具。4.3 故障摘除验证这是分布式 MCP 最关键的一步。保持 Agent 持续调用执行./start-mcp-cluster.sh stop-one 8081杀掉 8081 实例。Nacos 会在心跳超时后把 8081 标记为不健康并摘除Client 侧通过订阅变更收到通知更新syncClients列表。之后的调用应该全部落到 8082不再出现连接 8081 的报错。验证时注意两点一是摘除有延迟Nacos 默认心跳间隔和超时时间决定了摘除速度通常几秒到十几秒二是 Client 的订阅回调要正确触发如果subscribe没生效Client 会继续往已挂的实例发请求报连接拒绝。日志里看到removed instance 8081之类的输出就说明变更通知生效了。提示如果你在摘除瞬间看到一两次调用失败属于正常现象——变更通知到达 Client 之前可能还有请求发往已挂实例。生产环境靠重试兜底或者把超时设短让失败快速暴露并重试到健康实例。5. 分布式 MCP 常见报错排查5.1 401 与鉴权失败Nacos 开了鉴权但配置里没填username/password或者填错注册和订阅都会失败。表现是 Server 启动日志里NacosException: user not found或403Client 侧拿不到实例列表。检查spring.ai.alibaba.mcp.nacos.username和password是否和 Nacos 控制台一致。如果 Nacos 没开鉴权这两项留空即可填了反而可能报错。5.2 local proxy failed 与连接拒绝这个报错通常出现在 Client 往已挂实例发请求时底层是Connection refused。原因有两种一是实例已挂但 Client 还没收到摘除通知二是注册上去的实例端口是错的比如注册了 actuator 端口而不是业务端口。前者等几秒自动恢复后者要检查onApplicationEvent里过滤 management web server 的逻辑是否生效。如果你的应用有多个 web server注册器可能选错端口。5.3 reading choices 与响应解析失败reading choices类报错一般出现在模型侧和 MCP 本身关系不大但如果 MCP 工具返回的 schema 和模型期望的不一致也可能触发。检查 MCP 工具的输入 schema 是否合法McpToolSpecification里的inputSchema是否符合 JSON Schema 规范。工具描述里如果有特殊字符也可能导致解析异常。5.4 OAuth 与安全配置MCP security 和 OAuth2 是另一个话题但如果你的 MCP Server 开了鉴权Client 侧没配对应的 token调用会返回 401。分布式场景下鉴权信息要在 Client 侧统一配置不能指望每个实例单独处理。当前版本的分布式 MCP 对 OAuth 的支持还在演进复杂鉴权场景建议先在单机验证通过再上集群。5.5 实例列表为空Client 拿不到实例按这个顺序查Server 是否真的注册成功看 Nacos 控制台、Client 的service-name和group是否和 Server 一致、命名空间是否一致、协议类型是否匹配。这四项里任何一项对不上实例列表都是空的。日志里搜subscribe和discovered能看到订阅和发现的详细过程。6. 从单机到集群分布式 MCP 的接入路径把 MCP 工具从单机推到多节点核心就三件事Server 侧注册到 Nacos、Client 侧订阅并维护实例列表、调用时按策略路由。Spring AI Alibaba 把这三件事封装在自动配置里应用层几乎不用改代码换配置就能从单机切到集群。当前版本的负载均衡是轮询策略硬编码传输协议和分布式逻辑耦合较紧——如果你要深度定制这两处是改造入口。实际落地时建议先用两个本地实例把链路跑通确认注册、发现、轮询、摘除都正常再上生产。生产环境还要考虑 Nacos 集群、实例健康检查、超时重试这些微服务常规问题。MCP 的分布式能力本质上是复用了 Java 微服务的成熟体系所以你在微服务里踩过的坑这里大概率还会再踩一遍提前有心理准备就好。如果你在配置过程中需要确认模型侧的调用效果可以到模型对话页面直接试接入相关的 Key 和文档在 API Keys 与接入文档里长期跑编码类 Agent、需要稳定调用额度的可以看 Coding Plan。把分布式 MCP 的注册发现链路和模型调用链路分开验证排障会快很多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →