MCP Server 实践之旅第 3 站:MCP 协议亲和性的技术内幕

简介: 本文深入探讨了分布式架构中请求亲和性技术在Serverless范式下的实践。文章以MCP Server在函数计算平台的集成为例,剖析了基于SSE长连接通信模型的会话亲和、优雅升级等关键技术。通过双阶段协商机制与网关层协同设计,函数计算实现了MCP SSE会话亲和性保障,解决了无状态服务处理有状态请求的难题。同时,文章还展示了压测结果,验证了系统的稳定性和扩展能力,并总结了Serverless与有状态服务融合的技术创新点。

作者:柳下,西流

背景

在分布式架构设计中,请求亲和性是实现有状态服务高可用的核心技术,通过将具备相同会话标识的请求智能路由至固定计算节点,保障会话连续性及缓存有效性。然而在 Serverless 范式下,函数计算服务因其瞬时实例生命周期、自动弹性扩缩容、无状态化运行等特性,在亲和性保障层面面临原生架构冲突。本文将以 MCP Server 在函数计算平台的深度集成为研究载体,解构基于 SSE 长连接通信模型,剖析会话亲和、优雅升级等关键技术,揭示 Serverless 架构在 MCP 场景中的亲和性创新实践。

概念介绍

在系列文章首篇 MCP Server 实践之旅第 1 站:MCP 协议解析与云上适配 我们深入解析了 MCP 以及 SSE 协议,为了方便本文的阅读, 这里再简单介绍下 MCP 以及 SSE 协议。

MCP:作为开放标准协议,为 AI 应用构建了通用化上下文交互框架。可以将 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 为设备连接各种外设和配件提供了标准化方式一样,MCP 为 AI 模型连接不同的数据源和工具提供了标准化方式。

MCP Server&Client:MCP 通过 Server+Client 在 AI 应用程序和数据之间搭起了一座桥梁,MCP Server 负责打通 data、tools,AI 应用程序通过 MCP Client 连接 MCP Server。其中 Client 与 Server 的通信基于 SSE 协议实现,而 MCP 亲和奥秘也恰恰隐藏在 SSE 协议之中,下文将详细介绍。
image.png

SSE协议:作为 HTTP/1.1扩展协议,SSE(Server-Sent Events)定义了结构化流式传输规范,允许服务器实时向客户端推送事件或数据更新。在接收到订阅请求后,服务器会保持连接开启,并通过 HTTP 流向客户端推送事件,其中每个流数据格式如下:

event: <event-type>       // 事件类型(endpoint/message/close)
data: <payload>           // 通过数据字段传递实际内容
id: <message-id>          // 用于唯一标识事件
retry: <milliseconds>     // 重连退避策略

image.png

函数计算:函数计算是事件驱动的全托管计算服务。使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码或镜像。函数计算为您准备好计算资源,弹性地、可靠地运行任务,并提供日志查询、性能监控和报警等功能。

MCP交互协议解析

在解析 MCP 亲和性实现原理前,需重点解析其基于 SSE(Server-Sent Events)构建的通信框架。该协议通过定义标准化事件类型,实现了客户端-服务端的交互控制及会话保持机制,具体流程如下:

  1. 会话建立阶段:
    • 客户端发起初始 SSE 连接请求;
    • 服务端通过 event:'endpoint' 事件响应,在 data 字段中嵌入唯一会话标识(Session ID);
  2. 请求保持机制
    • 后续所有客户端请求必须携带该 Session ID;
    • 服务端通过该标识验证请求来源的合法性;
    • 实现客户端与服务端实例的绑定关联(Session Affinity);
  3. 实例绑定校验:
    • 当 messages 请求的路由目标实例与SSE连接绑定实例不一致时;
    • 服务端将触发安全校验失败机制,返回 4xx Conflict 错误代码;

该设计通过事件驱动架构确保了会话状态的连续性,同时通过实例绑定校验机制保障了分布式环境下的请求一致性。需要注意的是,Session ID 的有效期与 SSE 连接生命周期严格绑定,连接中断后需重新进行会话协商,一个简单的交互流程示例如下:

