Java 转 Agent 开发之路(十五):部署边界——单体、分布式、A2A 与 Nacos 注册

举报
小不点和猫 发表于 2026/11/03 08:35:37 2026/11/03
【摘要】 当交易 Agent 需要由支付团队维护、搜索 Agent 需要由搜索团队维护,当某个 Agent 的流量增长曲线和其他 Agent 完全不同,当 Python 侧的实验性 Agent 需要和 Java 侧的事务性系统协作时,“所有 Agent 在一个进程里”就不再是一个中性的技术选择,而是一个组织矛盾和可用性风险的藏身之处。Spring AI Alibaba 的文档把单进程多 Agent 的...

当交易 Agent 需要由支付团队维护、搜索 Agent 需要由搜索团队维护,当某个 Agent 的流量增长曲线和其他 Agent 完全不同,当 Python 侧的实验性 Agent 需要和 Java 侧的事务性系统协作时,“所有 Agent 在一个进程里”就不再是一个中性的技术选择,而是一个组织矛盾和可用性风险的藏身之处。Spring AI Alibaba 的文档把单进程多 Agent 的代价说得很直接:组织协同困难(两个团队维护同一份代码)、可用性难保证(一个 Agent 崩溃拖垮全部)、存在安全风险(内存与上下文共享导致权限边界模糊)。

这一篇要回答的是:什么时候该从单体走到进程边界,A2A 协议和 Nacos 注册各自解决哪一段问题,以及跨语言双栈的边界应该画在哪里。核心判断先放在前面:进程边界的意义不在于“多了几个 Agent”,而在于把故障域、发布节奏和权限边界从“代码约定”变成“物理隔离” 。如果拆完之后所有 Agent 仍然共享同一个数据库、同一个安全上下文、同一条发布流水线,拆分只是增加了网络跳数。

一、单体阶段:模块化不是为了好看,是为了让替换半径可控

在讨论拆进程之前,先确认单体阶段应该做什么。一个没有被模块化的单体,拆进程时付出的代价不是“搬家”,是“考古” ——依赖纠缠在一起,你无法判断哪个类属于哪个边界。

第六篇已经给出了一个参考的包结构:config(模型和向量库配置)、client(ChatClient 封装)、advisor(记忆和上下文策略)、rag(文档 ETL 与检索)、tool(业务工具)、mcp(外部工具适配)、workflow(任务编排)、eval(评测用例)、tenant(多租户过滤)。这个拆分的核心目的不是“看起来整洁”,而是让每一个模块的替换和迁移有一个明确的接缝:把 Redis 记忆换成数据库,只动 advisor 和 config;把本地检索换成远程 MCP 检索服务,只动 tool 和 mcp;把 Planning 从进程内循环改成远程 A2A 调用,只动 workflow。

单体阶段的合理目标不是“永远不拆”,而是“当拆分到来时,被拆出去的那个模块已经是一个边界清晰的单元” 。如果一个 ReactAgent 的构造、工具注册、Prompt 拼装和业务校验全部散落在 Controller 和 Service 里,那么它被拆成独立 Agent 服务的那一天,等于要重新理解一遍整条链路。模块化的价值在那一刻兑现。

二、什么时候该拆进程:三个判断条件,以及“一个都不该拆”的反面

拆分决策和十年前“单体要不要拆微服务”是同一类问题,但 Agent 的拆分比微服务更复杂,因为 Agent 的行为不是确定的,拆分之后的路由错误和上下文损耗更难定位。一个可操作的判断框架是三个条件:

条件一:Prompt 是否已经膨胀到难以维护。 一个 Agent 要处理知识检索、工单查询、转人工三个场景,System Prompt 里就要定义三套规则。每加一个新场景,Prompt 就长一段,改一句担心影响全局。当 Prompt 的场景数量和条件分支超过了单个人能在头脑中完整维护的规模,拆分就是合理的信号。

条件二:不同能力对模型的要求是否已经分化。 知识检索需要大模型的理解能力,意图分类只需要小模型的分类能力。如果所有能力被绑在一个 Agent 上,你只能用同一个模型服务所有场景——大模型做简单分类是浪费,小模型做复杂检索是能力不足。拆分让每个 Agent 独立选模型、独立定温度、独立配置工具集,这是单 Agent 做不到的。

条件三:不同场景的流量是否需要独立扩容。 大促期间,物流查询的咨询量可能翻几倍,但复杂退换货咨询的占比并不高。单 Agent 只能整条链路一起扩容;拆开之后,物流 Agent 单独扩容,知识 Agent 保持不动。当流量曲线在场景之间显著不均衡时,拆分是成本效率问题,不是架构洁癖。

