diff --git a/docs/development/cluster-design.mdx b/docs/development/cluster-design.mdx new file mode 100644 index 0000000..2d3081f --- /dev/null +++ b/docs/development/cluster-design.mdx @@ -0,0 +1,1221 @@ +--- +title: 'MCPHub Cluster Design' +description: 'Detailed design document for MCPHub cluster functionality supporting multi-node deployment' +--- + +# MCPHub 集群功能设计方案 + +## 1. 概述 + +### 1.1 设计目标 + +本方案旨在为 MCPHub 引入集群功能,支持多节点部署,实现以下目标: + +- **高可用性**:通过多节点部署,避免单点故障 +- **水平扩展**:支持动态增加 Worker 节点,提升 MCP Server 承载能力 +- **负载均衡**:智能分发客户端请求到各节点,均衡系统负载 +- **会话亲和性**:保证客户端 SSE 连接的稳定性(Sticky Session) +- **副本管理**:支持为单个 MCP Server 配置多个运行副本 +- **兼容现有部署**:不影响现有单节点部署和使用方式 + +### 1.2 架构概览 + +``` + ┌─────────────────────────────────────────────────────────┐ + │ Master Node │ + │ ┌──────────┐ ┌───────────┐ ┌───────────────────────┐│ + Client Requests ───────►│ │ UI │ │ API │ │ Cluster Coordinator ││ + (HTTP/SSE/MCP) │ │ (React) │ │ Routes │ │ - Node Registry ││ + │ └──────────┘ └───────────┘ │ - Load Balancer ││ + │ │ - Session Manager ││ + │ ┌───────────────────────────┐│ - Health Monitor ││ + │ │ Local MCP Servers │└───────────────────────┘│ + │ │ (Optional) │ │ + │ └───────────────────────────┘ │ + └───────────────────────┬─────────────────────────────────┘ + │ + ┌─────────────────────────────┼─────────────────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ + │ Worker Node 1 │ │ Worker Node 2 │ │ Worker Node N │ + │ ┌────────────────┐ │ │ ┌────────────────┐ │ │ ┌────────────────┐ │ + │ │ MCP Server A │ │ │ │ MCP Server A │ │ │ │ MCP Server C │ │ + │ │ (Replica 1) │ │ │ │ (Replica 2) │ │ │ │ (Replica 1) │ │ + │ ├────────────────┤ │ │ ├────────────────┤ │ │ ├────────────────┤ │ + │ │ MCP Server B │ │ │ │ MCP Server C │ │ │ │ MCP Server D │ │ + │ │ (Replica 1) │ │ │ │ (Replica 2) │ │ │ │ (Replica 1) │ │ + │ └────────────────┘ │ │ └────────────────┘ │ │ └────────────────┘ │ + └────────────────────┘ └────────────────────┘ └────────────────────┘ +``` + +## 2. 节点类型与职责 + +### 2.1 Master 节点(主节点) + +Master 节点是集群的控制中心,负责: + +| 职责 | 描述 | +|------|------| +| **UI 服务** | 对外提供 React 前端管理界面 | +| **API 网关** | 统一接收所有客户端 API 请求 | +| **SSE/MCP 端点** | 提供 `/sse`, `/mcp` 端点,代理到对应的 Worker | +| **节点注册** | 管理 Worker 节点的注册与注销 | +| **健康监控** | 监控所有节点和 MCP Server 的运行状态 | +| **负载均衡** | 根据策略分发请求到各 Worker 节点 | +| **会话管理** | 维护 Session 到 Worker 的映射关系 | +| **配置同步** | 将配置变更同步到所有 Worker 节点 | +| **可选:运行 MCP Server** | Master 也可以运行本地 MCP Server | + +### 2.2 Worker 节点(从节点) + +Worker 节点是实际运行 MCP Server 的工作节点: + +| 职责 | 描述 | +|------|------| +| **运行 MCP Server** | 执行实际的 MCP Server 进程 | +| **心跳上报** | 定期向 Master 上报自身状态 | +| **接收配置** | 从 Master 接收 MCP Server 配置 | +| **暴露内部 API** | 提供 Master 调用的内部管理 API | +| **不对外暴露端点** | 不直接对外提供 UI/API/SSE/MCP 端点 | + +## 3. 核心模块设计 + +### 3.1 新增类型定义 + +```typescript +// src/types/cluster.ts + +/** + * 节点角色 + */ +export type NodeRole = 'master' | 'worker'; + +/** + * 节点状态 + */ +export type NodeStatus = 'online' | 'offline' | 'unhealthy'; + +/** + * 集群节点信息 + */ +export interface ClusterNode { + id: string; // 节点唯一标识 (UUID) + role: NodeRole; // 节点角色 + host: string; // 节点主机地址 (IP 或域名) + port: number; // 节点端口 + internalPort?: number; // 内部通信端口 (默认与 port 相同) + status: NodeStatus; // 节点状态 + lastHeartbeat: number; // 最后心跳时间戳 + registeredAt: number; // 注册时间戳 + metadata?: { + hostname?: string; // 主机名 + version?: string; // MCPHub 版本 + os?: string; // 操作系统 + cpuUsage?: number; // CPU 使用率 + memoryUsage?: number; // 内存使用率 + }; +} + +/** + * MCP Server 副本信息 + */ +export interface ServerReplica { + id: string; // 副本唯一标识 + serverName: string; // MCP Server 名称 + nodeId: string; // 运行节点 ID + status: 'running' | 'stopped' | 'error'; + createdAt: number; + error?: string; +} + +/** + * MCP Server 副本配置 + */ +export interface ReplicaConfig { + serverName: string; // MCP Server 名称 + replicas: number; // 期望副本数 + affinity?: { + nodeIds?: string[]; // 首选节点列表 + excludeNodeIds?: string[]; // 排除节点列表 + }; + strategy?: 'round-robin' | 'least-connections' | 'random'; +} + +/** + * 会话路由信息 + */ +export interface SessionRoute { + sessionId: string; // 客户端会话 ID + nodeId: string; // 关联的 Worker 节点 ID + serverName: string; // 关联的 MCP Server 名称 + replicaId: string; // 关联的副本 ID + createdAt: number; + lastActivity: number; +} + +/** + * 集群配置 + */ +export interface ClusterConfig { + enabled: boolean; // 是否启用集群模式 + role: NodeRole; // 当前节点角色 + nodeId?: string; // 节点 ID (自动生成或指定) + + // Master 节点配置 + master?: { + host: string; // Master 地址 (Worker 连接用) + port: number; + heartbeatInterval?: number; // 心跳间隔 (毫秒, 默认 5000) + heartbeatTimeout?: number; // 心跳超时 (毫秒, 默认 15000) + loadBalanceStrategy?: 'round-robin' | 'least-connections' | 'weighted'; + }; + + // Worker 节点配置 + worker?: { + masterUrl: string; // Master 节点 URL + advertiseHost?: string; // Worker 对外地址 (NAT 场景) + advertisePort?: number; // Worker 对外端口 + maxServers?: number; // 最大运行 Server 数量 + }; + + // 通信安全配置 + security?: { + token?: string; // 节点间通信 Token + tlsEnabled?: boolean; // 是否启用 TLS + tlsCert?: string; // TLS 证书路径 + tlsKey?: string; // TLS 私钥路径 + }; +} +``` + +### 3.2 集群协调器服务 (Master 端) + +```typescript +// src/services/clusterCoordinatorService.ts + +/** + * 集群协调器 - 运行在 Master 节点 + * 职责: + * - 管理 Worker 节点注册与心跳 + * - 负载均衡与请求路由 + * - 会话亲和性管理 + * - 副本调度与管理 + */ +export class ClusterCoordinatorService { + private nodes: Map; // 已注册节点 + private replicas: Map; // Server -> 副本列表 + private sessions: Map; // 会话路由表 + private replicaConfigs: Map; // 副本配置 + + /** + * 注册 Worker 节点 + */ + async registerNode(nodeInfo: ClusterNode): Promise; + + /** + * 处理心跳 + */ + async handleHeartbeat(nodeId: string, status: NodeHeartbeat): Promise; + + /** + * 分配 MCP Server 到 Worker + */ + async allocateServer(serverName: string, nodeId?: string): Promise; + + /** + * 根据负载均衡策略选择节点 + */ + async selectNode(serverName: string): Promise; + + /** + * 获取或创建会话路由 + */ + async getOrCreateSessionRoute(sessionId: string, serverName: string): Promise; + + /** + * 代理请求到 Worker 节点 + */ + async proxyToWorker(nodeId: string, path: string, options: RequestInit): Promise; + + /** + * 同步配置到所有 Worker + */ + async syncConfigToWorkers(config: any): Promise; + + /** + * 获取集群状态摘要 + */ + async getClusterStatus(): Promise; + + /** + * 设置 Server 副本数 + */ + async setServerReplicas(serverName: string, replicas: number): Promise; + + /** + * 重新调度副本 (节点故障时) + */ + async rescheduleReplicas(failedNodeId: string): Promise; +} +``` + +### 3.3 Worker 代理服务 (Worker 端) + +```typescript +// src/services/clusterWorkerService.ts + +/** + * Worker 代理服务 - 运行在 Worker 节点 + * 职责: + * - 向 Master 注册和发送心跳 + * - 接收并执行 MCP Server 管理指令 + * - 上报本地 Server 状态 + */ +export class ClusterWorkerService { + private masterClient: MasterClient; // Master 通信客户端 + private localServers: Map; // 本地运行的 Server + + /** + * 连接到 Master 并注册 + */ + async connectToMaster(): Promise; + + /** + * 发送心跳 + */ + async sendHeartbeat(): Promise; + + /** + * 启动 MCP Server + */ + async startServer(serverName: string, config: ServerConfig): Promise; + + /** + * 停止 MCP Server + */ + async stopServer(serverName: string): Promise; + + /** + * 获取本地 Server 状态 + */ + getLocalServerStatus(): ServerInfo[]; + + /** + * 处理来自 Master 的配置更新 + */ + async handleConfigUpdate(config: any): Promise; +} +``` + +### 3.4 请求代理服务 + +```typescript +// src/services/clusterProxyService.ts + +/** + * 请求代理服务 - 运行在 Master 节点 + * 职责: + * - 代理 SSE 连接到 Worker + * - 代理 MCP 请求到 Worker + * - 维护代理连接池 + */ +export class ClusterProxyService { + private connectionPool: Map; + + /** + * 代理 SSE 连接 + * 建立 Client -> Master -> Worker 的 SSE 通道 + */ + async proxySseConnection( + req: Request, + res: Response, + targetNode: ClusterNode, + sessionId: string + ): Promise; + + /** + * 代理 MCP POST 请求 + */ + async proxyMcpRequest( + req: Request, + res: Response, + targetNode: ClusterNode, + sessionId: string + ): Promise; + + /** + * 代理 Tool 调用请求 + */ + async proxyToolCall( + serverName: string, + toolName: string, + args: any, + sessionId: string + ): Promise; +} +``` + +## 4. API 设计 + +### 4.1 复用现有 API + +以下现有 API 可在 Worker 节点复用,Master 通过这些 API 与 Worker 通信: + +| API | 方法 | 用途 | +|-----|------|------| +| `/health` | GET | 节点健康检查 | +| `/api/servers` | GET | 获取 Server 列表和状态 | +| `/api/servers/:name` | GET | 获取单个 Server 配置 | +| `/api/servers` | POST | 创建 Server | +| `/api/servers/:name` | DELETE | 删除 Server | +| `/api/servers/:name/toggle` | POST | 启用/禁用 Server | +| `/api/servers/:name/reload` | POST | 重新加载 Server | +| `/api/tools/call/:server` | POST | 调用 Tool | +| `/sse/:group?` | GET | SSE 连接 | +| `/mcp/:group?` | POST | MCP Streamable HTTP 请求 | + +### 4.2 新增集群管理 API + +```typescript +// src/routes/clusterRoutes.ts + +// ==================== Master 端 API ==================== + +/** + * 获取集群状态 + * GET /api/cluster/status + */ +interface ClusterStatusResponse { + enabled: boolean; + role: 'master'; + nodes: ClusterNode[]; + totalReplicas: number; + healthyReplicas: number; + sessions: number; +} + +/** + * 获取所有节点 + * GET /api/cluster/nodes + */ +interface NodesResponse { + nodes: ClusterNode[]; +} + +/** + * 获取节点详情 + * GET /api/cluster/nodes/:nodeId + */ +interface NodeDetailResponse { + node: ClusterNode; + servers: ServerReplica[]; +} + +/** + * 移除节点 + * DELETE /api/cluster/nodes/:nodeId + */ + +/** + * 获取 Server 副本配置 + * GET /api/cluster/replicas/:serverName + */ +interface ReplicaConfigResponse { + serverName: string; + desiredReplicas: number; + currentReplicas: number; + replicas: ServerReplica[]; +} + +/** + * 设置 Server 副本数 + * PUT /api/cluster/replicas/:serverName + */ +interface SetReplicasRequest { + replicas: number; + affinity?: { + nodeIds?: string[]; + excludeNodeIds?: string[]; + }; +} + +/** + * 获取会话路由表 + * GET /api/cluster/sessions + */ +interface SessionsResponse { + sessions: SessionRoute[]; +} + +// ==================== Worker 端内部 API ==================== + +/** + * 节点注册 (Worker -> Master) + * POST /internal/cluster/register + */ +interface RegisterRequest { + nodeId: string; + host: string; + port: number; + metadata?: ClusterNode['metadata']; +} + +/** + * 心跳 (Worker -> Master) + * POST /internal/cluster/heartbeat + */ +interface HeartbeatRequest { + nodeId: string; + servers: Array<{ + name: string; + status: 'connected' | 'disconnected'; + tools: number; + }>; + metrics?: { + cpuUsage: number; + memoryUsage: number; + activeConnections: number; + }; +} + +/** + * 分配 Server (Master -> Worker) + * POST /internal/servers/allocate + */ +interface AllocateServerRequest { + serverName: string; + config: ServerConfig; + replicaId: string; +} + +/** + * 释放 Server (Master -> Worker) + * POST /internal/servers/release + */ +interface ReleaseServerRequest { + serverName: string; + replicaId: string; +} +``` + +## 5. 数据流与通信机制 + +### 5.1 Worker 节点注册流程 + +``` +┌────────────┐ ┌────────────┐ +│ Worker │ │ Master │ +└─────┬──────┘ └──────┬─────┘ + │ │ + │ 1. POST /internal/cluster/register │ + │ { nodeId, host, port, metadata } │ + │ ─────────────────────────────────────────────► │ + │ │ + │ 2. Response: { success, config, servers } │ + │ ◄───────────────────────────────────────────── │ + │ │ + │ 3. 启动分配的 MCP Servers │ + │ │ + │ 4. POST /internal/cluster/heartbeat (定期) │ + │ { nodeId, servers: [...], metrics } │ + │ ─────────────────────────────────────────────► │ + │ │ +``` + +### 5.2 客户端请求路由流程 (SSE) + +``` +┌────────────┐ ┌────────────┐ ┌────────────┐ +│ Client │ │ Master │ │ Worker │ +└─────┬──────┘ └──────┬─────┘ └──────┬─────┘ + │ │ │ + │ 1. GET /sse/group │ │ + │ ─────────────────────►│ │ + │ │ │ + │ 2. 查询会话路由表 │ │ + │ 3. 选择目标 Worker │ │ + │ │ │ + │ │ 4. GET /sse/group │ + │ │ (internal) │ + │ │ ─────────────────────►│ + │ │ │ + │ 5. SSE Stream │ ◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ + │ (proxied) │ SSE Stream │ + │ ◄─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ + │ │ │ + │ 6. POST /messages │ │ + │ ─────────────────────►│ │ + │ │ │ + │ │ 7. POST /messages │ + │ │ (to same Worker) │ + │ │ ─────────────────────►│ + │ │ │ +``` + +### 5.3 Tool 调用请求路由 + +``` +┌────────────┐ ┌────────────┐ ┌────────────┐ +│ Client │ │ Master │ │ Worker │ +└─────┬──────┘ └──────┬─────┘ └──────┬─────┘ + │ │ │ + │ 1. POST /mcp/group │ │ + │ {call_tool: xxx} │ │ + │ ─────────────────────►│ │ + │ │ │ + │ 2. 解析目标 Server │ │ + │ 3. 查找 Server 副本 │ │ + │ 4. 负载均衡选择 │ │ + │ │ │ + │ │ 5. POST /api/tools/ │ + │ │ call/:server │ + │ │ ─────────────────────►│ + │ │ │ + │ │ 6. Tool Result │ + │ │ ◄─────────────────────│ + │ │ │ + │ 7. Result (proxied) │ │ + │ ◄─────────────────────│ │ + │ │ │ +``` + +## 6. 负载均衡策略 + +### 6.1 支持的策略 + +| 策略 | 描述 | 适用场景 | +|------|------|----------| +| **Round Robin** | 轮询分发请求 | 节点配置相同时 | +| **Least Connections** | 选择当前连接数最少的节点 | 长连接场景 (SSE) | +| **Weighted** | 根据节点权重分发 | 节点性能差异大时 | + +### 6.2 会话亲和性 (Sticky Session) + +```typescript +/** + * 会话亲和性实现 + * 确保同一 Session 的所有请求路由到同一 Worker + */ +class SessionAffinityManager { + private sessionMap: Map; // sessionId -> nodeId + + /** + * 绑定会话到节点 + */ + bindSession(sessionId: string, nodeId: string): void; + + /** + * 获取会话绑定的节点 + */ + getNodeForSession(sessionId: string): string | undefined; + + /** + * 解绑会话 (连接关闭时) + */ + unbindSession(sessionId: string): void; + + /** + * 节点故障时迁移会话 + */ + migrateSessionsFromNode(failedNodeId: string, newNodeId: string): void; +} +``` + +## 7. 副本管理 + +### 7.1 副本调度算法 + +```typescript +/** + * 副本调度器 + * 负责将 MCP Server 副本分配到合适的 Worker 节点 + */ +class ReplicaScheduler { + /** + * 计算最优节点分配 + */ + schedule( + serverName: string, + desiredReplicas: number, + nodes: ClusterNode[], + currentReplicas: ServerReplica[] + ): ScheduleResult { + // 1. 过滤可用节点 + const availableNodes = nodes.filter(n => + n.status === 'online' && + !this.isNodeExcluded(n, config.affinity) + ); + + // 2. 计算每个节点的得分 + const scores = availableNodes.map(node => ({ + node, + score: this.calculateScore(node, serverName) + })); + + // 3. 按得分排序并选择 + scores.sort((a, b) => b.score - a.score); + + // 4. 返回调度结果 + return { + toCreate: [...], // 需要在哪些节点创建副本 + toRemove: [...], // 需要移除哪些副本 + }; + } + + private calculateScore(node: ClusterNode, serverName: string): number { + let score = 100; + + // 已运行副本数越少得分越高 + const currentCount = this.getReplicaCount(node.id, serverName); + score -= currentCount * 20; + + // CPU/内存使用率越低得分越高 + if (node.metadata?.cpuUsage) { + score -= node.metadata.cpuUsage * 0.5; + } + + // 节点亲和性加分 + if (this.hasAffinity(node.id, serverName)) { + score += 30; + } + + return score; + } +} +``` + +### 7.2 副本生命周期 + +``` + 创建请求 + │ + ▼ + ┌───────────────────────────────┐ + │ Pending │ + │ (等待调度到 Worker) │ + └───────────────┬───────────────┘ + │ 调度成功 + ▼ + ┌───────────────────────────────┐ + │ Starting │ + │ (Worker 启动 Server 中) │ + └───────────────┬───────────────┘ + │ 启动成功 + ▼ + ┌───────────────────────────────┐ + │ Running │◄───┐ + │ (Server 运行中) │ │ 健康检查通过 + └───────────────┬───────────────┘ │ + │ 错误/停止 │ + ▼ │ + ┌───────────────────────────────┐ │ + │ Error/Stopped │────┘ + │ (需要重试或移除) │ 重试 + └───────────────┬───────────────┘ + │ 删除 + ▼ + ┌───────────────────────────────┐ + │ Terminated │ + │ (副本已删除) │ + └───────────────────────────────┘ +``` + +## 8. 健康检查与故障恢复 + +### 8.1 健康检查机制 + +```typescript +/** + * 健康检查器 + */ +class HealthChecker { + private checkInterval: number = 5000; // 检查间隔 (毫秒) + private unhealthyThreshold: number = 3; // 连续失败次数阈值 + private failureCounts: Map; // 节点失败计数 + + /** + * 执行健康检查 + */ + async checkNode(node: ClusterNode): Promise { + try { + // 调用 Worker 的 /health 端点 + const response = await fetch(`http://${node.host}:${node.port}/health`, { + timeout: 3000, + headers: { 'X-Cluster-Token': this.token } + }); + + if (response.ok) { + this.failureCounts.set(node.id, 0); + return { healthy: true }; + } + } catch (error) { + // 累加失败次数 + const count = (this.failureCounts.get(node.id) || 0) + 1; + this.failureCounts.set(node.id, count); + + if (count >= this.unhealthyThreshold) { + return { healthy: false, reason: 'consecutive_failures' }; + } + } + + return { healthy: true }; // 尚未达到阈值 + } + + /** + * 处理节点故障 + */ + async handleNodeFailure(nodeId: string): Promise { + // 1. 标记节点为 offline + await this.coordinator.markNodeOffline(nodeId); + + // 2. 重新调度该节点上的副本 + await this.coordinator.rescheduleReplicas(nodeId); + + // 3. 通知管理员 (可选) + await this.notifyAdmin(`Node ${nodeId} is offline`); + } +} +``` + +### 8.2 故障恢复流程 + +``` +Worker 节点故障 + │ + ▼ +┌─────────────────────────────┐ +│ Master 检测到心跳超时 │ +│ (连续 3 次健康检查失败) │ +└──────────────┬──────────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ 标记节点为 offline │ +│ 更新节点状态到 UI │ +└──────────────┬──────────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ 获取故障节点上的所有副本 │ +└──────────────┬──────────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ 调用 ReplicaScheduler │ +│ 重新调度副本到其他健康节点 │ +└──────────────┬──────────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ 迁移相关会话到新节点 │ +│ (如启用会话重建) │ +└──────────────┬──────────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ 故障恢复完成 │ +│ 系统继续正常服务 │ +└─────────────────────────────┘ +``` + +## 9. 配置管理 + +### 9.1 集群配置示例 + +```json +// 环境变量或配置文件 +{ + "cluster": { + "enabled": true, + "role": "master", + "nodeId": "master-001", + "master": { + "host": "0.0.0.0", + "port": 3000, + "heartbeatInterval": 5000, + "heartbeatTimeout": 15000, + "loadBalanceStrategy": "least-connections" + }, + "security": { + "token": "your-cluster-secret-token", + "tlsEnabled": false + } + } +} +``` + +```json +// Worker 节点配置 +{ + "cluster": { + "enabled": true, + "role": "worker", + "nodeId": "worker-001", + "worker": { + "masterUrl": "http://master-host:3000", + "advertiseHost": "worker-001.internal", + "advertisePort": 3001, + "maxServers": 20 + }, + "security": { + "token": "your-cluster-secret-token" + } + } +} +``` + +### 9.2 环境变量配置 + +| 环境变量 | 描述 | 默认值 | +|----------|------|--------| +| `CLUSTER_ENABLED` | 启用集群模式 | `false` | +| `CLUSTER_ROLE` | 节点角色 (master/worker) | - | +| `CLUSTER_NODE_ID` | 节点 ID | 自动生成 UUID | +| `CLUSTER_MASTER_HOST` | Master 主机地址 | `0.0.0.0` | +| `CLUSTER_MASTER_PORT` | Master 端口 | `3000` | +| `CLUSTER_MASTER_URL` | Master URL (Worker 用) | - | +| `CLUSTER_TOKEN` | 集群通信 Token | - | +| `CLUSTER_HEARTBEAT_INTERVAL` | 心跳间隔 (ms) | `5000` | +| `CLUSTER_HEARTBEAT_TIMEOUT` | 心跳超时 (ms) | `15000` | +| `CLUSTER_ADVERTISE_HOST` | Worker 对外地址 | - | +| `CLUSTER_ADVERTISE_PORT` | Worker 对外端口 | - | + +## 10. 前端 UI 扩展 + +### 10.1 集群仪表板 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Cluster Dashboard [Refresh] │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Total Nodes │ │ Online Nodes │ │ Total Replicas │ │ +│ │ 4 │ │ 3 │ │ 12 │ │ +│ │ │ │ │ │ │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +│ │ +│ Nodes │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ ● master-001 │ Master │ 192.168.1.10:3000 │ Online │ 0/0 │ │ +│ │ ● worker-001 │ Worker │ 192.168.1.11:3001 │ Online │ 5/20 │ │ +│ │ ● worker-002 │ Worker │ 192.168.1.12:3001 │ Online │ 4/20 │ │ +│ │ ○ worker-003 │ Worker │ 192.168.1.13:3001 │ Offline│ - │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### 10.2 副本管理界面 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Server: fetch │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ Replicas: ← 2 → [Apply] │ +│ │ +│ Current Replicas: │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ fetch-replica-1 │ worker-001 │ ● Running │ Since: 2h ago │ │ +│ │ fetch-replica-2 │ worker-002 │ ● Running │ Since: 1h ago │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ Node Affinity: │ +│ □ worker-001 ☑ worker-002 □ worker-003 (offline) │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## 11. 单节点兼容性 + +### 11.1 兼容性设计 + +为确保集群功能不影响现有单节点部署: + +1. **默认关闭**:`CLUSTER_ENABLED=false` 为默认值 +2. **条件加载**:集群相关服务只在启用时初始化 +3. **代码隔离**:集群逻辑封装在独立模块,通过依赖注入使用 +4. **无缝切换**:同一镜像支持单节点/集群两种模式 + +```typescript +// src/server.ts 修改示例 + +export class AppServer { + private clusterCoordinator?: ClusterCoordinatorService; + private clusterWorker?: ClusterWorkerService; + + async initialize(): Promise { + // ... 现有初始化代码 ... + + // 条件初始化集群功能 + if (config.cluster?.enabled) { + if (config.cluster.role === 'master') { + this.clusterCoordinator = new ClusterCoordinatorService(); + await this.clusterCoordinator.start(); + console.log('Cluster coordinator started (Master mode)'); + } else if (config.cluster.role === 'worker') { + this.clusterWorker = new ClusterWorkerService(); + await this.clusterWorker.connectToMaster(); + console.log('Connected to master node (Worker mode)'); + } + } else { + console.log('Running in standalone mode'); + } + } +} +``` + +### 11.2 路由适配 + +```typescript +// src/middlewares/clusterMiddleware.ts + +/** + * 集群路由中间件 + * 在 Master 模式下,根据会话路由请求到对应 Worker + * 在单节点或 Worker 模式下,直接处理请求 + */ +export const clusterRouteMiddleware = async ( + req: Request, + res: Response, + next: NextFunction +) => { + // 单节点模式或 Worker 模式 - 直接处理 + if (!config.cluster?.enabled || config.cluster.role !== 'master') { + return next(); + } + + // Master 模式 - 检查是否需要代理 + const sessionId = req.headers['mcp-session-id'] as string; + + if (sessionId) { + const route = await clusterCoordinator.getSessionRoute(sessionId); + if (route && route.nodeId !== config.cluster.nodeId) { + // 代理到 Worker + return clusterProxy.proxyToWorker(route.nodeId, req, res); + } + } + + // 本地处理 + next(); +}; +``` + +## 12. Docker 部署示例 + +### 12.1 Docker Compose 集群配置 + +```yaml +# docker-compose.cluster.yml + +version: '3.8' + +services: + mcphub-master: + image: samanhappy/mcphub:latest + ports: + - "3000:3000" + environment: + - CLUSTER_ENABLED=true + - CLUSTER_ROLE=master + - CLUSTER_NODE_ID=master-001 + - CLUSTER_MASTER_HOST=0.0.0.0 + - CLUSTER_MASTER_PORT=3000 + - CLUSTER_TOKEN=your-secure-cluster-token + - CLUSTER_HEARTBEAT_INTERVAL=5000 + - CLUSTER_HEARTBEAT_TIMEOUT=15000 + volumes: + - ./mcp_settings.json:/app/mcp_settings.json + networks: + - mcphub-cluster + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 10s + timeout: 5s + retries: 3 + + mcphub-worker-1: + image: samanhappy/mcphub:latest + environment: + - CLUSTER_ENABLED=true + - CLUSTER_ROLE=worker + - CLUSTER_NODE_ID=worker-001 + - CLUSTER_MASTER_URL=http://mcphub-master:3000 + - CLUSTER_TOKEN=your-secure-cluster-token + - CLUSTER_ADVERTISE_HOST=mcphub-worker-1 + - CLUSTER_ADVERTISE_PORT=3000 + depends_on: + mcphub-master: + condition: service_healthy + networks: + - mcphub-cluster + + mcphub-worker-2: + image: samanhappy/mcphub:latest + environment: + - CLUSTER_ENABLED=true + - CLUSTER_ROLE=worker + - CLUSTER_NODE_ID=worker-002 + - CLUSTER_MASTER_URL=http://mcphub-master:3000 + - CLUSTER_TOKEN=your-secure-cluster-token + - CLUSTER_ADVERTISE_HOST=mcphub-worker-2 + - CLUSTER_ADVERTISE_PORT=3000 + depends_on: + mcphub-master: + condition: service_healthy + networks: + - mcphub-cluster + +networks: + mcphub-cluster: + driver: bridge +``` + +### 12.2 Kubernetes 部署 (可选) + +```yaml +# kubernetes/mcphub-cluster.yaml + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcphub-master +spec: + replicas: 1 # Master 通常只需要 1 个 + selector: + matchLabels: + app: mcphub + role: master + template: + metadata: + labels: + app: mcphub + role: master + spec: + containers: + - name: mcphub + image: samanhappy/mcphub:latest + ports: + - containerPort: 3000 + env: + - name: CLUSTER_ENABLED + value: "true" + - name: CLUSTER_ROLE + value: "master" + - name: CLUSTER_TOKEN + valueFrom: + secretKeyRef: + name: mcphub-cluster-secret + key: token + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcphub-worker +spec: + replicas: 3 # Worker 可以水平扩展 + selector: + matchLabels: + app: mcphub + role: worker + template: + metadata: + labels: + app: mcphub + role: worker + spec: + containers: + - name: mcphub + image: samanhappy/mcphub:latest + env: + - name: CLUSTER_ENABLED + value: "true" + - name: CLUSTER_ROLE + value: "worker" + - name: CLUSTER_MASTER_URL + value: "http://mcphub-master:3000" + - name: CLUSTER_TOKEN + valueFrom: + secretKeyRef: + name: mcphub-cluster-secret + key: token +``` + +## 13. 实现计划与优先级 + +### Phase 1: 基础架构 (优先级: 高) + +| 任务 | 描述 | 工作量估算 | +|------|------|-----------| +| 类型定义 | 新增集群相关 TypeScript 类型 | 1 天 | +| 配置加载 | 支持集群配置环境变量/配置文件 | 1 天 | +| ClusterCoordinatorService | Master 端核心服务框架 | 3 天 | +| ClusterWorkerService | Worker 端核心服务框架 | 2 天 | +| 节点注册与心跳 | 实现 Worker 注册和心跳机制 | 2 天 | + +### Phase 2: 请求路由 (优先级: 高) + +| 任务 | 描述 | 工作量估算 | +|------|------|-----------| +| 会话路由表 | 实现 Session 到 Node 的映射管理 | 1 天 | +| SSE 代理 | 实现 SSE 连接的透明代理 | 3 天 | +| MCP 请求代理 | 实现 MCP POST 请求代理 | 2 天 | +| 负载均衡 | 实现 Round-Robin 和 Least-Connections | 2 天 | + +### Phase 3: 副本管理 (优先级: 中) + +| 任务 | 描述 | 工作量估算 | +|------|------|-----------| +| 副本调度器 | 实现副本分配算法 | 2 天 | +| 副本 CRUD API | 实现副本管理 REST API | 1 天 | +| 自动扩缩容 | 支持动态调整副本数 | 2 天 | + +### Phase 4: 高可用与监控 (优先级: 中) + +| 任务 | 描述 | 工作量估算 | +|------|------|-----------| +| 健康检查 | 实现节点健康检查机制 | 1 天 | +| 故障检测与恢复 | 实现节点故障时的副本迁移 | 3 天 | +| 监控指标 | 收集并暴露集群指标 | 2 天 | + +### Phase 5: 前端 UI (优先级: 低) + +| 任务 | 描述 | 工作量估算 | +|------|------|-----------| +| 集群仪表板 | 新增集群状态展示页面 | 3 天 | +| 节点管理 | 节点列表和详情页面 | 2 天 | +| 副本管理 | Server 副本配置 UI | 2 天 | + +### 总工作量估算 + +- **Phase 1-2 (核心功能)**: 约 17 天 +- **Phase 3-4 (增强功能)**: 约 9 天 +- **Phase 5 (UI)**: 约 7 天 +- **总计**: 约 33 个工作日 + +## 14. 风险与缓解措施 + +| 风险 | 影响 | 缓解措施 | +|------|------|----------| +| SSE 长连接代理复杂性 | 可能导致连接不稳定 | 使用成熟的代理库,添加重连机制 | +| 网络分区 | 脑裂问题 | 实现简单的 quorum 机制,或依赖外部协调服务 | +| 会话状态丢失 | 故障转移时连接中断 | 复用现有的 Session Rebuild 机制 | +| 性能开销 | 代理层增加延迟 | 优化代理路径,支持连接复用 | +| 配置同步延迟 | Worker 配置不一致 | 使用版本号,支持配置强制刷新 | + +## 15. 未来扩展 + +1. **多 Master 高可用**: 支持 Master 节点热备 +2. **地理分布式部署**: 支持跨数据中心部署 +3. **自动伸缩**: 根据负载自动增减 Worker 节点 +4. **服务网格集成**: 与 Istio/Linkerd 等服务网格集成 +5. **Prometheus/Grafana 监控**: 提供标准监控指标和仪表板 + +--- + +**文档版本**: 1.0.0 +**最后更新**: 2024-12-05 +**作者**: MCPHub Team