image.png

  1. Client 端发起一个 GET 请求,建立 SSE 长连接。(Connection1)
  2. Server 端回复event:endpoint类型的事件,将 sessionId 信息放入 data 中返回。(Connection1)
  3. Client 端使用第2步返回的 sessionId 信息发起首个 HTTP POST 请求。(Connection2)
  4. Server 端迅速响应202,但无内容。(Connection2)
  5. Server 端返回第3步请求的实际消息。(Connection1)
  6. Client 端使用第2步返回的 sessionId 发起 HTTP POST 请求initialized作为确认。(Connection3)
  7. Server 端迅速响应202,无内容。(Connection3)
  8. Client 端使用第2步返回的 sessionId 发起 HTTP POST 请求list tools。(Connection4)
  9. Server 端迅速响应202,无内容。(Connection4)
  10. Server 端返回第8步请求的实际消息,即工具列表。(Connection1)
  11. Client 端使用第2步返回的 sessionId 发起 HTTP POST 请求call tool。(Connection5)
  12. Server 端迅速响应202,无内容。(Connection5)
  13. Server 端返回第11步请求的实际消息,即工具调用结果。(Connection1)

亲和性机制解析

宏观分类与核心价值

系统亲和性主要分为两大维度:

  1. 节点亲和性:面向资源调度场景,确保工作负载优先部署至符合标签规则的节点。
  2. 会话亲和性:面向请求路由场景,保障客户端流量持续定向到特定后端实例。

两类机制均通过属性一致性调度实现核心价值:

  • 提升局部资源复用率(如缓存命中)
  • 保障有状态业务连续性
  • 满足合规性数据路由要求

会话亲和性实现范式

而常见的会话亲和性主要有以下几类:

  1. Cookie 植入模式:首请求时 LB、网关类服务注入含后端标识的 Set-Cookie 头,后续请求基于 Cookie 值进行会话绑定。适用 HTTP 无状态协议场景,且客户端缺乏显式标识信息。
  2. 源 IP 哈希模式:基于 ClientIP 哈希值映射到后端特定节点,满足 TCP/UDP 四层流量及需要客户端级会话保持的场景。
  3. Header 字段路由模式:预定义 Header 字段值提取(如 X-Session-ID),并哈希计算生成目标映射,支持多客户端标识共存场景,满足细粒度路由策略。

MCP SSE 亲和性架构特性

MCP SSE 采用双阶段协商机制:

  1. 会话建立阶段:MCP Server 生成全局唯一 SessionID 并同步至客户端
  2. 请求路由阶段:网关通过专有协议实时获取 Session-Node 映射关系

该模式需网关层与 MCP Server 间实现会话信息同步。相较于传统会话亲和方案,在获得精确路由控制能力的同时,需权衡协议交互带来的系统复杂度提升。

MCP ON FC 亲和调度设计

函数计算支持一键托管 MCP Server,并通过深度适配 MCP SSE 协议,提供了一种即开即用的 Serverless 亲和调度能力,帮助您实现 MCP 服务的 Serverless 托管能力,下面将详细介绍函数计算的亲和策略机制。

亲和策略

函数计算作为集调度、计算托管、免运维等特性于一身的 Serverless 服务,可将函数计算核心组件抽象为三部分:

  1. Gateway:网关层,用户流量入口,负责接收用户请求、鉴权、流控等功能。
  2. Scheduler:调度引擎层,负责将用户的请求调度到合适的节点和实例。
  3. VMS:资源层,函数执行环境 。

当客户通过函数计算托管 MCP 服务并通过 MCP Client 发起请求时,可将用户请求分为两类:SSE 管控链路和 Message 数据链路。
SSE 管控链路(会话初始化)
image.png

  1. Client 发起首个 SSE 请求路由到一台函数计算网关节点 Gateway1,网关节点权限校验通过后转发至调度模块 Scheduler。
  2. 调度模块根据特定标识识别出请求类型为 SSE 时,将调度到一台可用实例。
  3. 当请求和实例绑定时,实例将启动用户代码。
  4. 用户代码启动完成后,会通过event:endpoint事件将 sessionId 放入 data 中,返回第一个数据包。
  5. 在 response 返回经过 Gateway 网关层时,网关层将拦截 SSE 请求的首个回包,解析 SessionID 信息,并将 SessionID 和实例的映射关系持久化到DB。

Message 数据链路(请求处理)