反过来,什么时候不该拆? 当一个 Agent 的表现未达预期时,先尝试让它可靠运行的最小改动,而不是直接拆。如果请求被路由到了错误的功能,先检查工具描述和 System Prompt 是否可以把它们区分得更清楚;如果 Agent 需要新能力,考虑把它作为工具或知识源加到同一个 Agent 里。单独部署一个 Agent 属于较大的改动,应该留到较小的改动无法解决问题时再考虑。微软的经验判断也值得参考:当主 Agent 拥有超过 30–40 个可选动作(工具、主题、其他 Agent)时,路由准确率可能开始下降;在此之前,优化描述比拆分更划算。

最关键的一条判断是:拆分之后,每个子 Agent 是否真的只需要局部上下文。 如果拆分后每个子 Agent 仍然需要完整的全局对话历史、完整的用户画像、完整的工具执行记录,那么拆分只是把一个大 Prompt 拆成了若干个小 Prompt 再加一层通信开销,上下文膨胀的乘法效应(第八篇、第十四篇讨论过)不会消失。进程边界应该对应上下文边界,而不是为了“多 Agent”而多 Agent。

三、A2A 协议:进程之间的“接口契约”,而不是又一个工具调用层

当拆分发生之后,第一个问题不是“怎么调用”,而是“怎么让对方知道我能做什么、我该找谁”。

A2A(Agent-to-Agent)协议解决的就是这件事。Google 推出的 A2A 协议面向“智能体与其他使用不同框架、部署在不同机器、不同公司的智能体进行有效通信和协作”的场景,它定义的核心不是消息格式,而是 Agent Card——一个描述 Agent 身份、能力、端点和认证方式的元数据契约。Agent Card 通常暴露在 /.well-known/agent.json 路径下,包含名称、描述、版本、提供者、支持的能力、默认输入输出模式和技能列表。

A2A 和 MCP 解决的是不同层面的问题,这一点必须分清。 MCP(第十一篇)解决的是工具的标准化发现和调用:模型看到工具 Schema,请求执行,运行时把结果回灌。A2A 解决的是 Agent 与 Agent 之间的协作:一个 Agent 需要把任务委派给另一个独立的 Agent 进程,对方有自己的规划、自己的工具、自己的模型。MCP 的调用方是模型,A2A 的调用方是 Agent。一个 Agent 内部可以使用 MCP 工具,同时通过 A2A 与其他 Agent 协作——两者是叠加关系,不是替代关系。

Spring AI Alibaba 对 A2A 的封装把上述流程简化为三个组件:A2A Server 将本地 ReactAgent 暴露为 A2A 服务,A2A Registry 负责 AgentCard 的注册(支持 Nacos),A2A Discovery 负责远程 Agent 的发现。一个本地 ReactAgent 只要配置了 A2A Server,就会自动生成 AgentCard 并暴露两个端点:GET /.well-known/agent.json(元数据)和 POST /a2a(JSON-RPC 消息端点)。

这里有一个容易踩的配置坑:registry.enabled 和 discovery.enabled 是两个独立的开关。registry.enabled: true 才会把本地 Agent 的 AgentCard 注册到 Nacos,discovery.enabled: true 才能通过 AgentCardProvider 发现其他 Agent。只开 registry 不开 discovery,你的 Agent 能被别人找到,但它找不到别人;反过来则相反。

四、Nacos 注册中心:服务发现的职责边界在哪里

Nacos 3.1.0 引入 A2A 注册中心,提供了轻量化的 Agent 服务注册与发现能力:支持发布与发现 AgentCard 及 Agent 服务端点,当前支持基于 Agent 名称的精准发现和模糊搜索,后续会扩展基于技能标签的多维度检索。Spring AI Alibaba 与 Nacos 的集成让这套能力变成“开箱即用”:Agent 注册、发现、负载均衡都在框架层完成。

但注册中心解决的是“找到谁”,不是“能不能调用”和“调用是否安全” 。Nacos 提供的是地址簿和元数据管理,它不替代以下三件事:

第一,认证与授权不在注册中心里。 AgentCard 的发现端点通常是公开的(/.well-known/agent.json),这是有意的设计——发现应该是公开的,但调用必须鉴权。A2A 的 Java 实现中,Client 和 Server 都支持可选的 shared-secret 认证(X-API-Key header),Server 在 /tasks/send 上校验密钥,而 AgentCard 端点保持开放。更完整的 JWT/OIDC 认证在企业场景中需要在 Server 侧或网关层自己补,不能指望注册中心替你做。

