Files
mcphub/docs/development/cluster-design.mdx

1222 lines
42 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<string, ClusterNode>; // 已注册节点
private replicas: Map<string, ServerReplica[]>; // Server -> 副本列表
private sessions: Map<string, SessionRoute>; // 会话路由表
private replicaConfigs: Map<string, ReplicaConfig>; // 副本配置
/**
* 注册 Worker 节点
*/
async registerNode(nodeInfo: ClusterNode): Promise<void>;
/**
* 处理心跳
*/
async handleHeartbeat(nodeId: string, status: NodeHeartbeat): Promise<void>;
/**
* 分配 MCP Server 到 Worker
*/
async allocateServer(serverName: string, nodeId?: string): Promise<ServerReplica>;
/**
* 根据负载均衡策略选择节点
*/
async selectNode(serverName: string): Promise<ClusterNode>;
/**
* 获取或创建会话路由
*/
async getOrCreateSessionRoute(sessionId: string, serverName: string): Promise<SessionRoute>;
/**
* 代理请求到 Worker 节点
*/
async proxyToWorker(nodeId: string, path: string, options: RequestInit): Promise<Response>;
/**
* 同步配置到所有 Worker
*/
async syncConfigToWorkers(config: any): Promise<void>;
/**
* 获取集群状态摘要
*/
async getClusterStatus(): Promise<ClusterStatus>;
/**
* 设置 Server 副本数
*/
async setServerReplicas(serverName: string, replicas: number): Promise<void>;
/**
* 重新调度副本 (节点故障时)
*/
async rescheduleReplicas(failedNodeId: string): Promise<void>;
}
```
### 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<string, ServerInfo>; // 本地运行的 Server
/**
* 连接到 Master 并注册
*/
async connectToMaster(): Promise<void>;
/**
* 发送心跳
*/
async sendHeartbeat(): Promise<void>;
/**
* 启动 MCP Server
*/
async startServer(serverName: string, config: ServerConfig): Promise<ServerReplica>;
/**
* 停止 MCP Server
*/
async stopServer(serverName: string): Promise<void>;
/**
* 获取本地 Server 状态
*/
getLocalServerStatus(): ServerInfo[];
/**
* 处理来自 Master 的配置更新
*/
async handleConfigUpdate(config: any): Promise<void>;
}
```
### 3.4 请求代理服务
```typescript
// src/services/clusterProxyService.ts
/**
* 请求代理服务 - 运行在 Master 节点
* 职责:
* - 代理 SSE 连接到 Worker
* - 代理 MCP 请求到 Worker
* - 维护代理连接池
*/
export class ClusterProxyService {
private connectionPool: Map<string, ProxyConnection>;
/**
* 代理 SSE 连接
* 建立 Client -> Master -> Worker 的 SSE 通道
*/
async proxySseConnection(
req: Request,
res: Response,
targetNode: ClusterNode,
sessionId: string
): Promise<void>;
/**
* 代理 MCP POST 请求
*/
async proxyMcpRequest(
req: Request,
res: Response,
targetNode: ClusterNode,
sessionId: string
): Promise<void>;
/**
* 代理 Tool 调用请求
*/
async proxyToolCall(
serverName: string,
toolName: string,
args: any,
sessionId: string
): Promise<any>;
}
```
## 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<string, string>; // 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<string, number>; // 节点失败计数
/**
* 执行健康检查
*/
async checkNode(node: ClusterNode): Promise<HealthStatus> {
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<void> {
// 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<void> {
// ... 现有初始化代码 ...
// 条件初始化集群功能
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