image.png

  1. Client 完成SSE请求后,将发起多个 Message 请求,由于函数计算网关节点无状态,Message 请求将打散到多个网关节点。
  2. 当 Gateway 收到 Message 请求,将检查网关节点 cache 中是否存在 Message 请求携带的 SessionID 亲和信息,如果 cache 中无记录,将回源到 DB 获取相关数据。
  3. Gateway 通过 cache 或 DB 拿到 SessionID 和实例的绑定关系时,将携带相关信息转发至调度模块。
  4. 调度模块根据特定标识识别出请求类型为 Message 时,解析携带的实例信息,将请求定向调度到特定实例。
  5. 当请求和实例绑定时,MCP Server 校验请求通过,将返回202通知 Client 请求接收成功,实际数据将通过 SSE 请求建立的连接返回。

函数计算通过无状态网关层与智能调度层的协同设计,在 Serverless 架构下创新实现了 MCP SSE 会话亲和性保障。SSE 管控链路借助首包拦截实现 SessionID 与实例的动态绑定,Message 数据链路则通过多级缓存与持久化存储确保请求精准路由。该架构既保留了函数计算的弹性优势,又攻克了无状态服务处理有状态请求的难题,为 MCP 场景提供了高可靠、低延迟的 Serverless 化解决方案,同时通过冷启动优化与智能扩缩容机制,实现资源效率与性能的最佳平衡。

MCP 场景会话配额控制体系

配额冲突建模分析

在 MCP 会话资源需求模型中,单个 Session 生命周期内存在两类并发需求:

image.png

而这种并发需求在传统配额分配中存在严重缺陷,需要引入一种动态预留的配额分配策略:

image.png

动态配额分配策略

为避免上述问题,函数计算引入 Session Quota 策略,即结合函数实例的并发度配置,限制每个实例最多绑定 Round (函数单实例多并发配置 / 10) 个 Session。如下流程所示:

  1. 当函数配置了20并发时,可服务 20/10=2 个 Session 请求。
  2. Client1 发起 SSE 请求时,分配了VM1实例,并占用1 Session Quota。
  3. Client2 发起 SSE 请求时,Scheduler 计算 VM1 仍有1 Session Quota,成功和 VM1 再次完成绑定。
  4. Client3 发起 SSE 请求时,Scheduler 计算 VM1 2个并发 Session Quota 已被2个 SSE 请求占用,无法再次绑定,则调度到新实例 VM2,完成实例绑定。

image.png

MCP 会话场景灰度优雅升级方案

函数计算支持 UpdateFunction 操作更新函数配置,在用户更新函数后,新的请求将路由到新配置拉起的实例,旧实例不再接收新请求,在处理完存量请求后后台自动销毁。如下图在 UpdateFunction 前,请求1-n路由到 VM1,UpdateFunction 后新请求路由到 VM2,VM1 在处理完存量请求后自动销毁。

image.png

在 MCP 场景下,数据请求从请求级无状态变为会话级绑定,在 UpdateFunction后,如果存量 Session 关联的请求路由到新实例,则新增无法识别到 SessionID 信息,返回错误。为解决这类问题,函数计算优雅更新能力从升级至有状态 Session 级别,在用户更新函数后,存量 Session 关联的请求仍路由到旧实例,新建 Session 请求路由至新实例,优雅实现 MCP 亲和场景下的升级需求。

image.png

压测

压测准备

我们以一个简单的 MCP Server 服务托管到函数计算作为压测对象, 函数代码如下:

import random
import asyncio
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount

mcp = FastMCP("My App")

@mcp.tool()
async def add(a: int, b: int) -> int:
    """计算两个整数的和(含150-1000ms随机延迟)"""
    delay = random.uniform(0.15, 1.0)
    await asyncio.sleep(delay) 

    print(f"add工具被调用,延迟 {delay:.3f}s")
    return a + b

app = Starlette(
    routes=[
        Mount("/", app=mcp.sse_app()),
    ]
)

压测脚本 load_test.py 如下:

# 并发 100 个 mcp client
# 每次 client 建立一条 SSE 连接, 执行 call tool
# 并且校验 call tool 的结果是符合预期的

import asyncio
from mcp.client.sse import sse_client
from mcp import ClientSession
import logging
import time
import random
import traceback

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("LoadTestClient")

class ErrorCounter:
    def __init__(self):
        self.count = 0
        self._lock = asyncio.Lock()

    async def increment(self):
        async with self._lock:
            self.count += 1

err_counter = ErrorCounter()