第二,租户隔离需要在 A2A Server 的边界上完成。 一个 Nacos 注册中心可能同时承载多个租户的 Agent。A2A Java SDK 的 AgentCardRouter 提供了按租户解析不同 AgentCard 的机制,但这要求应用层显式实现租户路由,而不是“注册了就能被正确隔离”。如果没有租户维度的 AgentCard 路由和调用鉴权,Nacos 中的 Agent 列表本身就是一个信息泄露面——知道有哪些 Agent 存在,和能够调用它们是两件事,但前者已经不应该对无关租户可见。

第三,超时、重试和幂等是调用方必须处理的问题。 注册中心告诉你“服务在哪个地址”,但网络分区、Agent 进程重启、A2A 调用超时,这些故障模式不会因为有了服务发现就消失。A2A 的远程调用和第十一篇 MCP 的远程工具调用一样,需要在调用方实现超时、重试和幂等键,而不是假设“发现了就一定能调通”。

一个务实的部署判断是:Nacos 适合作为 A2A 和 MCP 的统一注册中心,把 Agent 和工具的服务发现收敛到同一个基础设施上。但注册中心是控制面,不是数据面——它管发现和元数据,不管权限强制和调用可靠性。这两件事需要在 Agent 服务自身和网关层分别处理。

五、Java + Python 双栈:进程隔离是主流方案,不是“混搭”的妥协

第十五篇必须面对一个系列前十四篇刻意没有展开的现实:企业级 Agent 系统很少是纯 Java 的。

纯 Java 做 Agent 的优势和短板都很清楚:与 Spring Cloud 生态无缝集成、强类型、事务安全、分布式成熟,但 AI 开源框架迭代慢,多模态、复杂多 Agent 协作和向量检索生态相对薄弱,复杂 Agent 流程的开发成本高、实验调参效率低。纯 Python 的镜像正好相反:AI 工具链完善、RAG 和智能体编排开箱即用,但不适合高并发交易、分布式事务和复杂权限管控,大规模集群的稳定性和链路追踪成本高。

主流的落地架构是“Java 全栈做业务底座 + Python 智能体做 AI 大脑” :Java 承载交易、权限、分布式、高可用核心链路,Python 依托 LangChain、LangGraph 等框架搭建具备规划、记忆、工具调用能力的智能体,通过 REST/gRPC、消息队列或 MCP 完成跨语言协同。

这个分工在部署边界上的含义是明确的:进程隔离是跨语言协作的主流方案,不是“退而求其次”的妥协。Spring AI 和 LangChain4j 是两套独立的抽象,如果直接把两套框架混在同一个 JVM 的同一个 Service 里——一会儿 ChatClient、一会儿 ChatModel——抽象不互通、工具和向量存储互不识别,后期维护会变成地狱。正确的做法是:Java 侧管业务网关、鉴权、限流、计费和简单工具调用,复杂 Agent 任务转发给 Python 服务;Python 侧只负责 Agent 编排、复杂 RAG 和状态机,输出标准化 JSON,不返回 Prompt 内部细节。

跨语言通信的方式按耦合程度递增有三种选择:普通 HTTP 最简单,但会话状态要自己维护;MCP 更标准化,Python 服务把能力暴露为 MCP Server,Java 侧作为 MCP Client 调用,Spring AI 原生支持这条路径,模型不需要关心底层是 LangChain 还是别的实现;消息队列适合长耗时异步 Agent,Java 侧发任务到 RocketMQ/Kafka,Python 消费执行后回写结果。三种方式的选择标准不是“哪个更先进”,而是调用的实时性要求和故障容忍度——同步对话用 HTTP 或 MCP,长任务用消息队列。

六、收束:部署边界的终点,是让每一个故障域和权限域都有物理承载

这一篇从单体模块化讲到进程拆分时机,再到 A2A 协议、Nacos 注册和 Java/Python 双栈,核心可以收束为三个判断。

第一,拆进程的前提是模块化已经就位,而拆进程的触发条件是三个具体的信号:Prompt 膨胀到难以维护、不同能力需要不同模型、不同场景的流量曲线不均衡。 在信号出现之前,优化描述和工具划分比拆分更划算;在信号出现之后,拆分是为了让故障域和发布节奏获得物理隔离,而不是为了“多 Agent”这个标签。

第二,A2A 解决“Agent 之间的契约”,Nacos 解决“找到谁”,但两者都不解决“能不能安全地调用”。 AgentCard 是公开的发现元数据,调用鉴权、租户隔离、超时幂等必须在 A2A Server 和调用方分别实现。注册中心是控制面,不是安全边界。

第三,Java + Python 双栈的部署边界应该画在进程之间,而不是同一个 JVM 里混用两套框架。 Java 管事务、权限、高可用,Python 管复杂编排和实验迭代,通过 HTTP/MCP/消息队列协作。进程隔离不是妥协,是让两种技术栈各自发挥长处的唯一可靠方式。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。