这段时间一直在做多Agent系统的工程落地,团队内部项目代号叫 Agent-Reach。一开始只是想治自己团队里的“连接瘫痪”,后来做着做着我发现,凡是把Agent数量堆到两位数、还指望它们互相调得动的团队,迟早都会撞上同一堵墙:接入方式五花八门、Agent之间彼此找不到、路由全靠硬编码、出了问题没人说得清是谁调了谁。Agent-Reach 说白了就是一层“智能体触达基础设施”——它负责把不同的Agent统一接入到一张网络里,让Agent之间、Agent与上层编排器之间,按能力、按策略、按权限被可靠地触达。这篇文章会把我在设计、实现、部署Agent-Reach过程中沉淀的东西完整讲一遍,包括架构取舍、注册与路由机制、权限边界、观测体系,以及实际踩过的三个坑和完整排错链路,适合正在做多Agent系统、或者准备把一组Agent沉淀成平台的团队参考。
1. 为什么需要Agent-Reach:多智能体协作的“最后一公里”
1.1 从三个Agent到十几个Agent,连接成了最大的隐性成本
先说背景。我最早做的其实只有三个Agent:一个管订单履约,一个管售后客服,一个管风控。第一版实现非常简单,A要调B就直接写B的HTTP接口,调不通就加个重试,日志打一打,跑起来很顺。但到了Agent数量突破十几个的时候,问题开始接二连三地冒出来:
- 接入协议碎片化:有人用HTTP,有人用WebSocket,有人坚持走gRPC,还有人封装了自己的私有SDK。每个Agent都自带一套“方言”。
- 互相发现靠配置文件:新上一个Agent之后,需要手动把它能处理的能力、它的地址、调用方式同步给所有相关方,漏改一个配置就是线上一次404。
- 重试与超时逻辑各写各的:每个Agent自己实现了一套“失败就重试三次”,导致一次业务请求在链路里被放大成几十次调用,下游直接被冲垮。
- 没有统一审计:出了事故之后根本说不清一个请求是谁发起的、经过了哪些Agent、每一步花了多长时间、最后在哪一环被吃了。
说白了,业务逻辑本身没有变得多复杂,复杂度全堆在了“让Agent能互相找到、调通、并追踪”这件事上。这就像一屋子人打电话,每个人都要自己拉一根线到别人的电话机上,线越拉越多,迟早缠成一团。
1.2 Agent-Reach 要解决什么、不解决什么
我设计Agent-Reach时给自己定过一个原则:它只负责“触达”,不负责“思考”。
Agent-Reach 解决的核心问题是四个:
- 接入统一:不管Agent是HTTP、WebSocket还是gRPC,只要接入Agent-Reach,对外就暴露一套统一的路由入口。
- 能力发现:Agent启动时声明自己能处理什么能力,Agent-Reach维护一张“能力到Agent实例”的动态映射表。
- 策略路由:调用方不直接指定“我要调某某Agent”,而是指定“我要做什么事”,由Agent-Reach按策略分发给合适的实例。
- 安全与可观测:所有跨Agent调用统一过身份校验、权限校验、审计记录,并且每个调用都带全局TraceID。
它不碰的事我也说得很清楚:每个Agent内部的模型选择、提示词组织、RAG逻辑、业务决策,全部留在Agent本地。Agent-Reach不管你的Agent聪明不聪明,只管消息能不能送对地方、能不能送到合法的人手上。这个边界如果不守住,很容易把这个基础设施做成一个什么都往里塞的大泥球。
1.3 用“电话总机”理解触达层的价值
我最喜欢拿电话总机来类比。在没有交换机的年代,每两台电话之间都要拉一根专线,N台电话就要N*(N-1)/2根线。等电话多了以后,有人发明了总机,每台电话只连到总机,总机负责把呼叫接到目标话机。
Agent-Reach 就是这套多Agent协作里的总机。每个Agent只连一次Agent-Reach,之后任何Agent要找任何能力,都通过Agent-Reach中转。总机坏了,全网瘫痪,所以总机本身必须可靠——这也是为什么我在架构上花了大量精力做状态监控、心跳保活和路由降级。
2. 整体架构与模块边界:把Agent连接层单独抽出来
2.1 第一版点对点调用的教训
Agent-Reach 不是一上来就设计成现在这个样子的。我最早其实先做了一个更轻量的“服务发现表”,每个Agent启动的时候往一张共享表里写自己的地址和能力,调用方自己去查表再直连。
这个方案比纯配置文件进了一步,但它有个很致命的问题:调用链路控制不住。A调了B,B又调C,C挂了,A和B各自用自己的方式做重试,整个链路没有任何全局视角。我后来意识到,如果只是做一个“通讯录”,那连接层仍然散落在各个Agent内部,问题的本质没有被解决。要收敛,就必须把点对点连接彻底改成“星型拓扑”——所有连接都经过一个中枢,也就是后来在项目里叫 Reach Hub 的组件。
2.2 Reach Hub 的五个核心模块
Agent-Reach 的核心由五个模块组成,每个模块都有明确的职责边界:
| 模块 | 职责 | 对外暴露形式 |
|---|---|---|
| Adapter 接入层 | 收编HTTP、WebSocket、gRPC等不同协议,统一转为内部事件 | 各种协议的接入端点 |
| Registry 注册中心 | 维护Agent实例注册、能力声明、健康检查、状态变更 | /v1/registry/* |
| Dispatcher 路由引擎 | 调用方意图到能力、能力到具体实例的匹配,执行优先级与分发策略 | /v1/dispatch/* |
| Security 安全网关 | 身份认证、Token签发、细粒度授权、审计日志记录 | /v1/auth/, /v1/audit/ |
| Observer 观测模块 | TraceID链、心跳状态看板、熔断状态、指标统计 | Dashboard API |
模块划分的核心原则是:每个模块只和“触达”这件事相关,不掺和任何业务判断。比如Router只关心这条消息应该送给谁,它不需要知道消息内容里那个订单号意味着什么;Security只关心调用方是不是有权限,它也不需要理解业务。
2.3 一次触达请求的完整路径
用一次“订单履约Agent请求风险质检Agent判断一笔订单是否可疑”来走一遍完整流程:
- 编排器向 Agent-Reach 安全网关发起请求,附上自己的Token。
- 网关校验身份与Scope权限,生成TraceID并写入请求上下文。
- Dispatcher 解析调用方的意图“risk.checkOrder”,把它翻译为能力查询。
- Registry 返回当前持有该能力、且状态健康的风险质检Agent实例列表。
- Dispatcher 按路由策略选出目标实例,经Adapter把请求转发过去。
- 风险质检Agent处理完,原路返回结果,Observer记录整个过程的状态与耗时。
这个链路里每一步都可以单独观测,哪一个环节变慢了、失败率高了,直接能定位到具体模块,而不是让业务方在十几个Agent的日志里慢慢翻。
2.4 为什么没用现成的ESB,而是自己写一层
评估阶段有人问我:这不就是企业服务总线(ESB)吗,为什么不用现成方案?我当时的回答是:ESB的重心在系统间集成和协议转换,而Agent系统的重心在能力发现和意图路由,两者形态接近但内核不同。
ESB处理的是相对稳定的服务接口,接口定义好了基本很少变;Agent体系里能力是动态的,Agent可以随时上线、下线、升级能力版本,调用方甚至不确定谁会响应它的请求,它只知道自己要“达成什么意图”。这个差异决定了路由模型必须更接近“意图驱动”而不是“接口驱动”。所以Agent-Reach没有把协议转换作为核心卖点,而是把能力注册和意图路由作为核心,协议适配只是接入层的一个附属功能。
这里想再补一句:如果你们团队已经有成熟的ESB维护着几十个老系统,又想让Agent能力接入现有的企业集成体系,不要纠结“用ESB还是用Agent-Reach”,更务实的路线是让Agent-Reach作为Agent域的接入层,通过ESB适配器对外提供能力,两边各管各的地盘,硬融合反而会把两边都拖垮。
3. 核心实现:注册协议、路由规则与安全边界
3.1 Agent如何“入网”:注册与心跳机制
Agent接入Agent-Reach的第一步是注册。每个Agent启动后必须调用注册接口,声明自己的身份、地址、能力列表和标签。下面是我在实际项目里用的一份注册请求示例:
POST /v1/registry/register { "agent_id": "risk-check-v3", "display_name": "风险质检Agent", "endpoints": { "primary": "grpc://10.20.30.41:9100", "fallback": "http://10.20.30.42:9101" }, "capabilities": [ { "type": "risk.checkOrder", "version": "1.4.0", "timeout_ms": 3000, "weight": 80 } ], "labels": { "team": "risk", "env": "prod", "zone": "cn-east-1" } }这里有几个设计点值得单独解释。capabilities必须带版本号,路由规则在匹配时可以把“指定版本区间”作为条件,这样Agent升级能力版本时不会因为兼容性问题一次炸掉所有调用方。labels是给路由策略用的,灰度发布、多机房亲和性、团队隔离都能通过标签表达,后面路由部分会具体讲。
注册之后,Agent要周期性发送心跳。心跳不只是“我活着”的信号,还应该携带当前负载信息,帮助路由引擎做更聪明的分发:
POST /v1/registry/heartbeat { "agent_id": "risk-check-v3", "state": "ready", "reported_metrics": { "inflight_requests": 12, "queue_depth": 3, "error_rate_5m": 0.002 } }Registry在收到心跳后更新内部状态表。如果一个Agent连续三个心跳周期没上报,Registry会先把它的状态标记为“suspect”,不会立刻摘除,而是停止向它分派新请求但保留存量连接,再等一个宽限期,确认没有恢复后标记为“down”并摘除路由。这套“怀疑-确认-摘除”的流程借鉴了分布式系统中的故障检测思路,避免因为一次网络抖动就把正常Agent误踢下线。
下面是我用Python描述Registry内部剔除逻辑的简化版伪代码:
# registry/core.py class AgentRegistry: HEARTBEAT_INTERVAL_SECONDS = 10 SUSPECT_THRESHOLD = 3 EXPIRE_GRACE_SECONDS = 30 async def process_heartbeat(self, agent_id, payload): now = time.time() agent = self.agents[agent_id] agent.last_heartbeat_at = now agent.set_metrics(payload.get("reported_metrics", {})) if agent.health_state == "suspect": # 恢复 agent.health_state = "ready" self.dispatch_enabled[agent_id] = True await self.publish_state(agent_id, "ready") async def tick_failure_detector(self): now = time.time() for agent in list(self.agents.values()): silent_for = now - agent.last_heartbeat_at if agent.health_state == "ready" and \ silent_for > self.HEARTBEAT_INTERVAL_SECONDS * self.SUSPECT_THRESHOLD: agent.health_state = "suspect" self.dispatch_enabled[agent_id] = False await self.publish_state(agent_id, "suspect") # 宽限期后仍无心跳则摘除 agent.suspect_since = now elif agent.health_state == "suspect" and \ now - agent.suspect_since > self.EXPIRE_GRACE_SECONDS: agent.health_state = "down" await self.unregister(agent_id) await self.publish_state(agent_id, "down")这套逻辑里有个容易被忽视的细节:状态变更一定要主动广播给相关订阅方,不能只改自己的内存状态。否则编排器在本地缓存了Agent在线的信息,Registry这边已经把它摘除了,编排器还是会继续往已摘除的地址发消息,然后被一堆超时兜底逻辑搞晕。
3.2 意图路由:从调用方需求到具体Agent实例
Agent-Reach 的路由模型不是传统的“按接口名找服务”,而是“按意图找能力”。调用方不需要在代码里写死“我要调risk-check-v3”,它只说自己要做的事,由Dispatcher决定谁来接。
我最初版本的规则配置是把所有路由规则放在一个数组里顺序遍历,用起来很灵活,但后来发现规则一多,匹配优先级几乎靠人肉心算。这个在第四章会专门讲一次线上事故,这里先给出我重构后的路由策略格式:
{ "rules": [ { "priority": 100, "name": "生产环境订单风险必须走主集群高权重实例", "when": { "capability": "risk.checkOrder", "env": "prod", "zone_in": ["cn-east-1", "cn-east-2"] }, "to": { "agents": ["risk-check-v3", "risk-check-v4"], "strategy": "least_inflight", "version": ">=1.3.0" } }, { "priority": 50, "name": "兜底:任何环境都允许的常规分发", "when": { "capability": "risk.checkOrder" }, "to": { "agents": ["*"], "strategy": "weighted_random", "version": "*" } } ] }匹配过程分三步:
- 把调用方请求里的能力声明(比如“risk.checkOrder”)作为一级索引。
- 把调用上下文里的标签(比如环境、可用区、团队)与规则里的when条件做匹配。
- 在匹配成功的规则里按优先级排序,取最高优先级对应的Agent实例集合,再按strategy从集合里选一个实例。
我把这种设计叫作“先按优先级桶收拢,再按策略发散”。优先级决定了哪些规则优先占据分发入口,策略决定了在同一批候选里怎么选一个具体实例。least_inflight会选当前在处理请求数量最少的实例,适合那些耗时差异大、需要动态削峰的Agent;weighted_random按权重随机选,适合承接常规均衡流量。
关于超时,有一个来自实战的教训:Agent能力的超时参数不能只放在Agent自己那侧,路由层必须有独立的超时控制。我把注册接口里的timeout_ms设计成“推荐超时”,但Dispatcher实际执行时还会读取调用方带过来的deadline,两者取最小值作为最终网络超时。否则一旦某个Agent因为线程池耗尽开始排队积压,调用方会因为迟迟收不到响应而无限等待,连接资源全被占住,故障面会迅速扩大。
3.3 安全边界:为什么不能只靠内网白名单
很多团队做内部Agent平台时有一个惯性思维:只要把服务部署在内网,加个IP白名单就可以了。这个想法在Agent系统里是危险的,因为Agent的调用方经常不是固定IP——可能是另一组Agent,可能是上游编排任务,可能是外部可控的自动化脚本,白名单根本覆盖不全面。
Agent-Reach 的安全模型做了三层:
第一层是身份认证。每个Agent启动时从Security模块换取自己的身份凭证,我用的标准JWT。调用方不能只凭IP说话,必须带着能证明自己是“谁”的凭证。
{ "sub": "system:workflow-orchestrator", "iss": "agent-reach-auth", "iat": 1700000000, "exp": 1700003600, "scope": ["risk:check:write"], "targets": ["agent:risk-check-v3", "agent:risk-check-v4"] }第二层是授权,而且必须是“资源绑定的细粒度授权”。scopes不是简单的一个“全部允许”或“全部拒绝”,而是明确调用方可以对哪些Agent的哪些能力做什么操作。这里有个我自己踩过坑的重要细节:scope必须同时包含能力和资源维度,比如风险评估这个能力可能需要区分“读取结论”和“提交误报申诉”,如果不区分,一个本应只有只读权限的调用方就能对Agent做写入操作。
第三层是审计。所有跨Agent调用,无论成功失败,都记录一条审计日志。审计字段包括调用方身份、目标Agent、能力名、TraceID、调用结果、耗时、是否命中降级路径。这些日志用于事后追溯和安全分析,我在实现里把它们独立落库,不给业务日志混在一起,避免日志被业务噪音冲掉。
另外要额外提一点:Scope校验不能只做在网关入口,必须一路透传到目标Agent的接入层。因为Agent-Reach不可能100%挡住所有恶意请求,万一有一次穿透,目标Agent自身也要有能力判断“这个调用方是否真的被授权了”。我在设计Adapter转发协议时强制把调用方身份声明塞进了内部请求头,内部Agent可以校验,不能信任外部传入的身份字段。
3.4 观测与追踪:连接层的底线
连接层出问题的最典型表现是“整个系统看起来还活着,但消息送不到了”。这种情况下如果没有一套明确的观测体系,只能靠人工盲猜。Agent-Reach 在Observer模块里做四件事:
- TraceID贯穿:每一条进入Agent-Reach的调用都会生成全局TraceID,横跨所有Agent传下去,日志和审计都打上它。
- 活跃Agent状态表:实时展示每个Agent的注册状态、心跳状态、最近响应耗时、处理中的请求数。
- 调用链指标:每个“意图到Agent”的分发路径都统计成功率、P95耗时、超时次数、触发熔断次数。
- 降级状态:路由引擎自动摘除异常Agent时,Observer面板会标出该Agent的摘除原因和时长,提醒值班人员关注。
我实际在看板上长期盯住的指标就五个:
| 指标 | 含义 | 警戒线 |
|---|---|---|
| 触达成功率 | 被正确送达并收到成功响应的比例 | <99% 就要查 |
| P95触达时延 | 95%的触达请求在多少毫秒内完成 | 超过基线2倍要查 |
| 活跃Agent数 | 当前处于ready状态的实例数 | 低于预期值要查 |
| 心跳超时事件数 | 进入suspect状态的次数 | 突然飙升要查 |
| 路由无可用实例数 | 有意图进来但找不到合格Agent的调用次数 | 大于0要立刻查 |
最后一个指标我特别看重,它往往不是Agent挂掉导致的,而是路由规则写错了或者能力声明对不上,这时候哪怕所有Agent都健健康康,调用也一样会失败。
4. 实测中的三个真实事故与完整排错链路
4.1 夜间集体掉线:WebSocket连接僵死后“假活”
现象:某一晚凌晨2点左右,运维报障说多个Agent的WebSocket连接集体掉线,但业务方检查Agent本身进程都还活着,日志里也没有异常堆栈,重启Agent后连接能恢复,但过了几个小时又掉。
疑点:第一反应是Agent进程OOM或者被系统杀了,但检查了宿主机监控,内存和CPU都平稳,进程存活时间也很长。第二个怀疑点是WebSocket库配置问题,但线上代码已经稳定跑了两个月,不像是代码bug。
验证:我看了一下Agent-Reach这一侧维护的连接状态表,发现连接状态一直是“established”,心跳包也在正常收。按理说连接是活的,为什么消息就发不过去呢?于是抓包看TCP层,发现有对端发来的RST包,也就是说TCP连接其实已经被对端关闭了。
追查下去,发现Agent和Hub之间走了云上负载均衡,负载均衡对空闲连接有一个默认的空闲超时时间。我们当时的客户端只在业务请求发生时才会发心跳,而凌晨业务流量很低,一个连接连续几十分钟没有任何数据帧,触发了负载均衡的空闲断开。断开后负载均衡这一侧直接丢弃连接状态,TCP层看起来还连着,但数据已经发不过去了,于是表现成“状态活着、消息送不到”。
根因:保活机制用的是业务级消息,业务不活跃时保活就失效了。
修复:改成协议级的Ping/WebSocket Ping帧,Agent每5分钟主动发一个Ping,不依赖业务流量;Hub这一侧对超过10分钟没有任何帧的连接直接标记为stale并主动重建。同时所有Agent客户端统一了“连接断裂自动重连+指数退避”的逻辑。改完后这个事故再没出现过。
4.2 路由风暴:规则优先级没有真正生效
现象:上线了一个新的Agent实例后,某一段时间内所有原本应该分发给特定Agent的请求,忽然大量打到了一个低优先级的兜底路由上,导致几个关键Agent处理量暴增、响应变慢。
疑点:最开始怀疑是Registry的标签匹配有问题,新实例的labels写得不规范,把匹配条件污染了。检查后发现标签没有问题,新实例确实能被正常的规则命中。那问题就可能出在匹配顺序上。
验证:我拉出了规则引擎实际匹配的追踪日志,发现低优先级的兜底规则几乎在每一个请求里都在“抢答”,更可怕的是,当新实例加入后,它的能力版本号比旧实例更高,兜底规则里的version匹配条件是“*”,永远优先命中。
继续往下查,发现规则引擎在实现时用了数组存储规则,每次新增规则就append到末尾。虽然每条规则都有priority字段,但引擎在运行时是按数组顺序遍历的,只有在遍历完之后才发现优先级更高的规则在后面,导致低优先级规则有非常长的窗口期被命中。
根因:数据结构的物理顺序覆盖了逻辑上的优先级语义。
修复:整个匹配逻辑重写成“优先级桶”。预先按priority值把规则分桶排序,匹配时从高优先级桶开始,一旦某个桶里有规则命中,就绝不再看低优先级桶。同时启动时增加规则冲突检查,如果两个同优先级规则能力重叠且条件互斥,启动直接失败,避免线上用概率暴露问题。
4.3 权限绕过:Scope没有绑定资源维度
现象:上线审计功能后发现,有一个Agent在审计日志里出现了本不应该出现的调用,调用方持有的Token只能访问风险质检Agent,但审计日志显示它对另一个客户资料Agent发起过调用,而且成功了。
疑点:最开始怀疑是JWT伪造或者密钥泄露,查了一圈没发现异常。然后怀疑是Agent-Reach网关注册模块的缓存逻辑出了问题,旧Token的权限位没有及时更新。
验证:逐步排查后发现,网关在鉴权时只校验了scope里“risk:check:write”这个能力标识,并没有校验scope里的targets字段。而目标Agent的接入适配器也没有校验请求头里透传的调用方身份是否匹配Target。所以只要有人拿到一个拥有“risk:check:write”权限的合法Token,他就能把目标地址改成任意Agent,因为网关只看了“能不能调用这个能力”,没看“能不能调用这个实例”。
根因:Scope模型只做了能力维度的授权,漏了资源维度的授权。
修复:把授权模型改成“能力+资源”双层校验。Token里的targets字段必须显式声明允许调用的Agent实例范围,网关校验能力,适配器校验资源目标,两层都通过才放行。同时在审计日志里增加一个字段专门记录“资源所有权校验是否通过”,当时为了排查这个问题,我把所有鉴权日志都翻了一遍,这个字段成了事后追责的救命稻草。
4.4 三个事故放在一起看
| 事故 | 表象 | 根因 | 修复 |
|---|---|---|---|
| WS连接假活 | 连接状态正常但消息送不到 | 保活依赖业务流量,负载均衡空闲断开 | 协议级Ping + 自动重连 + 指数退避 |
| 路由优先失效 | 请求打错Agent | 数组物理顺序覆盖优先级语义 | 优先级桶 + 启动规则冲突检查 |
| 权限绕过 | 合法Token访问了越界Agent | Scope缺少资源维度 | 能力+资源双层校验 |
这三个事故给我最大的共同教训是:任何连接层组件,只要它依赖“其他组件的默认行为”才能正常工作,那它迟早会被那个默认行为坑一次。负载均衡默认踢空闲连接、数组默认按插入顺序遍历、权限默认只看能力不看资源——每一个都是我在设计时觉得“应该没问题”的地方。
5. 适用场景、部署节奏与选型落地建议
5.1 哪些场景值得上Agent-Reach,哪些不值得
不是所有团队都需要一个独立的Agent触达基础设施。我自己的判断标准是看三条:
- Agent数量:超过8个Agent,并且它们之间存在互相调用关系时,连接成本才开始指数上升,这时候值得上。
- 能力复用:你希望同一个能力被多个场景复用,而不是每次接入新Agent都要重新写一遍调用逻辑,值得上。
- 治理需求:你有跨Agent审计、权限收敛、灰度发布这类治理诉求,而不是仅仅让两个Agent互相通个消息,值得上。
反过来,如果整个系统只有两三个Agent,彼此之间调用频率也很低,那完全没有必要引入一个中枢组件。多一个中枢就是多一个故障点,为了架构而架构是我最反对的做法。我当时画Agent-Reach的架构图时一直提醒自己:这个组件存在的唯一理由,是它能消除点对点连接带来的混乱,而不是因为它看起来更高级。
5.2 从单体起步的落地节奏
Agent-Reach 如果一开始就按完整的五模块、高可用分布式架构来做,大概率做不完。我的落地节奏是分成四步,每一步都保证线上可用:
| 阶段 | 内容 | 目标 |
|---|---|---|
| 第1周 | HTTP接入 + 内存注册表 + 基础路由 | 跑通“注册-发现-调用”最小闭环 |
| 第2-3周 | 加上Token鉴权、审计日志、TraceID | 让链路可追溯、权限有边界 |
| 第4-8周 | 引入WebSocket长连接、状态广播、熔断降级 | 支持Agent主动推送和异常自动摘除 |
| 后续 | 按需引入多副本Hub、规则热更新、灰度分发 | 提升容量与运维体验 |
我特别想提醒:不要为了追求架构完整,在最开始就引入分布式协调组件。你大概率根本用不上ZooKeeper或者etcd,一个进程内的内存注册表加上事件广播,就足够支撑几十个Agent的规模了。等Agent数量真正上来、单点成为瓶颈之后,再考虑把Registry做成独立服务,成本会低很多。
5.3 生产部署后的几条运维提醒
Agent-Reach 属于自己的“基础设施”,它挂了会影响所有Agent调用。有几条运维层面的经验分享下:
- Hub本机必须先被监控:如果Hub所在进程的CPU、内存、文件描述符有任何异常,优先级高于所有业务Agent的告警。
- 配置变更要灰度:路由规则、权限策略这种配置,不要在全部Agent上一次性生效。我后来做了配置版本号,先在一小撮测试Agent上试跑再全量推送。
- 重试策略必须统一收敛:Agent调用方的重试参数不能自己随便定,统一由Agent-Reach下发的策略管理,否则重试风暴会把故障放大好几倍。
- 定期演练“摘除Agent”:在生产环境手动把某个Agent标记为down,观察路由是否正常切走、调用方是否报错、恢复后是否自动回到路由池。这条我建议每个月做一次,成本很低但效果极大。
关于容量预期,还有一个现实问题:Hub本身是转发层,它必须扛得住所有Agent汇聚的流量。如果单机Hub成为瓶颈,可以做多活跃实例并让Agent同时连多个Hub,注册信息通过Raft协议同步。不过这是团队规模到几百个Agent规模时才需要考虑的事,前期不用过度设计。
5.4 后续可以怎么扩展
Agent-Reach 当前的设计留了几个可扩展的口子。比如能力版本支持了“>=1.3.0”这种语义化版本匹配,未来可以在这个基础上做能力兼容性自动判定,Agent升级时如果能自动判断“新版本覆盖了旧版本的能力,且接口兼容”,就可以无缝切换。再比如路由的strategy目前支持了least_inflight和weighted_random,后续可以加一个基于历史响应耗时的模型,让路由引擎根据Agent历史表现自动调整分发权重。
把Agent-Reach做成今天这个形态,我最深的体会是:连接层作为基础设施,它的价值在最开始的时候几乎看不出——因为在Agent还少的时候,点对点调用也能跑;但等Agent到两位数之后,每一次新Agent接入、每一次排障、每一次权限审计,都会把省下的时间成倍地还回来。如果你的团队正在被“Agent越来越多、连成一锅粥”困扰,不用一开始就照搬整套方案,先把“注册-路由-权限-观测”这个最小闭环做出来,一定比你继续在点对点调用里补丁摞补丁要稳得多。