async def robust_client_instance(instance_id: int):
    try:
        async with sse_client(
            "https://xxx-yyy-zzz.cn-hangzhou.fcapp.run/sse",
            headers={
   
                "Authorization": "Bearer YOUR_API_KEY",
                "X-Instance-ID": str(instance_id),
            },
            timeout=10
        ) as streams:
            if streams is None:
                raise ConnectionError("SSE连接失败")

            async with ClientSession(
                read_stream=streams[0],
                write_stream=streams[1],
            ) as session:
                # 初始化及调用逻辑
                start = time.time()
                await asyncio.wait_for(session.initialize(), timeout=10.0)
                logger.info(f"实例{instance_id}; initialize 耗时: {time.time() - start}")

                try:
                    # 设置call_tool超时
                    start = time.time()
                    random_number = random.randrange(1, 51)
                    result = await asyncio.wait_for(
                        session.call_tool(
                            "add", {
   "a": instance_id, "b": random_number}
                        ),
                        timeout=10.0,
                    )
                    logger.info(
                        f"实例{instance_id};  call_tool 耗时: {time.time() - start}"
                    )

                    if result.isError:
                        logger.error(
                            f"实例{instance_id}, call_tool 调用失败: {result.content}"
                        )
                        raise Exception(
                            f"实例{instance_id}, call_tool 调用失败: {result.content}"
                        )
                    else:
                        logger.info(
                            f"实例{instance_id}, call_tool 调用成功: {result.content}"
                        )
                        assert (
                            int(result.content[0].text)
                            == instance_id + random_number
                        )
                except asyncio.TimeoutError:
                    logger.error(f"实例{instance_id} 操作超时")


    except asyncio.TimeoutError:
        await err_counter.increment()
        logger.error(f"实例{instance_id} 操作超时")
    except Exception as e:
        await err_counter.increment()
        logger.error(f"实例{instance_id} 失败: {str(e)}")
        logger.debug(traceback.format_exc())

async def main():
    BATCH_SIZE = 100
    tasks = [robust_client_instance(j) for j in range(0, BATCH_SIZE)]
    await asyncio.gather(*tasks, return_exceptions=True)

    print(f"总错误数: {err_counter.count}")

if __name__ == "__main__":
    asyncio.run(main())

压测结果:

执行压测命令, 并行启动 3 个压测进程, 每次压测进程并发启动 100 mcp client, 每次 client 建立一条 SSE 连接, 执行 call tool, 并且校验 call tool 的结果是符合预期的

python load_test.py & python load_test.py & python load_test.py & wait

3 个压测进程, 每个进程中的 100 个并发的 mcp client 全部成功执行

表示 SSE 长连接和后续配合该会话的 HTTP 请求(call tool)在同一个函数实例,实现亲和行为

image.png

MCP Server 函数实例从 0 毫秒级扩容到 15 个实例,实现有状态实例水平横向扩展

根据官方文档 MCP SSE亲和性调度 描述,该函数设置的单实例并发度为 200,那么单个实例支持的 session 数目(即 SSE 长连接个数为 20个), 300/20 = 15 实例

image.png

测试更新函数, MCP 会话灰度优雅升级

我们尝试在持续压测过程中,中途更新函数, 所有的 mcp client 的行为都符合预期无报错。

我们使用如下脚本模拟持续压测:

...
# 压测(分50批)
BATCH_SIZE = 100
for i in range(0, 5000, BATCH_SIZE):
    tasks = [robust_client_instance(j) for j in range(0, BATCH_SIZE)]
    await asyncio.gather(*tasks, return_exceptions=True)
    await asyncio.sleep(1)  # 批次间间隔

总结

通过系列技术方案的精妙设计,函数计算为 MCP 场景构筑了 Serverless 化的完整技术基座。在 Serverless 服务范式与有状态业务需求之间架起智能桥梁,通过会话亲和调度引擎、优雅会话升级、动态配额熔断等机制,让 Serverless 的极致弹性与有状态服务的强一致性实现了深度融合。

