UNPKG

@bililive-tools/douyin-recorder

Version:
197 lines (196 loc) 7.06 kB
import { getRoomInfo } from "../douyin_api.js"; /** * API 负载均衡器类 * 实现多个 API 接口的负载均衡调用,具备失败重试和禁用机制 */ export class APILoadBalancer { endpoints = []; config; constructor(config) { this.config = { maxFailures: 3, // 连续失败3次后禁用 blockDuration: 3 * 60 * 1000, // 禁用3分钟 retryMultiplier: 1.5, // 重试时间倍增 healthCheckInterval: 30 * 1000, // 30秒健康检查 ...config, }; // 初始化可用的 API 端点 this.initializeEndpoints(); } /** * 初始化 API 端点配置 */ initializeEndpoints() { const defaultEndpoints = [ { name: "web", priority: 2, weight: 1 }, { name: "webHTML", priority: 1, weight: 1 }, { name: "mobile", priority: 6, weight: 1 }, { name: "userHTML", priority: 4, weight: 1 }, ]; this.endpoints = defaultEndpoints.map((endpoint) => ({ endpoint, failureCount: 0, lastFailureTime: 0, isBlocked: false, nextRetryTime: 0, })); } /** * 获取下一个可用的 API 端点 * 使用加权轮询算法,优先选择权重高且未被禁用的端点 */ getNextEndpoint() { const now = Date.now(); // 清理过期的禁用状态 this.endpoints.forEach((status) => { if (status.isBlocked && now >= status.nextRetryTime) { status.isBlocked = false; status.failureCount = Math.max(0, status.failureCount - 1); // 部分恢复 } }); // 获取可用的端点 const availableEndpoints = this.endpoints.filter((status) => !status.isBlocked); if (availableEndpoints.length === 0) { return null; // 所有端点都被禁用 } // 按优先级和权重排序 availableEndpoints.sort((a, b) => { if (a.endpoint.priority !== b.endpoint.priority) { return a.endpoint.priority - b.endpoint.priority; // 优先级越小越好 } return b.endpoint.weight - a.endpoint.weight; // 权重越大越好 }); // 使用加权随机选择 const totalWeight = availableEndpoints.reduce((sum, status) => sum + status.endpoint.weight, 0); const random = Math.random() * totalWeight; let currentWeight = 0; for (const status of availableEndpoints) { currentWeight += status.endpoint.weight; if (random <= currentWeight) { return status; } } // 如果加权选择失败,返回第一个可用的 return availableEndpoints[0]; } /** * 记录 API 调用失败 */ recordFailure(apiType, error) { const status = this.endpoints.find((s) => s.endpoint.name === apiType); if (!status) return; status.failureCount++; status.lastFailureTime = Date.now(); // 如果失败次数超过阈值,禁用该端点 if (status.failureCount >= this.config.maxFailures) { status.isBlocked = true; const blockDuration = this.config.blockDuration * Math.pow(this.config.retryMultiplier, status.failureCount - this.config.maxFailures); status.nextRetryTime = Date.now() + blockDuration; console.warn(`API ${apiType} has been blocked due to ${status.failureCount} failures. Next retry at: ${(new Date(status.nextRetryTime).toISOString(), error.message)}`); } } /** * 记录 API 调用成功 */ recordSuccess(apiType) { const status = this.endpoints.find((s) => s.endpoint.name === apiType); if (!status) return; // 成功调用后,减少失败计数 if (status.failureCount > 0) { status.failureCount = Math.max(0, status.failureCount - 1); } // 如果之前被禁用,现在可以恢复 if (status.isBlocked && status.failureCount === 0) { status.isBlocked = false; status.nextRetryTime = 0; } } /** * 使用负载均衡策略调用 getRoomInfo */ async callWithLoadBalance(webRoomId, opts = {}) { const maxAttempts = this.endpoints.length; let lastError = null; for (let attempt = 0; attempt < maxAttempts; attempt++) { const endpointStatus = this.getNextEndpoint(); if (!endpointStatus) { throw new Error("所有 API 端点都不可用,请稍后重试"); } const apiType = endpointStatus.endpoint.name; try { const result = await getRoomInfo(webRoomId, { ...opts, api: apiType, }); // 调用成功,记录成功状态 this.recordSuccess(apiType); return result; } catch (error) { lastError = error; this.recordFailure(apiType, lastError); console.warn(`API ${apiType} failed (attempt ${attempt + 1}/${maxAttempts}):`, lastError.message); // 如果这是最后一次尝试,或者没有更多可用端点,则抛出错误 if (attempt === maxAttempts - 1) { break; } } } throw new Error(`所有 API 调用都失败了。最后一个错误: ${lastError?.message || "未知错误"}`); } /** * 获取当前端点状态(用于调试和监控) */ getEndpointStatus() { return this.endpoints.map((status) => ({ ...status })); } /** * 手动重置某个端点的状态 */ resetEndpoint(apiType) { const status = this.endpoints.find((s) => s.endpoint.name === apiType); if (status) { status.failureCount = 0; status.lastFailureTime = 0; status.isBlocked = false; status.nextRetryTime = 0; } } /** * 重置所有端点状态 */ resetAllEndpoints() { this.endpoints.forEach((status) => { status.failureCount = 0; status.lastFailureTime = 0; status.isBlocked = false; status.nextRetryTime = 0; }); } /** * 更新端点配置 */ updateEndpointConfig(apiType, updates) { const status = this.endpoints.find((s) => s.endpoint.name === apiType); if (status) { Object.assign(status.endpoint, updates); } } /** * 获取负载均衡器配置 */ getConfig() { return { ...this.config }; } /** * 更新负载均衡器配置 */ updateConfig(updates) { Object.assign(this.config, updates); } } // 创建全局单例实例 export const globalLoadBalancer = new APILoadBalancer();