# Exa-MCP-Server 多账号轮询负载均衡功能开发指南
## 第一部分:功能概述
`exa-mcp-server` 是一个基于 Model Context Protocol (MCP) 的服务器,旨在为 AI 助手提供高质量的网络搜索能力,通过集成 Exa API 实现语义搜索、实时网页内容获取等功能。当前 `exa-mcp-server` 的架构存在显著限制,主要表现为:
* **单账号限制**:仅支持单个 Exa API 密钥配置,这导致了服务容量的瓶颈。
* **并发瓶颈**:受限于单个 API 密钥的速率限制,无法有效处理高并发请求。
* **可用性风险**:一旦单个 API 密钥出现异常(如配额耗尽、被禁用),整个服务将中断,缺乏容错机制。
* **扩展性差**:无法通过简单增加 API 密钥来提升服务吞吐量和并发处理能力。
为了应对这些挑战,引入多账号轮询负载均衡功能成为必要。其核心业务需求是确保 `exa-mcp-server` 能够稳定、高效地响应大量 AI 搜索请求,即使在面对 Exa API 的速率限制和潜在故障时也能保持服务连续性。技术目标包括:
* **提升系统吞吐量和并发处理能力**:通过并行利用多个 Exa API 密钥,显著提高系统能够处理的请求量。
* **增强服务稳定性和容错能力**:在单个 API 密钥失效时,系统能够自动切换到其他可用密钥,避免单点故障。
* **优化资源利用效率**:智能调度 API 密钥,确保每个密钥的配额得到充分利用,同时避免因过度使用而触发限流。
* **支持多团队/多项目场景**:为不同的团队或项目分配独立的 API 密钥池,实现资源隔离和灵活管理。
多账号轮询负载均衡功能将为 `exa-mcp-server` 带来以下核心价值:
* **高可用性**:通过账号冗余和自动故障转移,大幅提升服务的可靠性,减少因外部 API 限制或故障导致的服务中断。
* **高性能**:实现请求的并行处理和智能调度,显著提高系统的最大 QPS (Queries Per Second) 和平均响应时间。
* **弹性伸缩**:能够根据业务需求动态增加或减少 API 密钥,实现服务的水平扩展。
* **智能管理**:引入健康检查、配额管理和自适应负载均衡策略,确保 API 密钥资源的最优利用。
## 第二部分:架构设计
`exa-mcp-balance` 模块在 `exa-mcp-server` 整体架构中扮演着核心的智能代理角色,负责管理和调度对 Exa API 的请求。其核心设计理念是**账号池化管理**和**智能负载均衡**,旨在将单一的 Exa API 密钥请求转换为通过多个密钥进行智能分发的请求流。
### 模块位置与职责
`exa-mcp-balance` 模块将位于 `exa-mcp-server` 的核心服务层,介于 MCP 客户端请求和实际的 Exa API 调用之间。其主要职责包括:
* **API 密钥管理**:维护一个包含多个 Exa API 密钥的账号池,并管理每个密钥的状态(活跃、限流、错误、耗尽)。
* **请求路由与负载均衡**:根据预设的负载均衡策略(如轮询、加权轮询、最少连接、自适应等),智能选择一个健康的 API 密钥来处理传入的请求。
* **健康监控**:定期检查每个 API 密钥的可用性和配额情况,并根据检查结果更新密钥状态。
* **错误处理与重试**:捕获 Exa API 返回的错误,并根据错误类型决定是否进行重试、切换密钥或触发降级策略。
* **配置管理**:从环境变量或配置文件中加载并管理多账号、负载均衡策略、健康检查等相关配置。
### 与现有组件的交互方式和数据流
```mermaid
graph TD
A[MCP Client] --> B{Request Router / Load Balancer}
B --> C[Account Manager]
C --> D1(Account 1)
C --> D2(Account 2)
C --> DN(Account N)
D1 --> E[Exa API]
D2 --> E
DN --> E
E --> D1
E --> D2
E --> DN
D1 --> F[Health Monitor]
D2 --> F
DN --> F
F --> C
B -- Config --> G[Config Service]
C -- Config --> G
F -- Config --> G
```
* **MCP Client (客户端层)**:发起对 `exa-mcp-server` 的搜索请求。
* **Request Router / Load Balancer (请求路由/负载均衡器)**:
* 接收来自 MCP 客户端的请求。
* 根据负载均衡策略,从 `AccountManager` 获取一个可用的 API 密钥。
* 将请求路由到选定的 API 密钥,并通过该密钥调用 Exa API。
* 处理 Exa API 的响应,并将其返回给 MCP 客户端。
* 在请求失败时,触发重试机制或错误处理。
* **Account Manager (账号管理器)**:
* 管理所有 Exa API 密钥的生命周期和状态。
* 提供获取下一个可用 API 密钥的接口。
* 根据 `HealthMonitor` 和 `RequestRouter` 的反馈,更新 API 密钥的健康状态、请求指标(请求计数、错误计数、响应时间等)和配额信息。
* 支持动态调整 API 密钥的权重,用于加权负载均衡策略。
* **Health Monitor (健康监控器)**:
* 后台运行,定期对账号池中的每个 API 密钥执行健康检查(例如,发送一个轻量级探测请求到 Exa API)。
* 根据检查结果,更新 `AccountManager` 中对应 API 密钥的状态(如 `active`、`throttled`、`error`、`exhausted`)。
* 处理速率限制错误,设置恢复时间,并在超时后自动将密钥重新激活。
* **Config Service (配置服务)**:
* 负责加载和管理系统的各项配置,包括:
* 多账号配置(API 密钥列表、初始权重、配额限制等)。
* 负载均衡策略配置(轮询、加权轮询、自适应等参数)。
* 健康检查参数(检查间隔、超时时间、恢复阈值)。
* 重试机制参数(最大重试次数、重试延迟)。
* 支持从环境变量、配置文件(如 YAML)中读取配置,并可能支持配置热加载。
### 核心组件的角色和相互关系
* **`AccountManager` (账号管理器)**:
* **角色**:核心数据管理层,负责 API 密钥的增删改查和状态维护。
* **关系**:
* `LoadBalancer` 依赖 `AccountManager` 获取可用密钥。
* `HealthMonitor` 和 `RequestRouter` 会向 `AccountManager` 报告密钥状态和性能指标。
* `ConfigService` 为 `AccountManager` 提供初始配置。
* **`LoadBalancer` (负载均衡器)**:
* **角色**:决策层,根据策略选择最优 API 密钥。
* **关系**:
* 从 `AccountManager` 获取所有健康密钥列表。
* 根据配置的负载均衡策略(如 `ROUND_ROBIN`、`WEIGHTED_ROUND_ROBIN`、`LEAST_CONNECTIONS`、`ADAPTIVE`)选择一个密钥。
* `RequestRouter` 会调用 `LoadBalancer` 进行密钥选择。
* **`HealthMonitor` (健康监控器)**:
* **角色**:诊断层,周期性检查密钥健康状况。
* **关系**:
* 独立运行的后台任务,定期与 Exa API 进行交互以探测密钥健康。
* 将检查结果反馈给 `AccountManager`,更新密钥状态。
* `ConfigService` 提供健康检查的配置参数。
* **`RequestRouter` (请求路由器)**:
* **角色**:执行层,封装请求发送、重试和错误处理逻辑。
* **关系**:
* 接收外部请求,通过 `LoadBalancer` 获取密钥。
* 使用选定的密钥发送请求到 Exa API。
* 实现重试逻辑,在遇到可重试错误时,会再次调用 `LoadBalancer` 获取新的密钥。
* 将请求结果和性能指标反馈给 `AccountManager`。
* **`ConfigService` (配置服务)**:
* **角色**:基础设施层,提供统一的配置管理。
* **关系**:
* 为 `AccountManager`、`LoadBalancer`、`HealthMonitor`、`RequestRouter` 提供各自模块所需的配置参数。
* 支持灵活的配置加载方式(环境变量、文件)。
### 如何利用 `ConfigService` 进行配置管理
`ConfigService` 将成为所有核心组件的配置中心。它将:
1. **加载配置**:在 `exa-mcp-server` 启动时,`ConfigService` 会从预定义的来源(如 `EXA_API_KEYS` 环境变量、`EXA_ACCOUNTS_CONFIG` JSON 环境变量,或 `exa-config.yaml` 配置文件)加载所有与多账号和负载均衡相关的配置。
2. **解析与验证**:对加载的配置进行解析和严格的格式与值校验,确保配置的正确性。例如,检查 API 密钥是否为空,权重是否为有效数字,策略名称是否合法等。
3. **分发配置**:将解析后的配置对象分发给 `AccountManager`、`LoadBalancer`、`HealthMonitor` 和 `RequestRouter` 等组件,供其初始化和运行时使用。
4. **动态更新 (可选)**:未来可以考虑实现配置的热加载机制,允许在不重启服务的情况下,通过 `ConfigService` 动态更新部分配置,例如调整账号权重、切换负载均衡策略等。这将通过监听配置文件变化或提供管理 API 来实现。
通过这种方式,`ConfigService` 确保了整个多账号轮询负载均衡功能的灵活性、可维护性和可扩展性。
## 第三部分:核心实现逻辑
### 1. 轮询算法
`exa-mcp-server` 的多账号轮询负载均衡功能支持多种可配置的负载均衡策略,以适应不同的业务场景和性能需求。核心的负载均衡器 (`LoadBalancer` 模块) 负责根据当前策略从账号池中选择最合适的 API 密钥。
#### 支持的策略:
* **轮询 (Round-Robin)**:按顺序依次选择账号。适用于所有账号性能和配额相似的场景。
* **加权轮询 (Weighted Round-Robin)**:根据预设或动态调整的权重选择账号。权重较高的账号被选中的概率更大。适用于账号性能或配额有差异的场景。
* **最少连接 (Least Connections)**:选择当前正在处理请求数量最少的账号。适用于请求处理时间不均匀的场景,旨在优化并发处理能力。
* **随机 (Random)**:随机选择一个可用账号。简单易实现,但可能无法充分利用资源。
* **自适应 (Adaptive)**:根据账号的实时性能指标(如成功率、响应时间、剩余配额、错误率)动态计算权重,并选择最优账号。这是最智能的策略,能最大化系统整体性能和稳定性。
#### 自适应负载均衡策略实现细节:
自适应策略通过 [`AdaptiveLoadBalancer`](exa-mcp-balance.md:114) 类实现,其核心在于 [`calculateWeight()`](exa-mcp-balance.md:115) 方法,该方法综合考虑以下因素来计算动态权重:
* **成功率**:账号的请求成功率,权重因子 0.3。
* **响应时间**:账号的平均响应时间,响应时间越短,权重越高,权重因子 0.2。
* **剩余配额**:账号的可用配额,剩余配额越多,权重越高,权重因子 0.3。
* **错误惩罚**:账号的错误计数,错误越多,权重越低,权重因子 0.2。
[`selectAccount()`](exa-mcp-balance.md:135) 方法会过滤掉非活跃状态的账号,然后根据计算出的动态权重,采用加权随机的方式选择一个账号。
### 2. 账号管理
`AccountManager` 模块是多账号轮询机制的核心,负责存储、管理和维护所有 Exa API 密钥的状态和性能指标。
#### 账号信息结构:
每个账号都由一个 `Account` 接口定义,包含以下关键属性:
* `id`: 唯一标识符。
* `apiKey`: Exa API 密钥。
* `status`: 账号的当前状态,包括:
* `active`: 活跃可用。
* `throttled`: 被限流,暂时不可用,等待重置。
* `error`: 发生错误,可能暂时或永久禁用。
* `exhausted`: 配额耗尽,等待重置。
* `metrics`: 性能指标集合,包括:
* `requestCount`: 请求总数。
* `errorCount`: 错误总数。
* `successRate`: 成功率。
* `avgResponseTime`: 平均响应时间。
* `lastUsed`: 最后使用时间。
* `quotaRemaining`: 剩余配额。
* `quotaResetTime`: 配额重置时间。
* `weight`: 动态权重,用于加权轮询或自适应负载均衡。
#### `AccountManager` 核心功能:
* `addAccount(apiKey: string)`: 添加新账号到池中。
* `removeAccount(id: string)`: 从池中移除账号。
* `getNextAccount()`: 根据当前负载均衡策略获取下一个可用账号。
* `updateAccountMetrics(id: string, metrics: Partial<AccountMetrics>)`: 更新账号的性能指标。
* `rebalanceWeights()`: 重新计算并调整账号权重(主要用于自适应策略)。
### 3. 状态同步
在多实例部署场景下,确保账号状态(如限流、禁用)在所有服务实例之间保持一致至关重要。虽然 `exa-mcp-balance.md` 文档中没有直接提及具体的分布式状态同步方案,但根据其架构图和常见的分布式系统实践,可以推断以下同步机制:
* **集中式状态存储**:通过引入共享存储(如 Redis)来存储账号的实时状态和指标。当任何一个实例更新账号状态时,都会将更新写入 Redis。
* **发布/订阅机制**:当账号状态发生关键变化(例如被限流、恢复活跃)时,服务实例可以通过 Redis 的 Pub/Sub 机制发布消息。其他订阅了这些消息的实例会接收到通知并更新其本地缓存的账号状态。
* **定期同步**:每个服务实例可以定期从 Redis 拉取最新的账号状态,以确保即使在消息丢失或延迟的情况下,状态也能最终保持一致。
* **分布式锁**:在对账号状态进行写操作时,使用分布式锁(例如 Redlock)来避免并发写入导致的数据不一致问题。
通过上述机制,即使在多个 `exa-mcp-server` 实例并行运行时,所有实例都能共享最新的账号状态,从而实现全局一致的负载均衡和故障切换决策。
### 4. 故障检测与切换
`HealthMonitor` 和 `RequestRouter` 协同工作,实现故障检测和自动切换机制,确保系统的高可用性。
#### `HealthMonitor` 的工作机制:
* **健康检查频率**:`HealthMonitor` 会以 [`checkInterval`](exa-mcp-balance.md:141)(默认为 30 秒)的频率对账号进行健康检查。
* **轻量级 API 调用**:[`performHealthCheck()`](exa-mcp-balance.md:145) 方法会执行轻量级的 Exa API 调用(例如 `pingEndpoint`),以验证账号的可用性。
* **超时机制**:健康检查包含超时机制,如果 API 调用在预设时间内未响应,则认为账号不健康。
* **错误阈值与状态转换**:
* 如果账号连续多次健康检查失败,其状态将从 `active` 转换为 `error` 或 `throttled`。
* 对于限流错误(HTTP 429),[`handleRateLimitError()`](exa-mcp-balance.md:160) 会将账号状态设置为 `throttled`,并根据 `Retry-After` 头设置 [`quotaResetTime`](exa-mcp-balance.md:162)。在重置时间到达后,账号会自动尝试恢复 `active` 状态。
* 当账号连续成功通过健康检查达到 [`recoveryThreshold`](exa-mcp-balance.md:142)(默认为 3 次)时,其状态会从 `error` 恢复为 `active`。
#### `RequestRouter` 的故障切换逻辑:
`RequestRouter` 负责处理实际的请求,并在检测到故障时自动切换到其他可用密钥。
* **重试机制**:[`executeRequest()`](exa-mcp-balance.md:180) 方法会尝试使用 [`accountManager.getNextAccount()`](exa-mcp-balance.md:184) 获取可用账号并执行请求。如果请求失败,它会根据 [`maxRetries`](exa-mcp-balance.md:177)(默认为 3 次)进行重试。
* **错误判断与重试条件**:[`shouldRetry()`](exa-mcp-balance.md:223) 方法定义了哪些错误类型需要重试:
* HTTP 429 (Rate Limited):限流错误,切换账号重试。
* HTTP 503 (Service Unavailable):服务不可用,重试。
* 网络超时:重试。
* 对于 HTTP 401 (Unauthorized) 或 400 (Bad Request) 等客户端错误,不会重试。
* **指数退避**:在重试之间,会采用指数退避策略 (`calculateBackoff`) 增加延迟,避免对故障账号造成更大压力。
* **指标更新**:无论请求成功或失败,`RequestRouter` 都会调用 [`updateMetrics()`](exa-mcp-balance.md:195) 来更新账号的性能指标,这些指标会影响自适应负载均衡和健康检查。
### 5. 推荐的代码实现模式
#### 5.1 `AccountManager` 类结构与核心方法
```typescript
// exa-mcp-server/src/account/AccountManager.ts
/**
* @interface AccountMetrics
* @description 账号性能指标
*/
interface AccountMetrics {
requestCount: number; // 请求总数
errorCount: number; // 错误总数
successRate: number; // 成功率
avgResponseTime: number; // 平均响应时间
lastUsed: Date; // 最后使用时间
quotaRemaining: number; // 剩余配额
quotaResetTime: Date; // 配额重置时间
consecutiveSuccesses: number; // 连续成功次数,用于健康恢复
consecutiveFailures: number; // 连续失败次数,用于故障判断
}
/**
* @interface Account
* @description API 账号信息
*/
interface Account {
id: string;
apiKey: string;
status: 'active' | 'throttled' | 'error' | 'exhausted';
metrics: AccountMetrics;
weight: number; // 动态权重
config: { // 账号的初始配置
quotaLimit?: number;
priority?: 'high' | 'normal' | 'low';
[key: string]: any;
};
}
/**
* @class AccountManager
* @description 管理所有 API 账号的生命周期、状态和指标
*/
class AccountManager {
private accounts: Map<string, Account>;
private loadBalancingStrategy: LoadBalancingStrategy; // 负载均衡策略
private nextRoundRobinIndex: number = 0; // 轮询索引
constructor(strategy: LoadBalancingStrategy = LoadBalancingStrategy.ADAPTIVE) {
this.accounts = new Map();
this.loadBalancingStrategy = strategy;
}
/**
* @method addAccount
* @description 添加新账号到管理器
* @param {string} apiKey - Exa API 密钥
* @param {object} config - 账号配置,可选
*/
addAccount(apiKey: string, config: Partial<Account['config']> = {}): void {
const id = `account-${this.accounts.size + 1}`; // 简单生成 ID
const newAccount: Account = {
id,
apiKey,
status: 'active',
metrics: {
requestCount: 0,
errorCount: 0,
successRate: 1,
avgResponseTime: 0,
lastUsed: new Date(),
quotaRemaining: config.quotaLimit || Infinity,
quotaResetTime: new Date(0),
consecutiveSuccesses: 0,
consecutiveFailures: 0,
},
weight: config.weight || 1, // 初始权重
config: config,
};
this.accounts.set(id, newAccount);
console.log(`Account ${id} added.`);
}
/**
* @method removeAccount
* @description 从管理器中移除账号
* @param {string} id - 账号 ID
*/
removeAccount(id: string): void {
if (this.accounts.delete(id)) {
console.log(`Account ${id} removed.`);
} else {
console.warn(`Account ${id} not found.`);
}
}
/**
* @method getNextAccount
* @description 根据当前负载均衡策略获取下一个可用账号
* @returns {Account | null} 可用账号或 null
*/
getNextAccount(): Account | null {
const activeAccounts = Array.from(this.accounts.values()).filter(
(a) => a.status === 'active' || a.status === 'throttled' // throttled 状态也可被选中进行健康检查或重试
);
if (activeAccounts.length === 0) {
return null;
}
switch (this.loadBalancingStrategy) {
case LoadBalancingStrategy.ROUND_ROBIN:
return this.selectRoundRobin(activeAccounts);
case LoadBalancingStrategy.WEIGHTED_ROUND_ROBIN:
return this.selectWeightedRoundRobin(activeAccounts);
case LoadBalancingStrategy.LEAST_CONNECTIONS:
return this.selectLeastConnections(activeAccounts);
case LoadBalancingStrategy.RANDOM:
return this.selectRandom(activeAccounts);
case LoadBalancingStrategy.ADAPTIVE:
return this.selectAdaptive(activeAccounts);
default:
return this.selectRoundRobin(activeAccounts);
}
}
/**
* @method updateAccountMetrics
* @description 更新账号的性能指标
* @param {string} id - 账号 ID
* @param {Partial<AccountMetrics>} metrics - 部分指标更新
*/
updateAccountMetrics(id: string, metrics: Partial<AccountMetrics>): void {
const account = this.accounts.get(id);
if (account) {
account.metrics = { ...account.metrics, ...metrics };
// 重新计算成功率等
if (account.metrics.requestCount > 0) {
account.metrics.successRate = (account.metrics.requestCount - account.metrics.errorCount) / account.metrics.requestCount;
}
// 如果是自适应策略,可能需要重新计算权重
if (this.loadBalancingStrategy === LoadBalancingStrategy.ADAPTIVE) {
this.rebalanceWeights();
}
}
}
/**
* @method rebalanceWeights
* @description 重新计算并调整账号权重(主要用于自适应策略)
*/
rebalanceWeights(): void {
if (this.loadBalancingStrategy === LoadBalancingStrategy.ADAPTIVE) {
const adaptiveBalancer = new AdaptiveLoadBalancer(); // 假设 AdaptiveLoadBalancer 是一个独立的类
this.accounts.forEach((account) => {
if (account.status === 'active') {
account.weight = adaptiveBalancer['calculateWeight'](account); // 访问私有方法
} else {
account.weight = 0; // 非活跃账号权重为0
}
});
console.log('Account weights rebalanced.');
}
}
/**
* @method updateAccountStatus
* @description 更新账号状态
* @param {string} id - 账号 ID
* @param {'active' | 'throttled' | 'error' | 'exhausted'} status - 新状态
* @param {Date} [resetTime] - 如果是限流或耗尽状态,提供重置时间
*/
updateAccountStatus(id: string, status: Account['status'], resetTime?: Date): void {
const account = this.accounts.get(id);
if (account) {
account.status = status;
if (resetTime) {
account.metrics.quotaResetTime = resetTime;
}
console.log(`Account ${id} status updated to ${status}.`);
}
}
/**
* @method getAccount
* @description 根据 ID 获取账号
* @param {string} id - 账号 ID
* @returns {Account | undefined} 账号对象或 undefined
*/
getAccount(id: string): Account | undefined {
return this.accounts.get(id);
}
/**
* @method getAllAccounts
* @description 获取所有账号
* @returns {Account[]} 所有账号的数组
*/
getAllAccounts(): Account[] {
return Array.from(this.accounts.values());
}
// --- 负载均衡策略的具体实现 ---
private selectRoundRobin(accounts: Account[]): Account | null {
const availableAccounts = accounts.filter(a => a.status === 'active');
if (availableAccounts.length === 0) return null;
this.nextRoundRobinIndex = (this.nextRoundRobinIndex + 1) % availableAccounts.length;
return availableAccounts[this.nextRoundRobinIndex];
}
private selectWeightedRoundRobin(accounts: Account[]): Account | null {
const availableAccounts = accounts.filter(a => a.status === 'active');
if (availableAccounts.length === 0) return null;
const totalWeight = availableAccounts.reduce((sum, a) => sum + a.weight, 0);
let random = Math.random() * totalWeight;
for (const account of availableAccounts) {
if (random < account.weight) {
return account;
}
random -= account.weight;
}
return availableAccounts[Math.floor(Math.random() * availableAccounts.length)]; // Fallback
}
private selectLeastConnections(accounts: Account[]): Account | null {
const availableAccounts = accounts.filter(a => a.status === 'active');
if (availableAccounts.length === 0) return null;
return availableAccounts.reduce((prev, curr) =>
(prev.metrics.requestCount < curr.metrics.requestCount ? prev : curr)
);
}
private selectRandom(accounts: Account[]): Account | null {
const availableAccounts = accounts.filter(a => a.status === 'active');
if (availableAccounts.length === 0) return null;
return availableAccounts[Math.floor(Math.random() * availableAccounts.length)];
}
private selectAdaptive(accounts: Account[]): Account | null {
const adaptiveBalancer = new AdaptiveLoadBalancer();
return adaptiveBalancer.selectAccount(accounts);
}
}
// 负载均衡策略枚举
enum LoadBalancingStrategy {
ROUND_ROBIN = 'round_robin',
WEIGHTED_ROUND_ROBIN = 'weighted',
LEAST_CONNECTIONS = 'least_conn',
RANDOM = 'random',
ADAPTIVE = 'adaptive',
}
// AdaptiveLoadBalancer 伪代码(假设其为独立模块或内部类)
class AdaptiveLoadBalancer {
private calculateWeight(account: Account): number {
// 确保不会出现除以零的情况
const avgResponseTime = account.metrics.avgResponseTime > 0 ? account.metrics.avgResponseTime : 1;
const successRate = account.metrics.successRate;
const quotaRemaining = account.metrics.quotaRemaining;
const errorCount = account.metrics.errorCount;
const factors = {
successRate: successRate * 0.3,
responseTime: (1000 / avgResponseTime) * 0.2, // 响应时间越短,权重越高
quotaAvailable: (quotaRemaining / 1000) * 0.3, // 假设配额以1000为基准
errorPenalty: Math.max(0, 1 - errorCount * 0.1) * 0.2, // 错误越多,权重惩罚越大
};
return Object.values(factors).reduce((sum, val) => sum + val, 0);
}
selectAccount(accounts: Account[]): Account | null {
const weighted = accounts
.filter((a) => a.status === 'active')
.map((a) => ({ account: a, weight: this.calculateWeight(a) }))
.sort((a, b) => b.weight - a.weight); // 降序排列,权重最高的在前
if (weighted.length === 0) return null;
// 实现加权随机选择
const totalWeight = weighted.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * totalWeight;
for (const item of weighted) {
if (random < item.weight) {
return item.account;
}
random -= item.weight;
}
return weighted[0].account; // Fallback to the highest weighted if something goes wrong
}
}
```
#### 5.2 `HealthMonitor` 类结构与核心方法
```typescript
// exa-mcp-server/src/health/HealthMonitor.ts
/**
* @interface HealthStatus
* @description 账号健康状态
*/
interface HealthStatus {
accountId: string;
isHealthy: boolean;
message?: string;
lastCheckTime: Date;
}
/**
* @class HealthMonitor
* @description 负责账号的健康检查和状态管理
*/
class HealthMonitor {
private accountManager: AccountManager;
private checkInterval: number = 30000; // 30秒
private recoveryThreshold: number = 3; // 连续成功次数
private healthCheckTimer: NodeJS.Timeout | null = null;
constructor(accountManager: AccountManager, config?: { checkInterval?: number; recoveryThreshold?: number }) {
this.accountManager = accountManager;
if (config) {
this.checkInterval = config.checkInterval || this.checkInterval;
this.recoveryThreshold = config.recoveryThreshold || this.recoveryThreshold;
}
}
/**
* @method startMonitoring
* @description 启动健康检查定时器
*/
startMonitoring(): void {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
}
this.healthCheckTimer = setInterval(() => this.runHealthChecks(), this.checkInterval);
console.log(`Health monitoring started with interval ${this.checkInterval / 1000}s.`);
}
/**
* @method stopMonitoring
* @description 停止健康检查定时器
*/
stopMonitoring(): void {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
this.healthCheckTimer = null;
}
console.log('Health monitoring stopped.');
}
/**
* @method runHealthChecks
* @description 对所有账号执行健康检查
*/
private async runHealthChecks(): Promise<void> {
const accounts = this.accountManager.getAllAccounts();
for (const account of accounts) {
await this.performHealthCheck(account);
}
}
/**
* @method performHealthCheck
* @description 执行单个账号的健康检查
* @param {Account} account - 待检查的账号
* @returns {Promise<HealthStatus>} 健康状态结果
*/
async performHealthCheck(account: Account): Promise<HealthStatus> {
try {
// 模拟一个轻量级API调用
const response = await this.pingEndpoint(account.apiKey); // 假设 pingEndpoint 返回 { ok: boolean, status: number, retryAfter?: number }
if (response.ok) {
return this.handleHealthy(account);
} else {
return this.handleUnhealthy(account, response);
}
} catch (error) {
return this.handleError(account, error);
}
}
/**
* @method pingEndpoint
* @description 模拟轻量级 API 调用
* @param {string} apiKey - API 密钥
* @returns {Promise<{ ok: boolean, status?: number, retryAfter?: number }>}
*/
private async pingEndpoint(apiKey: string): Promise<{ ok: boolean; status?: number; retryAfter?: number }> {
// 实际实现中,这里会调用 Exa API 的一个轻量级端点,例如获取模型列表或简单的搜索
// 为了示例,这里模拟成功或失败
const isHealthy = Math.random() > 0.1; // 90% 概率健康
if (isHealthy) {
return { ok: true, status: 200 };
} else {
const errorType = Math.random();
if (errorType < 0.3) { // 模拟限流
return { ok: false, status: 429, retryAfter: 60 }; // 60秒后重试
} else if (errorType < 0.6) { // 模拟服务不可用
return { ok: false, status: 503 };
} else { // 模拟其他错误
return { ok: false, status: 400 };
}
}
}
/**
* @method handleHealthy
* @description 处理账号健康的情况
* @param {Account} account - 账号对象
* @returns {HealthStatus}
*/
private handleHealthy(account: Account): HealthStatus {
account.metrics.consecutiveSuccesses++;
account.metrics.consecutiveFailures = 0;
if (account.status !== 'active' && account.metrics.consecutiveSuccesses >= this.recoveryThreshold) {
this.accountManager.updateAccountStatus(account.id, 'active');
}
this.accountManager.updateAccountMetrics(account.id, { lastUsed: new Date() });
return { accountId: account.id, isHealthy: true, lastCheckTime: new Date() };
}
/**
* @method handleUnhealthy
* @description 处理账号不健康的情况(API 返回错误)
* @param {Account} account - 账号对象
* @param {{ ok: boolean, status?: number, retryAfter?: number }} response - API 响应
* @returns {HealthStatus}
*/
private handleUnhealthy(account: Account, response: { ok: boolean; status?: number; retryAfter?: number }): HealthStatus {
account.metrics.consecutiveFailures++;
account.metrics.consecutiveSuccesses = 0;
this.accountManager.updateAccountMetrics(account.id, { errorCount: account.metrics.errorCount + 1 });
if (response.status === 429 && response.retryAfter) {
this.handleRateLimitError(account, response.retryAfter);
return { accountId: account.id, isHealthy: false, message: 'Rate limited', lastCheckTime: new Date() };
} else if (response.status === 401) {
this.accountManager.updateAccountStatus(account.id, 'error'); // 认证失败,直接禁用
return { accountId: account.id, isHealthy: false, message: 'Unauthorized', lastCheckTime: new Date() };
} else {
// 其他错误,如果连续失败达到阈值,则标记为 error
if (account.metrics.consecutiveFailures >= this.recoveryThreshold) {
this.accountManager.updateAccountStatus(account.id, 'error');
}
return { accountId: account.id, isHealthy: false, message: `API Error: ${response.status}`, lastCheckTime: new Date() };
}
}
/**
* @method handleError
* @description 处理账号健康检查过程中发生的异常
* @param {Account} account - 账号对象
* @param {any} error - 错误对象
* @returns {HealthStatus}
*/
private handleError(account: Account, error: any): HealthStatus {
account.metrics.consecutiveFailures++;
account.metrics.consecutiveSuccesses = 0;
this.accountManager.updateAccountMetrics(account.id, { errorCount: account.metrics.errorCount + 1 });
// 如果连续失败达到阈值,则标记为 error
if (account.metrics.consecutiveFailures >= this.recoveryThreshold) {
this.accountManager.updateAccountStatus(account.id, 'error');
}
console.error(`Health check for account ${account.id} failed:`, error.message);
return { accountId: account.id, isHealthy: false, message: `Network/System Error: ${error.message}`, lastCheckTime: new Date() };
}
/**
* @method handleRateLimitError
* @description 处理限流错误,设置账号为 throttled 状态并定时恢复
* @param {Account} account - 账号对象
* @param {number} retryAfter - 重试间隔(秒)
*/
private handleRateLimitError(account: Account, retryAfter: number): void {
const resetTime = new Date(Date.now() + retryAfter * 1000);
this.accountManager.updateAccountStatus(account.id, 'throttled', resetTime);
// 设置定时器,到期后尝试恢复账号状态
setTimeout(() => {
const currentAccount = this.accountManager.getAccount(account.id);
if (currentAccount && currentAccount.status === 'throttled') {
// 尝试恢复为 active,但仍需等待下次健康检查确认
this.accountManager.updateAccountStatus(currentAccount.id, 'active'); // 使用 currentAccount.id
console.log(`Account ${currentAccount.id} attempted to recover from throttled state.`);
}
}, retryAfter * 1000);
}
}
```
#### 5.3 `RequestRouter` 类结构与核心方法
```typescript
// exa-mcp-server/src/router/RequestRouter.ts
/**
* @interface SearchRequest
* @description 搜索请求参数
*/
interface SearchRequest {
query: string;
// ... 其他 Exa API 搜索参数
options?: any;
}
/**
* @interface SearchResponse
* @description 搜索响应结果
*/
interface SearchResponse {
results: any[];
metadata: {
accountId: string; // 记录使用了哪个账号
responseTime: number;
// ... 其他响应元数据
};
}
/**
* @interface RequestMetrics
* @description 请求结果指标
*/
interface RequestMetrics {
success: boolean;
responseTime?: number;
error?: any;
}
/**
* @class RequestRouter
* @description 负责路由请求到合适的 API 账号,并处理重试和故障切换
*/
class RequestRouter {
private accountManager: AccountManager;
private maxRetries: number = 3; // 最大重试次数
private initialRetryDelay: number = 1000; // 初始重试延迟(毫秒)
constructor(accountManager: AccountManager, config?: { maxRetries?: number; initialRetryDelay?: number }) {
this.accountManager = accountManager;
if (config) {
this.maxRetries = config.maxRetries || this.maxRetries;
this.initialRetryDelay = config.initialRetryDelay || this.initialRetryDelay;
}
}
/**
* @method executeRequest
* @description 执行带有负载均衡和重试逻辑的 Exa API 请求
* @param {SearchRequest} request - 搜索请求
* @returns {Promise<SearchResponse>} 搜索响应
*/
async executeRequest(request: SearchRequest): Promise<SearchResponse> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
const account = this.accountManager.getNextAccount(); // 获取下一个可用账号
if (!account) {
lastError = new Error('No available API accounts for request.');
console.error(lastError.message);
break; // 没有可用账号,直接退出重试循环
}
try {
console.log(`Attempting request with account ${account.id} (Attempt ${attempt + 1}/${this.maxRetries})`);
const startTime = Date.now();
// 实际调用 Exa API 的逻辑
const response = await this.sendRequestToExa(account, request); // 假设 sendRequestToExa 是一个私有方法
const responseTime = Date.now() - startTime;
// 更新成功指标
this.updateMetrics(account, { success: true, responseTime });
console.log(`Request successful with account ${account.id}. Response time: ${responseTime}ms.`);
return {
results: response.data, // 假设响应数据在 .data 字段
metadata: {
accountId: account.id,
responseTime: responseTime,
},
};
} catch (error: any) {
lastError = error;
console.warn(`Request failed with account ${account.id}: ${error.message}.`);
// 更新失败指标
this.updateMetrics(account, { success: false, error });
// 判断是否需要重试
if (this.shouldRetry(error)) {
console.log(`Retrying request due to recoverable error...`);
await this.delay(this.calculateBackoff(attempt));
// 如果是限流错误,通知 HealthMonitor 更新账号状态
if (error.status === 429 && error.retryAfter) {
const resetTime = new Date(Date.now() + error.retryAfter * 1000);
this.accountManager.updateAccountStatus(account.id, 'throttled', resetTime);
}
continue; // 继续下一次重试
} else {
console.error(`Request failed with non-retryable error: ${error.message}.`);
break; // 不可重试错误,退出重试循环
}
}
}
// 所有重试都失败了
throw lastError || new Error('Failed to execute request after multiple retries.');
}
/**
* @method sendRequestToExa
* @description 模拟实际调用 Exa API
* @param {Account} account - 使用的账号
* @param {SearchRequest} request - 请求参数
* @returns {Promise<any>} Exa API 响应
*/
private async sendRequestToExa(account: Account, request: SearchRequest): Promise<any> {
// 实际这里会使用 account.apiKey 调用 Exa API
// 例如:
// const exaClient = new ExaClient(account.apiKey);
// return exaClient.search(request.query, request.options);
// 模拟 Exa API 响应
const delay = Math.random() * 500 + 100; // 100ms - 600ms 延迟
await new Promise(resolve => setTimeout(resolve, delay));
const success = Math.random() > 0.05; // 95% 成功率
if (success) {
return { data: [{ title: `Result for ${request.query} from ${account.id}` }] };
} else {
const errorType = Math.random();
if (errorType < 0.3) { // 模拟限流
const err: any = new Error('Rate Limit Exceeded');
err.status = 429;
err.retryAfter = 30; // 30秒后重试
throw err;
} else if (errorType < 0.6) { // 模拟服务不可用
const err: any = new Error('Service Unavailable');
err.status = 503;
throw err;
} else if (errorType < 0.8) { // 模拟认证失败
const err: any = new Error('Unauthorized');
err.status = 401;
throw err;
} else { // 模拟其他客户端错误
const err: any = new Error('Bad Request');
err.status = 400;
throw err;
}
}
}
/**
* @method updateMetrics
* @description 更新账号的请求指标
* @param {Account} account - 账号对象
* @param {RequestMetrics} metrics - 请求结果指标
*/
private updateMetrics(account: Account, metrics: RequestMetrics): void {
const updatedMetrics: Partial<AccountMetrics> = {
requestCount: account.metrics.requestCount + 1,
lastUsed: new Date(),
};
if (metrics.success) {
updatedMetrics.avgResponseTime = (account.metrics.avgResponseTime * account.metrics.requestCount + metrics.responseTime) / updatedMetrics.requestCount;
updatedMetrics.consecutiveSuccesses = account.metrics.consecutiveSuccesses + 1;
updatedMetrics.consecutiveFailures = 0;
} else {
updatedMetrics.errorCount = account.metrics.errorCount + 1;
updatedMetrics.consecutiveFailures = account.metrics.consecutiveFailures + 1;
updatedMetrics.consecutiveSuccesses = 0;
}
this.accountManager.updateAccountMetrics(account.id, updatedMetrics);
}
/**
* @method shouldRetry
* @description 判断是否需要重试请求
* @param {any} error - 错误对象
* @returns {boolean} 是否重试
*/
private shouldRetry(error: any): boolean {
// 429: Rate Limited - 切换账号重试
// 503: Service Unavailable - 重试
// 网络超时 (例如 error.code === 'ECONNREFUSED' 或 error.timeout) - 重试
// 401: Unauthorized - 不重试 (账号配置问题)
// 400: Bad Request - 不重试 (请求参数问题)
const status = error.status || error.response?.status; // 兼容不同错误对象的 status 属性
return status === 429 || status === 503 || error.code === 'ECONNREFUSED' || error.timeout;
}
/**
* @method calculateBackoff
* @description 计算指数退避延迟
* @param {number} attempt - 当前重试次数
* @returns {number} 延迟时间(毫秒)
*/
private calculateBackoff(attempt: number): number {
return this.initialRetryDelay * Math.pow(2, attempt);
}
/**
* @method delay
* @description 延迟指定时间
* @param {number} ms - 延迟毫秒数
* @returns {Promise<void>}
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
```
### 6. `LoadBalancer` 模块结构
`LoadBalancer` 模块本身可以是一个接口或抽象类,定义了负载均衡器的通用行为,不同的策略(如 `AdaptiveLoadBalancer`)作为其具体实现。在 `AccountManager` 中,`loadBalancingStrategy` 字段决定了具体使用哪个负载均衡器实例。
```typescript
// exa-mcp-server/src/loadbalancer/LoadBalancer.ts
/**
* @interface ILoadBalancer
* @description 负载均衡器接口
*/
interface ILoadBalancer {
selectAccount(accounts: Account[]): Account | null;
}
/**
* @class RoundRobinLoadBalancer
* @implements {ILoadBalancer}
* @description 轮询负载均衡器实现
*/
class RoundRobinLoadBalancer implements ILoadBalancer {
private currentIndex: number = 0;
selectAccount(accounts: Account[]): Account | null {
const activeAccounts = accounts.filter(a => a.status === 'active');
if (activeAccounts.length === 0) return null;
const selectedAccount = activeAccounts[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % activeAccounts.length;
return selectedAccount;
}
}
// ... 其他负载均衡器实现 (WeightedRoundRobinLoadBalancer, LeastConnectionsLoadBalancer, RandomLoadBalancer)
// AdaptiveLoadBalancer 的实现已在 AccountManager 伪代码中提供。
/**
* @class LoadBalancerFactory
* @description 负载均衡器工厂,根据策略创建实例
*/
class LoadBalancerFactory {
static create(strategy: LoadBalancingStrategy): ILoadBalancer {
switch (strategy) {
case LoadBalancingStrategy.ROUND_ROBIN:
return new RoundRobinLoadBalancer();
// ... 其他策略的创建
case LoadBalancingStrategy.ADAPTIVE:
// AdaptiveLoadBalancer 依赖于 AccountManager 中的权重计算,
// 实际使用时可能需要 AccountManager 实例或其接口。
// 暂时返回一个简化的实例,实际会更复杂。
return new AdaptiveLoadBalancer();
default:
return new RoundRobinLoadBalancer();
}
}
}
```
## 第四部分:API接口定义与使用
`exa-mcp-balance` 模块将主要通过内部接口与 `exa-mcp-server` 的其他模块进行交互。这些接口封装了账号选择、请求路由和结果处理的复杂逻辑。
### 1. 主要内部 API 接口
#### 1.1 `RequestRouter.executeRequest`
* **功能描述**:这是 `exa-mcp-balance` 模块对外暴露的核心接口,用于执行对 Exa API 的请求。它负责账号选择、负载均衡、请求发送、错误处理、重试以及故障切换等端到端逻辑。
* **参数**:
* `request: SearchRequest` (必需): 包含搜索查询 (`query`) 和其他 Exa API 请求选项 (`options`) 的对象。
* **返回值**:
* `Promise<SearchResponse>`: 异步返回搜索结果 (`results`) 和请求元数据 (`metadata`),其中 `metadata` 包含实际使用的 `accountId` 和 `responseTime`。
* **错误码**:
* `Error`: 如果所有重试都失败或遇到不可恢复的错误,将抛出 `Error` 异常,包含详细的错误信息。
#### 1.2 `AccountManager.updateAccountMetrics`
* **功能描述**:用于更新指定账号的性能指标,例如请求成功/失败、响应时间等。`RequestRouter` 在每次请求完成后会调用此接口。
* **参数**:
* `id: string` (必需): 账号的唯一标识符。
* `metrics: Partial<AccountMetrics>` (必需): 需要更新的部分性能指标对象。
* **返回值**:
* `void`
#### 1.3 `AccountManager.updateAccountStatus`
* **功能描述**:用于更新指定账号的健康状态。`HealthMonitor` 和 `RequestRouter` 在检测到账号状态变化(如限流、错误、恢复)时会调用此接口。
* **参数**:
* `id: string` (必需): 账号的唯一标识符。
* `status: 'active' | 'throttled' | 'error' | 'exhausted'` (必需): 账号的新状态。
* `resetTime?: Date` (可选): 如果是 `throttled` 或 `exhausted` 状态,表示配额重置时间。
* **返回值**:
* `void`
### 2. 示例代码:其他模块如何调用
假设 `exa-mcp-server` 的主服务模块 (`src/index.ts` 或 `src/service/ExaService.ts`) 需要调用 Exa API 进行搜索。它将通过 `RequestRouter` 实例来完成。
```typescript
// exa-mcp-server/src/service/ExaService.ts
import { AccountManager, LoadBalancingStrategy } from './account/AccountManager'; // 假设路径
import { HealthMonitor } from './health/HealthMonitor'; // 假设路径
import { RequestRouter } from './router/RequestRouter'; // 假设路径
import { ConfigService } from './config/ConfigService'; // 假设路径
class ExaService {
private requestRouter: RequestRouter;
private accountManager: AccountManager;
private healthMonitor: HealthMonitor;
private configService: ConfigService;
constructor() {
this.configService = new ConfigService(); // 初始化配置服务
const accountsConfig = this.configService.getAccountsConfig(); // 获取账号配置
// 初始化 AccountManager
this.accountManager = new AccountManager(accountsConfig.strategy || LoadBalancingStrategy.ADAPTIVE);
accountsConfig.accounts.forEach(acc => {
this.accountManager.addAccount(acc.apiKey, acc);
});
// 初始化 HealthMonitor
this.healthMonitor = new HealthMonitor(this.accountManager, this.configService.getHealthCheckConfig());
this.healthMonitor.startMonitoring(); // 启动健康检查
// 初始化 RequestRouter
this.requestRouter = new RequestRouter(this.accountManager, this.configService.getRequestRouterConfig());
}
/**
* @method search
* @description 执行 Exa API 搜索请求
* @param {string} query - 搜索查询字符串
* @param {any} options - 其他 Exa API 搜索选项
* @returns {Promise<any[]>} 搜索结果列表
*/
async search(query: string, options?: any): Promise<any[]> {
try {
const response = await this.requestRouter.executeRequest({ query, options });
console.log(`Search completed using account: ${response.metadata.accountId}`);
return response.results;
} catch (error) {
console.error('Failed to perform search after multiple retries:', error.message);
throw error;
}
}
// ... 其他 Exa API 相关方法,例如 findSimilar, getContents 等
}
export const exaService = new ExaService();
```
通过这种封装,`exa-mcp-server` 的其他业务逻辑模块无需关心底层多账号管理、负载均衡和故障切换的复杂性,只需调用 `ExaService.search()` 方法即可,大大简化了开发和维护。
## 第五部分:配置与部署
本节详细介绍了如何配置和部署 `exa-mcp-server` 以启用多账号轮询负载均衡功能。
### 1. 配置项说明
多账号轮询负载均衡功能的核心在于灵活的配置管理。`exa-mcp-server` 支持多种配置方式,以适应不同的部署环境和需求。
#### 1.1 API 密钥配置
API 密钥是访问 Exa API 的凭证,支持以下三种配置方式:
* **环境变量 `EXA_API_KEYS` (逗号分隔)**:
这是最简单的配置方式,适用于快速部署或少量账号场景。
```bash
EXA_API_KEYS=key1,key2,key3
```
服务器将自动为每个密钥创建一个默认账号。
* **JSON 环境变量 `EXA_ACCOUNTS_CONFIG`**:
提供更详细的账号配置,包括 ID、权重等,适用于需要精细控制账号行为的场景。
```bash
EXA_ACCOUNTS_CONFIG='{
"accounts": [
{"id": "primary", "apiKey": "key1", "weight": 2},
{"id": "secondary", "apiKey": "key2", "weight": 1},
{"id": "backup", "apiKey": "key3", "weight": 0.5}
],
"strategy": "adaptive",
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000
}
}'
```
此配置允许定义每个账号的唯一 ID、API 密钥和初始权重,并可以指定负载均衡策略和健康检查参数。
* **YAML 配置文件 (例如 `exa-config.yaml`)**:
最灵活的配置方式,适用于复杂的多团队、多项目或动态配置场景。配置文件可以包含账号的配额限制、优先级等高级设置,并支持环境变量引用。
```yaml
# exa-config.yaml
accounts:
- id: team-a
apiKey: ${TEAM_A_API_KEY}
quotaLimit: 1000
priority: high
- id: team-b
apiKey: ${TEAM_B_API_KEY}
quotaLimit: 500
priority: normal
- id: shared-pool
apiKey: ${SHARED_API_KEY}
quotaLimit: 2000
priority: low
loadBalancing:
strategy: adaptive
weights:
successRate: 0.3
responseTime: 0.2
quotaAvailable: 0.3
errorRate: 0.2
monitoring:
healthCheck:
enabled: true
interval: 30s
timeout: 5s
retryThreshold: 3
metrics:
collectInterval: 10s
aggregationWindow: 5m
rateLimiting:
perAccount:
requests: 100
window: 60s
global:
requests: 500
window: 60s
```
在启动服务器时,需要指定加载此配置文件。
#### 1.2 负载均衡策略选择 (`LOAD_BALANCING_STRATEGY`)
通过配置 `strategy` 参数,可以选择不同的负载均衡策略:
* **`round_robin` (轮询)**:按顺序依次选择账号。
* **`weighted` (加权轮询)**:根据账号配置的权重进行轮询,权重高的账号被选中的概率更大。
* **`least_conn` (最少连接)**:选择当前连接数最少的账号。
* **`random` (随机)**:随机选择一个可用账号。
* **`adaptive` (自适应)**:根据账号的实时性能指标(成功率、响应时间、配额剩余、错误率)动态调整权重,选择最优账号。
#### 1.3 健康检查参数
健康检查确保只有健康的账号参与负载均衡。以下是主要配置项:
* **`HEALTH_CHECK_INTERVAL` (或 `monitoring.healthCheck.interval`)**:
健康检查的频率,单位为毫秒(或秒,取决于配置文件格式)。例如:`30000` 毫秒或 `30s`。
* **`RECOVERY_THRESHOLD` (或 `monitoring.healthCheck.retryThreshold`)**:
一个账号从不健康状态恢复到健康状态所需的连续成功检查次数。例如:`3`。
#### 1.4 重试机制参数
当请求失败时,重试机制可以提高系统的容错能力。
* **`MAX_RETRIES` (或 `requestRouter.maxRetries`)**:
单个请求的最大重试次数。例如:`3`。
* **`INITIAL_RETRY_DELAY` (或 `requestRouter.retryDelay`)**:
首次重试的延迟时间,单位为毫秒。重试延迟通常会采用指数退避策略。例如:`1000` 毫秒。
#### 1.5 账号权重、配额限制等
在 JSON 环境变量或 YAML 配置文件中,可以为每个账号配置更详细的参数:
* **`weight`**: 账号的初始权重,用于加权轮询和自适应策略。
* **`quotaLimit`**: 账号的配额限制,例如每分钟请求数。
* **`priority`**: 账号的优先级(例如 `high`, `normal`, `low`),可以在自定义负载均衡策略中使用。
### 2. 部署步骤
以下是在不同环境下部署 `exa-mcp-server` 并启用多账号轮询功能的通用步骤:
#### 2.1 环境要求与依赖
* **Node.js 版本**: 建议使用 LTS 版本(例如 Node.js 18.x 或更高版本)。
* **项目依赖**:
* `npm` (Node.js 包管理器)
* 项目所需的 `npm` 包 (通过 `npm install` 安装)
#### 2.2 Docker 部署
1. **构建 Docker 镜像**:
在项目根目录创建 `Dockerfile` (如果尚未创建),并构建镜像:
```bash
docker build -t exa-mcp-server:multi-account .
```
`Dockerfile` 示例(参考 `exa-mcp-balance.md` 中的 `docker-compose.yml`):
```dockerfile
# 示例 Dockerfile 内容
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"] # 或者根据实际启动命令调整
```
2. **配置环境变量**:
根据需要,在 `docker run` 命令中或 `docker-compose.yml` 文件中设置 `EXA_API_KEYS` 或 `EXA_ACCOUNTS_CONFIG` 环境变量。
**使用 `docker run`**:
```bash
docker run -d \
-p 3000:3000 \
-e EXA_API_KEYS="key1,key2,key3" \
exa-mcp-server:multi-account
```
**使用 `docker-compose.yml`**:
```yaml
# docker-compose.yml
version: '3.8'
services:
exa-mcp-server:
image: exa-mcp-server:multi-account
environment:
- EXA_ACCOUNTS_CONFIG=${EXA_ACCOUNTS_CONFIG} # 从宿主机环境变量加载
- REDIS_URL=redis://cache:6379
- MONITORING_ENABLED=true
ports:
- "3000:3000"
depends_on:
- cache # 如果使用缓存,依赖 Redis 服务
- monitoring # 如果使用 Prometheus 监控,依赖监控服务
cache:
image: redis:alpine
volumes:
- cache-data:/data
monitoring:
image: prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
volumes:
cache-data:
```
然后运行 `docker-compose up -d`。
#### 2.3 Kubernetes 部署
1. **创建 Docker 镜像**:同 Docker 部署步骤 1。
2. **创建 Secret 存储 API 密钥**:
为了安全起见,API 密钥应存储在 Kubernetes Secret 中。
```bash
kubectl create secret generic exa-api-keys --from-literal=EXA_API_KEY_1=key1 --from-literal=EXA_API_KEY_2=key2
# 或者存储完整的 JSON 配置
kubectl create secret generic exa-accounts-config --from-literal=EXA_ACCOUNTS_CONFIG='{"accounts": [...]}'
```
3. **创建 Deployment 和 Service**:
编写 Kubernetes Deployment 和 Service YAML 文件,将 Secret 挂载为环境变量。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: exa-mcp-server
spec:
replicas: 3
selector:
matchLabels:
app: exa-mcp-server
template:
metadata:
labels:
app: exa-mcp-server
spec:
containers:
- name: exa-mcp-server
image: exa-mcp-server:multi-account
ports:
- containerPort: 3000
env:
- name: EXA_API_KEYS # 或 EXA_ACCOUNTS_CONFIG
valueFrom:
secretKeyRef:
name: exa-api-keys
key: EXA_API_KEY_1,EXA_API_KEY_2 # 根据实际情况调整
# ... 其他配置,如健康检查探针,资源限制等
---
apiVersion: v1
kind: Service
metadata:
name: exa-mcp-server-service
spec:
selector:
app: exa-mcp-server
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer # 或 ClusterIP
```
应用配置:`kubectl apply -f your-deployment.yaml`。
#### 2.4 裸机部署
1. **安装 Node.js 和 npm**:
确保服务器上安装了正确版本的 Node.js 和 npm。
2. **克隆代码并安装依赖**:
```bash
git clone <your-repo-url>
cd exa-mcp-server
npm install
```
3. **配置环境变量**:
在启动脚本中或通过 `export` 命令设置 `EXA_API_KEYS` 或 `EXA_ACCOUNTS_CONFIG`。
```bash
export EXA_API_KEYS="key1,key2,key3"
npm start # 或者您的启动命令
```
4. **使用进程管理器**:
为了确保服务稳定运行,建议使用 `pm2`、`systemd` 或 `supervisor` 等进程管理器。
**使用 PM2 示例**:
```bash
npm install -g pm2
pm2 start src/index.ts --name exa-mcp-server --interpreter ts-node # 如果直接运行 TypeScript 文件
# 或者
pm2 start dist/index.js --name exa-mcp-server # 如果是编译后的 JavaScript 文件
```
## 第六部分:测试与验证
本节提供了 `exa-mcp-server` 多账号轮询负载均衡功能的测试策略和指导,以确保其正确性、稳定性和性能。
### 1. 单元测试
单元测试聚焦于独立组件的功能正确性,确保每个模块都能按照预期工作。
* **`AccountManager`**:
* **测试目的**: 验证账号的添加、移除、选择逻辑,以及账号状态和指标的更新。
* **测试案例**:
* 验证 `addAccount` 和 `removeAccount` 方法是否正确管理账号池。
* 在不同负载均衡策略下 (`round_robin`, `weighted`, ``random`) 验证 `getNextAccount` 是否返回正确的账号。
* 测试 `updateAccountMetrics` 是否能正确更新账号的请求计数、错误率、响应时间等指标。
* 验证 `rebalanceWeights` 在自适应策略下是否根据指标重新计算权重。
* 模拟账号故障或限流后,验证 `getNextAccount` 是否能跳过不可用账号。
* **示例代码 (使用 `Mocha`/`Jest`)**:
```typescript
describe('AccountManager', () => {
it('should select accounts using round-robin', () => {
const manager = new AccountManager({ strategy: 'round_robin' })
manager.addAccount('key1')
manager.addAccount('key2')
expect(manager.getNextAccount().id).toBe('account-1')
expect(manager.getNextAccount().id).toBe('account-2')
expect(manager.getNextAccount().id).toBe('account-1')
})
it('should handle account failures gracefully', () => {
const manager = new AccountManager()
manager.addAccount('key1')
manager.markAccountFailed('account-1')
expect(manager.getNextAccount()).toBeNull() // 假设没有其他账号可用
})
})
```
* **`LoadBalancer`**:
* **测试目的**: 验证各种负载均衡策略的实现逻辑。
* **测试案例**:
* 为 `round_robin` 策略,验证账号选择的循环顺序。
* 为 `weighted` 策略,验证账号选择是否遵循预设权重比例。
* 为 `adaptive` 策略,模拟不同的账号指标(成功率、响应时间、配额),验证其是否能选择“最优”账号。
* 测试当所有账号都不可用时,负载均衡器是否能正确处理(例如抛出错误或返回 null)。
* **`HealthMonitor`**:
* **测试目的**: 验证健康检查的执行、状态更新和恢复逻辑。
* **测试案例**:
* 模拟 API 调用成功和失败,验证账号状态 (`status`) 是否正确更新为 `active` 或 `error`。
* 测试限流响应 (HTTP 429) 后,账号是否被标记为 `throttled` 并设置正确的 `quotaResetTime`。
* 验证在达到 `recoveryThreshold` 后,不健康账号是否能自动恢复为 `active`。
* 测试网络超时等异常情况的处理。
* **`RequestRouter`**:
* **测试目的**: 验证请求的路由、重试和错误处理逻辑。
* **测试案例**:
* 模拟 Exa API 成功响应,验证请求是否正确执行并更新账号指标。
* 模拟可重试错误(例如 HTTP 429, 503, 网络超时),验证请求是否按预期重试,并切换到其他账号。
* 模拟不可重试错误(例如 HTTP 400, 401),验证请求是否立即失败且不重试。
* 测试当所有重试尝试都失败后,是否抛出最终错误。
* 验证重试延迟(指数退避)是否按预期工作。
### 2. 集成测试
集成测试验证不同组件协同工作的能力,确保整个多账号轮询链路的端到端功能正确。
* **测试目的**: 验证请求的正确路由、故障切换、限流处理、配额管理和端到端请求响应流程。
* **测试案例**:
* **基本路由**: 发送多个请求,验证请求是否根据配置的负载均衡策略在不同账号间正确分发。
* **故障切换**: 模拟一个或多个账号故障(例如,API 密钥失效、服务不可用),验证系统是否能自动切换到其他健康账号并继续处理请求。
* **限流处理**: 模拟一个账号达到 Exa API 的速率限制 (HTTP 429),验证系统是否能将后续请求路由到其他账号,并在限流期结束后自动恢复该账号。
* **配额管理**: 如果配置了账号配额,验证当一个账号配额耗尽时,请求是否能路由到其他有配额的账号。
* **错误处理**: 模拟各种 API 错误,验证系统是否能正确捕获、记录错误,并根据错误类型决定重试或失败。
* **并发请求**: 发送大量并发请求,验证系统在高负载下是否能稳定运行,并正确分发请求。
* **示例代码 (使用 `supertest` 或自定义 HTTP 客户端)**:
```typescript
describe('Multi-Account Integration', () => {
it('should failover when primary account rate limited', async () => {
const server = new ExaMCPServer({
accounts: ['primary', 'backup'] // 假设这些账号已配置
})
// 模拟主账号限流的逻辑 (例如,通过 mock 掉 Exa API 客户端的 sendRequest 方法)
// mockRateLimit('primary'); // 这是一个示意函数
const result = await server.search('test query')
expect(result).toBeDefined()
// expect(result.usedAccount).toBe('backup') // 验证请求是否切换到备份账号
})
it('should distribute requests across multiple accounts', async () => {
const server = new ExaMCPServer({
accounts: ['account1', 'account2', 'account3'],
strategy: 'round_robin'
})
const usedAccounts = []
for (let i = 0; i < 6; i++) {
const result = await server.search('query ' + i)
// usedAccounts.push(result.usedAccount); // 记录使用的账号
}
// expect(usedAccounts).toEqual(['account1', 'account2', 'account3', 'account1', 'account2', 'account3']);
})
})
```
### 3. 性能测试
性能测试旨在评估系统在不同负载下的响应能力、吞吐量和稳定性。
* **测试目的**: 验证系统的吞吐量、响应时间、资源利用率是否达到预期目标,并识别性能瓶颈。
* **测试工具**: `JMeter`, `Locust`, `k6`, `Artillery` 等。
* **测试场景**:
* **并发请求测试**: 模拟大量用户同时发送请求,逐步增加并发数,观察系统的 QPS (每秒查询数)、平均响应时间、错误率和资源(CPU、内存、网络)利用率。
* **负载测试**: 在预期峰值负载下长时间运行系统,检查其稳定性、是否存在内存泄漏或性能随时间下降的问题。
* **压力测试**: 持续增加负载直至系统崩溃或性能显著下降,以确定系统的最大容量和瓶颈。
* **关注指标**:
* **吞吐量 (Throughput)**: QPS (Queries Per Second)。
* **响应时间 (Latency)**: 平均响应时间、90% / 95% / 99% 分位响应时间。
* **错误率 (Error Rate)**: 请求失败的百分比。
* **资源利用率**: CPU 使用率、内存使用率、网络 I/O。
* **指导**:
1. **确定基准**: 在单账号模式下进行性能测试,记录各项指标作为基准。
2. **多账号对比**: 在启用多账号轮询功能后,重复相同的测试场景,与基准进行对比,验证性能提升。
3. **逐步加压**: 逐步增加并发用户数或 QPS,观察系统行为和性能曲线。
4. **识别瓶颈**: 利用监控工具(如 Prometheus, Grafana)识别 CPU、内存、网络或 Exa API 本身可能存在的瓶颈。
5. **优化与再测试**: 根据测试结果进行系统优化(例如调整配置、优化代码、增加账号),然后进行再测试。
### 4. 回归测试
回归测试旨在确保在引入新功能或修改现有功能后,系统原有的功能没有被破坏。
* **测试目的**: 确保多账号轮询负载均衡功能的引入或修改不会对 `exa-mcp-server` 的现有功能(如基本的搜索、相似页面查找等)产生负面影响。
* **测试范围**:
* **核心功能**: 验证所有已有的 API 接口 (搜索、查找相似页面等) 仍能正常工作。
* **兼容性**: 如果存在向后兼容层,验证旧客户端或旧配置是否仍能正常与新版本交互。
* **错误场景**: 验证各种已知的错误处理逻辑 (例如无效 API 密钥、网络问题) 在新版本中是否依然有效。
* **指导**:
1. **自动化测试套件**: 维护一套全面的自动化单元测试和集成测试,每次代码提交后自动运行。
2. **关键业务流程**: 识别并测试最关键的业务流程,确保它们在新版本中依然稳定。
3. **性能回归**: 关注性能指标,确保新功能不会导致性能显著下降。
4. **灰度发布**: 结合部署策略中的灰度发布 (Phase 4),逐步将新版本推向生产环境,并在每个阶段进行严格的监控和验证。
## 第七部分:扩展性与维护
为了确保 `exa-mcp-server` 多账号轮询负载均衡功能能够适应不断变化的需求并保持长期稳定运行,以下是关于扩展性与维护的详细考量:
### 未来的扩展点
1. **增加账号容量与动态管理**:
* **热插拔账号**:支持在不重启服务的情况下,动态添加、删除或修改 Exa API 账号配置。这可以通过一个管理接口(例如,一个内部 API 端点或命令行工具)来实现,管理员可以实时调整账号池。
* **账号分组与优先级**:引入账号分组机制,允许将账号按业务线、优先级或地域进行划分。例如,高优先级业务可以使用专属账号池,或在所有账号都可用时,优先使用高优先级账号。
* **自动发现与注册**:对于大规模部署,可以探索与配置中心(如 Consul, Etcd, Nacos)集成,实现账号的自动发现与注册,降低手动配置的复杂性。
2. **引入新的负载均衡策略**:
* **基于成本的策略**:如果 Exa API 引入了不同账号级别的定价模型,可以开发基于成本的负载均衡策略,优先使用成本较低的账号,同时满足性能要求。
* **基于地理位置的策略**:对于跨地域部署,可以根据用户请求的来源,智能选择地理位置更近、延迟更低的账号或代理节点。
* **机器学习驱动的策略**:结合历史数据(请求模式、账号性能、错误率等),利用机器学习模型预测最佳账号选择,实现更智能、更高效的流量调度。
3. **支持多类型 API**:
* **抽象化 API 接口**:将 Exa API 的特定调用逻辑进行抽象,定义通用的 `LLMProvider` 接口,以便轻松集成其他 AI 服务 API(例如,OpenAI, Gemini, Claude, Baidu Wenxin Yiyan 等)。
* **多服务网关**:构建一个统一的 API 网关,根据请求的类型或路由规则,将其分发到不同的后端 API 代理服务(例如,`exa-mcp-server`、`gemini-mcp-server`)。
* **插件化扩展**:设计插件化的架构,允许开发者通过编写插件的方式,快速集成新的 API 提供商,而无需修改核心代码。
### 性能优化策略
1. **缓存机制**:
* **请求结果缓存**:对于重复的或高频的搜索请求,缓存 Exa API 的响应结果。可以使用 Redis 或 Memcached 作为分布式缓存,并设置合理的 TTL (Time-To-Live)。
* **元数据缓存**:缓存账号的静态信息(如配额限制、优先级)和动态信息(如当前可用配额、上次使用时间),减少对底层存储的频繁访问。
* **局部缓存**:在 `AccountManager` 内部维护一个短期的内存缓存,用于存储最近使用的账号状态,进一步减少并发访问的开销。
2. **异步请求处理与连接池优化**:
* **异步非阻塞 I/O**:充分利用 Node.js 的事件循环机制,确保所有 API 请求和网络通信都采用异步非阻塞方式,避免阻塞主线程。
* **HTTP 连接池**:合理配置 HTTP 客户端库(如 `axios` 或 `node-fetch`)的连接池,复用与 Exa API 之间的 TCP 连接,减少连接建立和关闭的开销,提高请求吞吐量。
* **并发限制**:通过 `Semaphore` 或 `p-limit` 等工具,严格控制对 Exa API 的并发请求数量,防止单个账号或系统过载。
3. **批量请求与响应聚合**:
* **请求合并**:对于短时间内发送的相似请求,可以尝试将其合并为一个批量请求(如果 Exa API 支持),减少 API 调用次数。
* **响应聚合**:如果需要同时从多个账号获取数据并进行聚合,可以采用并行请求和异步等待 (`Promise.all`) 的方式,提高整体响应速度。
### 日常维护的注意事项
1. **日志分析与故障排查**:
* **结构化日志**:确保日志输出为 JSON 等结构化格式,便于使用 ELK Stack (Elasticsearch, Logstash, Kibana) 或 Grafana Loki 等工具进行日志收集、存储、查询和分析。
* **关键信息记录**:日志应包含请求 ID、使用的 API Key ID、代理 IP、请求耗时、HTTP 状态码、错误类型和详细信息等,以便快速定位问题。
* **异常告警**:配置日志监控,当出现大量错误日志、特定错误类型(如 401 Unauthorized, 429 Rate Limited)时,触发告警。
2. **监控指标解读与告警管理**:
* **系统级指标**:监控服务器的 CPU、内存、网络 I/O 等资源使用情况。
* **应用级指标**:
* **请求吞吐量 (QPS)**:总请求量、每个账号的请求量。
* **响应延迟**:平均响应时间、P90/P99 延迟。
* **错误率**:总错误率、每个账号的错误率、不同错误类型的分布。
* **账号状态**:活跃账号数、被限流/禁用账号数、配额使用情况。
* **缓存命中率**:如果引入了缓存。
* **告警阈值**:根据历史数据和业务需求,设置合理的告警阈值,例如,当总错误率超过 5%、单个账号错误率超过 20%、P99 延迟超过 1 秒时触发告警。
* **告警渠道**:将告警发送到 IM 工具(如 Slack, DingTalk)、邮件、短信等,确保及时通知相关人员。
3. **账号配额管理**:
* **定期检查**:定期检查所有 Exa API 账号的配额使用情况和剩余配额。
* **配额预警**:当账号配额即将耗尽时,提前触发预警,以便及时补充或切换账号。
* **自动化补充**:探索自动化流程,在配额不足时自动创建或激活新的 Exa API 账号。
4. **配置更新与版本管理**:
* **版本控制**:所有配置(包括账号列表、负载均衡策略参数、健康检查阈值等)都应纳入版本控制系统,并有清晰的变更记录。
* **安全更新**:配置更新应遵循严格的发布流程,例如,先在测试环境验证,然后灰度发布到生产环境。
* **回滚机制**:确保在配置更新出现问题时,能够快速回滚到之前的稳定版本。
5. **定期审计与安全检查**:
* **API Key 安全**:定期审查 API Key 的存储和访问权限,确保其安全性。考虑使用密钥管理服务(KMS)进行加密存储。
* **代理 IP 审计**:定期检查代理 IP 池的质量,移除被污染或不可用的代理。
* **安全漏洞扫描**:定期对 `exa-mcp-server` 代码进行安全漏洞扫描,及时修复潜在风险。
## 第八部分:潜在风险与解决方案
在设计和实现 `exa-mcp-server` 多账号轮询负载均衡功能时,需要充分识别和应对潜在的风险,以确保系统的稳定性、可靠性和安全性。
### 并发问题
1. **风险描述**:
* 在多实例部署或高并发场景下,多个请求可能同时尝试更新同一个 Exa API 账号的状态(如 `requestCount`、`errorCount`、`status`)或从 `AccountManager` 获取下一个可用账号。
* 这可能导致并发写入冲突、数据不一致,甚至竞争条件,使得 `AccountManager` 中的账号状态无法准确反映实际情况,影响负载均衡的决策。
2. **解决方案**:
* **分布式锁**:在更新共享账号状态或从账号池中选择账号时,引入分布式锁(例如,基于 Redis 的 Redlock 算法或 ZooKeeper/Etcd 的分布式锁)。确保在任何给定时间只有一个实例或线程能够修改关键数据。
* **CAS (Compare-And-Swap) 操作**:对于部分状态更新,如果底层数据存储支持,可以采用 CAS 操作。在更新数据前,先读取当前值,然后尝试写入新值,但只有当当前值与读取时的一致时才允许写入。这可以有效避免“脏写”。
* **异步队列与单线程处理**:将账号状态的更新请求放入一个消息队列(如 Kafka, RabbitMQ),由一个专门的单线程消费者进行处理。这可以避免并发写入冲突,但可能会增加一些延迟。
* **乐观锁**:在账号数据中添加版本号或时间戳字段。每次更新时,检查版本号是否一致,不一致则重试或报错。
### 数据一致性问题
1. **风险描述**:
* 在分布式部署中(例如,多个 `exa-mcp-server` 实例运行在不同的服务器上),每个实例可能维护一份独立的 `AccountManager` 状态。
* 当某个实例更新了账号状态(如将某个账号标记为“限流”或“禁用”)时,其他实例可能无法及时感知到这些变化,导致它们继续向已受限的账号发送请求,加剧问题或导致服务质量下降。
2. **解决方案**:
* **基于 Redis 的状态同步**:
* 将 `AccountManager` 的核心状态(账号列表、实时指标、状态等)存储在 Redis 等共享存储中。
* 每个 `exa-mcp-server` 实例启动时从 Redis 加载初始状态,并定期同步更新。
* 当某个实例更新了账号状态时,除了更新本地内存,还立即将变更写入 Redis,并通过 Redis Pub/Sub 机制通知其他实例进行状态同步。
* **最终一致性模型**:对于非强实时性的状态(如长期指标),可以接受最终一致性。通过定期(例如,每 5-10 秒)将本地状态同步到共享存储,并从共享存储拉取最新状态。
* **强一致性协议 (如 Paxos/Raft)**:如果对账号状态的一致性要求极高(例如,金融交易级别的精确度),可以考虑引入 Paxos 或 Raft 等强一致性协议。但这会显著增加系统的复杂性。
* **中心化 `AccountManager` 服务**:将 `AccountManager` 作为一个独立的微服务部署,所有 `exa-mcp-server` 实例都通过 RPC 调用来获取和更新账号状态,确保所有操作都经过中心服务处理,从而保证数据一致性。
### 性能瓶颈
1. **风险描述**:
* **Exa API 本身限流**:这是外部因素,需要通过多账号轮询来缓解。
* **`AccountManager` 成为瓶颈**:在高并发下,如果 `AccountManager` 的内部实现(如锁竞争、数据结构效率)不够优化,可能成为系统瓶颈,导致请求排队、延迟增加。
* **网络延迟**:与 Exa API 或代理服务器之间的网络延迟会直接影响响应时间。
* **数据库 I/O (如果持久化账号信息)**:如果账号信息需要频繁地从数据库读写,数据库 I/O 可能成为瓶颈。
* **Node.js 事件循环阻塞**:长时间运行的同步操作(尽管 Node.js 提倡异步)或 CPU 密集型计算可能阻塞事件循环,影响整体吞吐量。
2. **解决方案**:
* **缓存**:如“扩展性与维护”部分所述,引入请求结果缓存、元数据缓存,减少对 Exa API 和共享存储的访问。
* **连接池优化**:合理配置 HTTP 客户端的连接池,复用 TCP 连接,减少连接建立开销。
* **异步处理**:确保所有 I/O 操作(网络请求、文件读写、数据库访问)都采用异步非阻塞模式。
* **读写分离 (针对账号信息)**:如果账号信息需要持久化,可以考虑读写分离,将读请求分发到只读副本,减轻主库压力。
* **优化 `AccountManager` 内部实现**:
* 使用高效的数据结构(如 `Map` 代替 `Array` 进行账号查找)。
* 减少锁粒度,只在修改共享资源时加锁。
* 考虑使用无锁数据结构或原子操作(如果适用)。
* **垂直扩展与水平扩展**:
* **垂直扩展**:提升单个服务器的硬件配置(CPU、内存)。
* **水平扩展**:增加 `exa-mcp-server` 实例的数量,通过负载均衡器(如 Nginx, ALB)将流量分发到多个实例。
### 反检测与封禁风险
1. **风险描述**:
* Exa API 作为公共服务,通常会有反爬虫、反滥用机制,以防止恶意请求或过度使用。
* 如果请求模式过于规律、IP 频繁切换、User-Agent 固定等,可能被 Exa API 识别为非人类行为,导致账号被限流、封禁或请求被拒绝。
* 单点故障可能导致所有请求都使用同一个 IP,增加被封禁的风险。
2. **解决方案**:
* **IP 代理绑定 (Key-Proxy Binding)**:
* **核心策略**:强烈推荐并实现“一个 API Key 对应一个代理 IP”的策略,如 `exa_mcp_server_architecture_guidance.md` 中所述。通过对 API Key 进行哈希处理,将其稳定绑定到一个特定的代理 IP。
* **规避原理**:模拟真实用户行为,避免因 IP 频繁切换而触发 Exa API 的异常检测。同时,将不同 Key 的流量分散到不同的 IP 上,降低单个 IP 被封禁对整体服务的影响。
* **代理池管理**:维护一个高质量、高可用、地域分散的代理 IP 池,并定期进行健康检查和更新。
* **请求随机化**:
* **User-Agent 随机化**:使用一个包含多种常见浏览器 User-Agent 的列表,每次请求随机选择一个。
* **请求头随机化**:除了 User-Agent,还可以随机化其他 HTTP 请求头(如 `Accept-Language`, `Referer`),使其看起来更自然。
* **请求间隔随机化**:在连续请求之间引入随机延迟(例如,使用 `Math.random()` 结合 `setTimeout`),避免固定频率的请求模式。
* **用户行为模拟**:
* **请求参数随机化**:如果 Exa API 允许,可以对某些非关键的请求参数进行小范围的随机化。
* **请求路径多样化**:如果 Exa API 有多个相似的端点,可以在不同请求中随机使用不同的端点。
* **限流与熔断**:
* **细粒度限流**:为每个 API Key 和每个代理 IP 设置独立的请求频率限制,并动态调整。
* **熔断机制**:当某个 Key 或代理被 Exa API 限流或返回错误码时,立即触发熔断,暂时停止使用该 Key/代理,并切换到其他可用的资源。
* **日志脱敏与安全**:
* **API Key 脱敏**:在日志中对 API Key 进行脱敏处理,防止敏感信息泄露。
* **异常行为监控**:监控 Exa API 返回的错误码(特别是 403 Forbidden, 429 Too Many Requests)和请求被拒绝的情况,及时发现异常请求模式。
* **IP 信誉度管理**:对于代理 IP,可以考虑引入信誉度机制。根据 IP 的历史使用表现(成功率、错误率、被封禁次数),动态调整其优先级或将其从代理池中移除。