相关文章
|
10月前
|
安全 Serverless API
MCP Server 之旅第 5 站:服务鉴权体系解密
本文深入探讨了MCP协议在授权机制上的演进与函数计算对MCP场景下Auth的支持。文章从MCP协议的授权发展入手,分析了2024-11-05无授权支持到2025-03-26基于OAuth 2.1的授权机制,再到最新Draft中引入Protected Resource Metadata(RFC9728)的变化。同时,详细介绍了函数计算如何通过Bearer认证方式解决MCP场景下的授权问题,帮助开发者降低开发成本并提升安全性。
pip镜像源大全及配置
在中国使用pip时,可以配置国内镜像源来提高安装速度和稳定性。以下是一些常见的国内镜像源:
22010 0
|
12月前
|
人工智能 JSON 安全
MCP Server 实践之旅第 1 站:MCP 协议解析与云上适配
本文深入解析了Model Context Protocol(MCP)协议,探讨其在AI领域的应用与技术挑战。MCP作为AI协作的“USB-C接口”,通过标准化数据交互解决大模型潜力释放的关键瓶颈。文章详细分析了MCP的生命周期、传输方式(STDIO与SSE),并提出针对SSE协议不足的优化方案——MCP Proxy,实现从STDIO到SSE的无缝转换。同时,函数计算平台被推荐为MCP Server的理想运行时,因其具备自动弹性扩缩容、高安全性和按需计费等优势。最后,展望了MCP技术演进方向及对AI基础设施普及的推动作用,强调函数计算助力MCP大规模落地,加速行业创新。
2789 77
|
4月前
|
人工智能 运维 Serverless
AgentScope 拥抱函数计算 FC,为 Agent 应用提供 Serverless 运行底座
AgentScope推出Serverless运行时,直面AI Agent部署成本高、运维复杂、资源利用率低三大痛点。通过“按需启动、毫秒弹性、零运维”架构,实现低成本、高弹性、强隔离的智能体部署,助力多智能体应用从实验迈向规模化落地。
|
11月前
|
人工智能 Serverless API
MCP Server 之旅第 4 站: 长连接闲置计费最高降低87%成本的技术内幕
阿里云函数计算(FC)提供事件驱动的全托管计算服务,支持 MCP Server 场景优化。通过 [MCP Runtime](https://mp.weixin.qq.com/s/_DSMRovpr12kkiQUYDtAPA),实现 Stdio MCP Server 一键托管,并借助亲和性调度解决 Session 保持问题。针对 MCP Server 的稀疏调用特性,函数计算引入长连接闲置计费机制,在毫秒级计费基础上,显著降低资源闲置成本(最高可达87%)。用户可通过控制台或 API 开启该功能,Websocket 长请求场景亦默认支持。此方案有效提升资源利用率,为用户提供灵活、经济的计算服务。
|
12月前
|
人工智能 网络协议 Linux
MCP 协议: Streamable HTTP 是最佳选择
随着AI应用变得越来越复杂并被广泛部署,原有的通信机制面临着一系列挑战。近期MCP仓库的PR #206引入了一个全新的Streamable HTTP传输层替代原有的HTTP+SSE传输层。本文将详细分析该协议的技术细节和实际优势。
6870 102
|
7月前
|
存储 人工智能 Serverless
FunctionAI 图像生成:简化从灵感到 API 调用的每一步
FunctionAI 图像生成服务助力企业突破AI图像应用的三大难题:高成本算力、复杂运维与工程化壁垒。基于Serverless架构,提供从项目开发到API调用的全生命周期管理,支持ComfyUI、Stable Diffusion等主流工具,实现“一键部署、秒级调试、快速上线”。弹性伸缩、按需付费,大幅降低成本;国内网络加速、模型缓存、安全隔离,保障高效与稳定。让创意从灵感到生产无缝转化,真正驱动业务增长。
|
人工智能 Java Serverless
【MCP教程系列】搭建基于 Spring AI 的 SSE 模式 MCP 服务并自定义部署至阿里云百炼
本文详细介绍了如何基于Spring AI搭建支持SSE模式的MCP服务,并成功集成至阿里云百炼大模型平台。通过四个步骤实现从零到Agent的构建,包括项目创建、工具开发、服务测试与部署。文章还提供了具体代码示例和操作截图,帮助读者快速上手。最终,将自定义SSE MCP服务集成到百炼平台,完成智能体应用的创建与测试。适合希望了解SSE实时交互及大模型集成的开发者参考。
14720 60
|
6月前
|
人工智能 文字识别 并行计算
为什么别人用 DevPod 秒启 DeepSeek-OCR,你还在装环境?
DevPod 60秒极速启动,一键运行DeepSeek OCR大模型。告别环境配置难题,云端开箱即用,支持GPU加速、VSCode/Jupyter交互开发,重塑AI原生高效工作流。
892 35