无状态 MCP 服务器:告别粘性会话,简化 AWS 横向扩容

2026-09-25 27 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

最新版 Model Context Protocol(MCP)规范移除了协议级会话。这意味着远程 MCP 服务器不再要求负载均衡器把同一客户端持续路由到同一个实例,也不必为协议会话维护专门的共享存储。

这项变化直接简化了 AWS 上的多副本部署:ALB、NLB 或 Kubernetes Service 可以把每个请求发送给任意健康实例。不过,“协议无状态”并不等于“系统没有状态”。应用数据、重试、幂等性和可观测性仍然需要由其他层明确承担。

部署模型发生了什么变化

依赖会话的服务通常需要保存客户端上下文。请求一旦被路由到另一台服务器,新实例可能无法继续处理,因此部署时往往要选择以下方案之一:

  • 在负载均衡器上启用 sticky session;
  • 将协议会话存入 Redis、数据库等共享存储;
  • 在实例扩缩容和滚动发布期间迁移或保留会话;
  • 接受实例失效后会话中断。

协议级会话被移除后,每个 MCP 请求都可以独立路由:

MCP Client
    |
    v
AWS Load Balancer
    |------> MCP Server A
    |------> MCP Server B
    `------> MCP Server C
                 |
                 v
       External state and tools

服务器实例因此更接近可替换的计算单元。实例重启、扩容或滚动更新时,不再需要恢复 MCP 协议会话。对于 ECS、EKS 或基于 EC2 Auto Scaling Group 的部署,这会减少扩缩容过程中对连接归属的依赖。

需要注意,无状态化解决的是协议路由问题,不会自动解决业务状态。例如,一个工具调用创建了工单、启动了训练任务或触发了付款,这些结果仍然必须存储在数据库、队列或外部服务中。

在 EKS 上取消会话亲和性

下面是一个可以改造的 Kubernetes 部署示例。运行前需要把镜像地址替换为自己的 MCP Server 镜像,并确认应用提供 /healthz 健康检查端点。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mcp-server
  template:
    metadata:
      labels:
        app: mcp-server
    spec:
      containers:
        - name: mcp-server
          image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/mcp-server:1.0.0
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 3
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: "1"
              memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
  name: mcp-server
spec:
  type: LoadBalancer
  sessionAffinity: None
  selector:
    app: mcp-server
  ports:
    - name: http
      port: 80
      targetPort: http

保存为 mcp-server.yaml 后执行:

kubectl apply -f mcp-server.yaml
kubectl rollout status deployment/mcp-server
kubectl get pods -l app=mcp-server
kubectl get service mcp-server

这里最关键的不是 replicas: 3,而是任意副本都能独立处理请求。应用不应假设“上一次请求一定到达当前进程”,也不应只在进程内存中保存后续调用必需的数据。

如果使用 AWS Load Balancer Controller,还应检查 Ingress 或 Target Group 配置,确保没有因为历史部署而遗留粘性 Cookie。是否保留长连接则是另一个问题:连接生命周期和协议会话不是同一个概念,不能仅通过关闭粘性路由来推断长连接行为。

状态消失了吗?只是换了归属

协议无状态之后,职责边界需要重新划分。

应用状态

业务结果应保存在具有明确生命周期的系统中,例如 DynamoDB、Aurora、S3、ElastiCache 或任务队列。进程内缓存可以用于提速,但不能成为正确性的唯一来源。

长时间运行的操作可以返回业务任务 ID,客户端随后使用该 ID 查询状态。这个 ID 属于应用模型,而不是为了维持 MCP 路由而创建的协议会话。

重试与幂等性

负载均衡器、SDK 或客户端可能重试失败请求。查询类工具通常容易重试,但带副作用的工具可能造成重复写入。

下面的请求仅用于演示外围 HTTP 层如何传递幂等键;具体 URL、认证方式和工具参数需要按实际 MCP SDK 与服务实现调整:

curl --request POST 'https://mcp.example.com/mcp' \
  --header 'Authorization: Bearer REPLACE_ME' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: ticket-2025-00042' \
  --header 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \
  --data '{
    "jsonrpc": "2.0",
    "id": "req-42",
    "method": "tools/call",
    "params": {
      "name": "create_ticket",
      "arguments": {
        "title": "Database connection timeout"
      }
    }
  }'

Idempotency-Key 并不是“加上请求头就自动生效”。服务端需要把它与调用者、工具名称和参数摘要关联,并持久化执行状态与结果。重复请求到达另一个副本时,该副本才能返回已有结果,而不是再次执行副作用。

还要为幂等记录设置过期时间,并明确处理以下情况:

  • 同一个键携带了不同参数;
  • 第一次执行超时,但后端操作实际上已成功;
  • 工具执行到一半失败;
  • 两个副本同时收到相同的键。

可观测性

取消会话后,不能再依赖某个实例的本地日志拼接完整调用过程。建议每个请求都携带请求 ID 或分布式追踪上下文,并在负载均衡器、MCP Server、工具适配器和下游服务中继续传播。

至少应记录:

  • 请求 ID、追踪 ID和调用者身份;
  • MCP 方法及工具名称;
  • 目标实例、处理时间和结果状态;
  • 重试次数与幂等命中情况;
  • 下游依赖的错误类型,但不要记录令牌或敏感参数。

上线前的检查清单

采用无状态 MCP 部署时,可以逐项确认:

  • 任意健康副本是否都能处理下一次请求;
  • 实例重启后,业务正确性是否仍然成立;
  • 负载均衡器是否关闭了不再需要的会话亲和性;
  • 带副作用的工具是否定义了幂等和重试策略;
  • 长任务是否使用外部任务状态,而不是进程内对象;
  • 身份认证和授权信息是否能随每个请求被验证;
  • 日志、指标和追踪是否能跨实例关联;
  • 扩容测试是否覆盖并发写入、重复请求和实例中断。

无状态 MCP 的核心收益不是少配一个负载均衡参数,而是让计算实例真正可替换。与此同时,原先被协议会话隐式承担的责任会显性地落到应用、数据层和运维体系中。把这些边界设计清楚,横向扩容才会从“能够增加副本”变成“增加副本后仍然正确”。


相关推荐