ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

消息落错了副本:Higress MCP 网关的 SSE 会话路由与长连接保活机制

消息落错了副本:Higress MCP 网关的 SSE 会话路由与长连接保活机制 消息落错了副本Higress MCP 网关的 SSE 会话路由与长连接保活机制【免费下载链接】higress AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higressHigress 的 MCP 网关能力里SSE 传输依赖一条不断开的 HTTP 长连接而网关副本是无状态的且通常不止一台。本文拆解 Higress 如何用 Redis 发布订阅把会话路由拆开让任意副本都能处理 MCP 消息。一次 tools/call 为什么可能落到没有会话的副本客户端先发起 GET /sse负载均衡把这条长连接分给了副本 A会话上下文只存在于 A 的进程内存里。几秒后客户端发起 POST /message?sessionIdxxx 携带工具调用请求这一次负载均衡按常规策略挑选上游很可能落到副本 B。B 能正常执行工具但按 MCP 的 SSE 传输规范结果必须从 A 上那条打开的 SSE 流推给客户端。这正是它和普通 HTTP API 代理的本质区别普通请求是自包含的任何副本都能独立完成MCP SSE 模式把一次逻辑交互拆成两条互不相关的 HTTP 请求——建连一条、发消息一条——两条请求必须跨副本互相找到。B 手里的会话在哪这个问题不借助进程外的设施无解。为什么不让负载均衡记住这条连接可选项有三条。粘性会话按 Cookie 或源 IP 哈希把会话生命周期绑死在某个进程上副本扩缩容、滚动发布都会打断存量连接也和 Envoy 集群负载均衡的默认行为相抵触。把会话状态写成 KV 记录则不对症SSE 的状态本质是一条消息通道而不是一份可读写的数据存取开销和延迟都高于直接推送。第三条路是共享消息总线每个会话对应一个频道名持有连接的副本订阅频道执行了工具的副本把结果发到频道进程彻底无状态化副本可随意增删替换——代价是引入一个 Redis 依赖和多一跳发布开销。Higress 选了第三条。取舍的边界在代码里写得很直白没有配置 Redis 时SSE 端点直接返回 Redis is not enabled, SSE connection is not supported而 streamable HTTP 模式不需要会话状态承担无 Redis 场景的降级路径。会话建立每个 SSE 连接独享一个频道触发条件客户端 GET /sse路径后缀由 sse_path_suffix 指定缺失时配置解析直接报错。执行过程mcp-session 过滤器在头阶段生成一个 uuid 作为 sessionID并为此请求新建一个 SSEServer 实例。源码注释解释了为什么不能复用全局实例MCPServer 本身线程安全可以共享但 SSEServer 持有请求特有的 messageEndpoint跨连接复用会串路。随后它按mcp-server-sse:sessionID拼出频道名在 Redis 上订阅该频道并通过 Envoy 的 InjectData 接口把首个 endpoint 事件推给客户端——事件里携带的就是拼好 sessionId 查询参数的消息端点 URL。此时过滤器返回 api.Running 表示响应流未结束SSE 连接保持打开另有一个协程每 5 秒向频道发布一条 ping 的 JSON-RPC 请求防止中间层超时掐断空闲连接。channel : GetSSEChannelName(sessionID) // mcp-server-sse:sessionID initialEvent : fmt.Sprintf(event: endpoint\ndata: %s\n\n, messageEndpoint)配置入口mcp-session 过滤器配置的 redis 字段address、username、password、db、secret与 sse_path_suffix。secret 仅用于 AES 加密写入 Redis 的存储值与发布订阅路径无关。这段逻辑见 SSE 会话与频道订阅。这里埋着整个设计的唯一亲和信息sessionId 被内嵌进下发给客户端的 URL此后所有消息请求都携带它于是任何副本都能从查询参数反推出频道名。进程内存里不存任何哪个会话在哪台机器的映射。消息投递POST /message 在任意副本上执行触发条件客户端 POST /message?sessionIdxxx。这里 sessionId 设计成查询参数而非请求头是为了让消息端点可以直接写进 endpoint 事件、被客户端原样复用。执行过程分两种链路。内置 MCP 服务器golang-filter 形态的 mcp-server在进程内执行工具请求体被缓冲完整后交给 HandleMessage结果直接写回 HTTP 响应SSE 侧的推送由会话机制兜底见 mcp-server 过滤器。代理外部 MCP 服务器的场景更依赖跨副本路由mcp-session 过滤器按 match_list 匹配出上游类型收到上游响应体后若 URL 携带 sessionId就把响应体包成event: message发布到对应频道持有长连接的那台副本经订阅回调 InjectData 推给客户端HTTP 响应与 SSE 推送是同一份数据前者兼容只看 HTTP 响应的客户端后者才是协议真正的主通道。这条发布逻辑在 mcp-session 过滤器 的响应编码路径里同一文件还处理第三种上游原生 SSE 协议的 MCP 服务器其首个 endpoint 事件会被 rewriteEndpointUrl 改写成网关对外路径防止客户端绕过网关直连后端。连接保活与故障恢复触发条件网络抖动、副本重启、Redis 断连。Redis 客户端在后台协程里每 5 秒 Ping 一次探测到失败就关闭旧连接并按原配置重建重建期间已有会话的订阅会随新连接恢复会话侧请求结束或被中断时 OnDestroy 关闭 stopChan订阅协程退出并清理 pubsub 资源频道映射同步删除。所有会触碰 Envoy 回调的协程都包了 recover 与 RecoverPanic单个 panic 不会击穿 Envoy worker。实现见 Redis 客户端与保活逻辑。边界要认清Redis 断连期间5 秒一次的 ping 发布失败只产生错误日志SSE 连接本身不掉但断连窗口内的工具结果会丢失客户端只能靠 ping 中断感知异常。恢复后旧会话无需重建这一点和粘性会话方案形成对照。测试如何覆盖这些路径单元测试集中在会话解析与流处理的边角上SSE 事件行结束符的 \r\n 组合按 HTML 规范逐一验证endpoint 事件在流中被截断、跨缓冲块到达的情况由缓存拼接逻辑覆盖上游响应非 SSE 类型时的跳过路径也有对应断言。端到端层面Higress 在 test/e2e 下搭了真实集群的验证框架conformance 用例按 Gateway API 规范组织整体结构如下运维视角上5 秒一次的 ping 机制天然兼做连接健康信号监控里某会话 ping 停发可以作为会话失效的判定基准比依赖连接断开事件更及时。多副本部署 MCP 网关时的检查项Redis 可用性排第一发布订阅不落盘Redis 故障不影响已返回的 HTTP 响应但会阻断所有新建会话和工具结果推送生产环境至少上主从或集群模式并把Redis ping 失败日志纳入告警。不要给网关前配置粘性会话前置负载均衡用任意均衡策略即可若强行按源 IP 亲和等于把状态性又绑回进程故障域原样返回。无 Redis 的场景走 streamable HTTP 降级代码中 sessionId 为空时 HandleMessage 返回 200 并把结果直接写进 HTTP 响应适合不需要服务端主动推送的调用方。启用用户级服务器enable_user_level_server时注意两点/config 端点仅对集群内网 IP 放行rate_limit 的 limit 与 window 是针对 uid 的唯一限流位未命中白名单的匿名调用方全部受其约束。协议类型上网关支持的 MCP 传输在 MCP 模型定义 里以常量列出mcp-sse 与 mcp-streamable 之外还覆盖 stdio、dubbo 等形态选型时先确认上游实际协议再决定是否需要 Redis 依赖。对需要在网关层托管 MCP 接入的团队这套机制给出的参考答案是把长连接的会话性拆成一个可推导的频道名进程保持无状态代价交给一个可独立扩容的 Redis。理解了这个拆法后续无论是排查结果偶发丢失还是评估 streamable 迁移都有了明确的判断坐标。【免费下载链接】higress AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表