UNPKG

multi-lane-manager

Version:

Nacos 泳道管理与请求路由组件

1 lines 30.7 kB
{"version":3,"sources":["../src/utils/nacos.ts"],"sourcesContent":["import axios from 'axios';\nimport type { ServiceInstanceInfo } from '../types';\nimport { getConfig, getGlobalState, updateConfigPort } from './config';\nimport { logger } from './logger';\n\n/**\n * Nacos 实例查询缓存项\n */\ninterface NacosCacheItem {\n data: ServiceInstanceInfo[]; // 缓存的实例数据\n timestamp: number; // 缓存时间戳\n promise?: Promise<ServiceInstanceInfo[]>; // 正在进行的请求 Promise(用于去重)\n}\n\n/**\n * Nacos 实例查询缓存\n * 使用 Map 存储缓存,key 为 \"serviceName:targetLaneId\" 格式\n */\nconst nacosInstanceCache = new Map<string, NacosCacheItem>();\n\n/**\n * 生成缓存键\n * @param serviceName 服务名称\n * @param targetLaneId 目标泳道ID\n * @returns 缓存键\n */\nfunction getCacheKey(serviceName: string, targetLaneId: string): string {\n return `${serviceName}:${targetLaneId}`;\n}\n\n/**\n * 检查缓存是否有效\n * @param cacheItem 缓存项\n * @param ttl 缓存过期时间(毫秒)\n * @returns 是否有效\n */\nfunction isCacheValid(cacheItem: NacosCacheItem, ttl: number): boolean {\n return Date.now() - cacheItem.timestamp < ttl;\n}\n\n/**\n * 清理过期的缓存项\n * @param ttl 缓存过期时间(毫秒)\n */\nfunction cleanExpiredCache(ttl: number): void {\n const now = Date.now();\n for (const [key, item] of nacosInstanceCache.entries()) {\n if (now - item.timestamp >= ttl) {\n nacosInstanceCache.delete(key);\n logger.debug(`🗑️ 清理过期缓存: ${key}`);\n }\n }\n}\n\n/**\n * Nacos 实例接口\n * 定义了从 Nacos 服务器返回的实例数据结构\n */\nexport interface NacosInstance {\n instanceId: string; // 实例 ID\n ip: string; // 实例 IP 地址\n port: number; // 实例端口\n weight: number; // 实例权重\n healthy: boolean; // 实例是否健康\n enabled: boolean; // 实例是否启用\n ephemeral: boolean; // 是否为临时实例\n clusterName: string; // 集群名称\n serviceName: string; // 服务名称\n metadata: Record<string, string>; // 实例元数据\n instanceHeartBeatInterval?: number; // 心跳间隔\n}\n\n/**\n * Nacos 服务列表响应接口\n * 定义了 Nacos 服务实例列表查询的响应结构\n */\nexport interface NacosServiceListResponse {\n name?: string; // 服务名称\n groupName?: string; // 分组名称\n clusters?: string; // 集群名称\n cacheMillis?: number; // 缓存时间\n hosts: NacosInstance[]; // 实例列表\n lastRefTime?: number; // 最后刷新时间\n checksum?: string; // 校验和\n allIPs?: boolean; // 是否包含所有 IP\n env?: string; // 环境\n}\n\n/**\n * 注册服务实例到 Nacos\n * 将当前服务实例注册到 Nacos 服务注册中心\n *\n * @param port 服务端口\n * @returns 注册是否成功\n */\nexport async function registerServiceInstance(port: number): Promise<boolean> {\n const config = getConfig();\n\n // 检查泳道功能是否启用\n if (!config.isLaneEnabled) {\n logger.debug('🚫 泳道功能未启用,跳过服务注册');\n return true;\n }\n\n try {\n logger.debug(`🚀 开始服务注册流程,时间戳: ${new Date().toISOString()}`);\n\n // 更新配置中的端口\n updateConfigPort(port);\n\n // 构建注册请求\n const registrationUrl = `${config.nacosUrl}/nacos/v1/ns/instance`;\n const params = new URLSearchParams();\n\n // 设置基本参数\n params.append('serviceName', config.serviceName);\n params.append('ip', config.host);\n params.append('port', port.toString());\n params.append('weight', '1.0');\n params.append('healthy', 'true');\n params.append('metadata', JSON.stringify(config.metadata));\n params.append('clusterName', config.currentLaneId || 'DEFAULT');\n params.append('ephemeral', 'true');\n\n // 添加过期时间参数\n params.append('ipDeleteTimeout', config.instanceTtl.toString());\n params.append('instanceHeartBeatInterval', config.heartbeatInterval.toString());\n params.append('instanceHeartBeatTimeOut', (config.heartbeatInterval * 2).toString());\n\n // 发送注册请求\n logger.debug(`📤 发送注册请求到 Nacos: ${registrationUrl}`);\n\n const response = await axios.post(registrationUrl, params, {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': 'Nuxt3-LaneManager/1.0',\n },\n timeout: config.registrationTimeout,\n });\n\n // 记录响应结果\n logger.debug(`📥 注册响应状态码: ${response.status}, 数据: ${JSON.stringify(response.data)}`);\n\n if (response.status === 200 && response.data === 'ok') {\n logger.success(`✅ 服务注册成功: ${config.serviceName}@${config.host}:${port} (泳道: ${config.currentLaneId})`);\n\n // 设置全局状态\n const globalState = getGlobalState();\n globalState._laneMgrRegistered = true;\n globalState._laneMgrPort = port;\n\n // 启动心跳\n startHeartbeat(port);\n\n return true;\n } else {\n logger.error(`❌ 服务注册失败: ${response.data}`);\n return false;\n }\n } catch (error) {\n logger.error(`❌ 服务注册错误: ${error instanceof Error ? error.message : String(error)}`);\n if (axios.isAxiosError(error) && error.response) {\n logger.error(`🔴 HTTP状态码: ${error.response.status}, 响应: ${JSON.stringify(error.response.data)}`);\n }\n return false;\n }\n}\n\n/**\n * 从 Nacos 注销服务实例\n * 从 Nacos 服务注册中心注销当前服务实例\n *\n * @param port 服务端口\n * @returns 注销是否成功\n */\nexport async function deregisterServiceInstance(port: number): Promise<boolean> {\n const config = getConfig();\n\n // 检查泳道功能是否启用\n if (!config.isLaneEnabled) {\n logger.debug('🚫 泳道功能未启用,跳过服务注销');\n return true;\n }\n\n try {\n // 停止心跳\n stopHeartbeat();\n\n // 构建注销请求\n const deregistrationUrl = `${config.nacosUrl}/nacos/v1/ns/instance`;\n const params = new URLSearchParams();\n params.append('serviceName', config.serviceName);\n params.append('ip', config.host);\n params.append('port', port.toString());\n params.append('ephemeral', 'true');\n\n // 发送注销请求\n logger.debug(`📤 发送注销请求到 Nacos: ${deregistrationUrl}`);\n\n const response = await axios.delete(`${deregistrationUrl}?${params.toString()}`, {\n timeout: config.registrationTimeout,\n });\n\n // 记录响应结果\n logger.debug(`📥 注销响应状态码: ${response.status}, 数据: ${JSON.stringify(response.data)}`);\n\n if (response.status === 200 && response.data === 'ok') {\n logger.success(`✅ 服务注销成功: ${config.serviceName}@${config.host}:${port}`);\n\n // 更新全局状态\n const globalState = getGlobalState();\n globalState._laneMgrRegistered = false;\n globalState._laneMgrPort = undefined;\n\n return true;\n } else {\n logger.error(`❌ 服务注销失败: ${response.data}`);\n return false;\n }\n } catch (error) {\n logger.error(`❌ 服务注销错误: ${error instanceof Error ? error.message : String(error)}`);\n return false;\n }\n}\n\n/**\n * 发送心跳到 Nacos\n * 定期向 Nacos 发送心跳,保持服务实例的活跃状态\n *\n * @param port 服务端口\n * @param logDetails 是否打印详细日志,默认为 false\n * @returns 心跳是否成功\n */\nexport async function sendHeartbeat(port: number, logDetails: boolean = false): Promise<boolean> {\n const config = getConfig();\n\n // 检查泳道功能是否启用\n if (!config.isLaneEnabled) {\n if (logDetails) {\n logger.debug('🚫 泳道功能未启用,跳过心跳');\n }\n return true;\n }\n\n try {\n // 构建心跳请求\n const heartbeatUrl = `${config.nacosUrl}/nacos/v1/ns/instance/beat`;\n const params = new URLSearchParams();\n params.append('serviceName', config.serviceName);\n params.append('ip', config.host);\n params.append('port', port.toString());\n\n // 构建心跳数据\n const now = new Date();\n const beat = {\n serviceName: config.serviceName,\n ip: config.host,\n port: port,\n weight: 1.0,\n metadata: {\n ...config.metadata,\n lastHeartbeat: now.toISOString(), // 添加心跳时间戳\n heartbeatTimestamp: now.getTime().toString(), // 添加时间戳(毫秒)\n },\n cluster: config.currentLaneId,\n scheduled: false\n };\n\n params.append('beat', JSON.stringify(beat));\n\n // 发送心跳请求(只在需要时打印日志)\n if (logDetails) {\n logger.debug(`💓 发送心跳到 Nacos: ${heartbeatUrl}`);\n }\n\n const response = await axios.put(`${heartbeatUrl}?${params.toString()}`, null, {\n headers: {\n 'User-Agent': 'Nuxt3-LaneManager/1.0',\n },\n timeout: config.registrationTimeout / 2, // 心跳超时时间应该比注册短\n });\n\n // 记录响应结果(只在需要时打印日志)\n if (logDetails) {\n logger.debug(`📥 心跳响应状态码: ${response.status}, 数据: ${JSON.stringify(response.data)}`);\n }\n\n if (response.status === 200) {\n if (logDetails) {\n logger.debug(`✅ 心跳成功: ${config.serviceName}@${config.host}:${port}`);\n }\n return true;\n } else {\n // 失败总是打印日志,无论 logDetails 如何设置\n logger.warn(`⚠️ 心跳失败: ${response.data}`);\n return false;\n }\n } catch (error) {\n logger.error(`❌ 心跳错误: ${error instanceof Error ? error.message : String(error)}`);\n return false;\n }\n}\n\n/**\n * 启动心跳定时器\n * 创建一个定时器,定期向 Nacos 发送心跳\n *\n * @param port 服务端口\n */\nexport function startHeartbeat(port: number): void {\n const config = getConfig();\n const globalState = getGlobalState();\n\n // 如果已经有心跳定时器,先停止\n stopHeartbeat();\n\n // 初始化心跳计数器\n globalState._laneMgrHeartbeatCount = 0;\n\n // 创建新的心跳定时器\n logger.info(`⏱️ 启动心跳定时器,间隔: ${config.heartbeatInterval}毫秒,每30次心跳打印一次日志`);\n\n globalState._laneMgrHeartbeatTimer = setInterval(async () => {\n try {\n // 增加心跳计数器\n if (globalState._laneMgrHeartbeatCount !== undefined) {\n globalState._laneMgrHeartbeatCount++;\n } else {\n globalState._laneMgrHeartbeatCount = 1;\n }\n\n // 是否打印详细日志(每30次打印一次)\n const shouldLogDetails = globalState._laneMgrHeartbeatCount % 30 === 0;\n\n // 发送心跳\n await sendHeartbeat(port, shouldLogDetails);\n\n // 如果达到30次,在日志中显示计数器状态\n if (shouldLogDetails) {\n logger.info(`💓 心跳计数: ${globalState._laneMgrHeartbeatCount},继续保持连接...`);\n }\n } catch (error) {\n logger.error(`❌ 心跳定时器错误: ${error instanceof Error ? error.message : String(error)}`);\n }\n }, config.heartbeatInterval);\n}\n\n/**\n * 停止心跳定时器\n * 清除心跳定时器,通常在服务注销时调用\n */\nexport function stopHeartbeat(): void {\n const globalState = getGlobalState();\n\n if (globalState._laneMgrHeartbeatTimer) {\n logger.info('⏹️ 停止心跳定时器');\n clearInterval(globalState._laneMgrHeartbeatTimer);\n globalState._laneMgrHeartbeatTimer = undefined;\n globalState._laneMgrHeartbeatCount = 0; // 重置心跳计数器\n }\n}\n\n/**\n * 内部函数:直接从 Nacos 查询实例(不使用缓存)\n * @param serviceName 服务名称\n * @param targetLaneId 目标泳道ID\n * @param sortByHeartbeat 是否按心跳时间排序\n * @returns 健康服务实例列表\n */\nasync function fetchNacosLaneInstancesFromServer(\n serviceName: string,\n targetLaneId: string,\n sortByHeartbeat: boolean = true\n): Promise<ServiceInstanceInfo[]> {\n const config = getConfig();\n\n logger.debug(`📡 直接从 Nacos 服务器查询实例,服务名: ${serviceName}, 目标泳道: ${targetLaneId}`);\n\n try {\n // 构建查询 URL\n const nacosListUrl = `${config.nacosUrl}/nacos/v1/ns/instance/list`;\n\n // 设置查询参数\n const params = new URLSearchParams({\n serviceName,\n healthyOnly: 'true', // 只获取健康实例\n groupName: config.nacosGroupName,\n });\n\n // 添加命名空间参数(如果有)\n if (config.nacosNamespace) {\n params.append('namespaceId', config.nacosNamespace);\n }\n\n logger.debug(`📤 Nacos 实例查询 URL: ${nacosListUrl}?${params.toString()}`);\n\n // 发送查询请求\n const response = await axios.get<NacosServiceListResponse>(`${nacosListUrl}?${params.toString()}`, {\n timeout: config.registrationTimeout,\n });\n\n logger.debug(`📥 Nacos 实例查询响应状态: ${response.status}`);\n\n // 处理响应数据\n if (response.data && response.data.hosts) {\n // 过滤出目标泳道的健康实例\n const filteredInstances = response.data.hosts\n .filter(\n (instance) =>\n instance.healthy && instance.enabled && instance.metadata && instance.metadata.laneId === targetLaneId,\n )\n .map(\n (instance): ServiceInstanceInfo => {\n // 尝试从元数据中获取最后心跳时间\n let lastHeartbeat = Date.now();\n\n // 检查实例元数据中是否有心跳时间信息\n if (instance.metadata?.heartbeatTimestamp) {\n // 优先使用数字时间戳(毫秒)\n try {\n const timestamp = parseInt(instance.metadata.heartbeatTimestamp, 10);\n if (!isNaN(timestamp) && timestamp > 0) {\n lastHeartbeat = timestamp;\n }\n } catch (error) {\n logger.debug(`⚠️ 解析实例 ${instance.ip}:${instance.port} 数字时间戳失败: ${error}`);\n }\n } else if (instance.metadata?.lastHeartbeat) {\n // 备选:使用ISO字符串时间戳\n try {\n const heartbeatTime = new Date(instance.metadata.lastHeartbeat).getTime();\n if (!isNaN(heartbeatTime)) {\n lastHeartbeat = heartbeatTime;\n }\n } catch (error) {\n logger.debug(`⚠️ 解析实例 ${instance.ip}:${instance.port} ISO时间戳失败: ${error}`);\n }\n }\n\n // 如果Nacos返回了实例的心跳间隔信息,可以估算最后心跳时间\n if (instance.instanceHeartBeatInterval && !instance.metadata?.lastHeartbeat) {\n // 使用当前时间减去心跳间隔的一半作为估算值\n lastHeartbeat = Date.now() - (instance.instanceHeartBeatInterval / 2);\n }\n\n return {\n ip: instance.ip,\n port: instance.port,\n serviceName: instance.serviceName,\n clusterName: instance.clusterName,\n ephemeral: instance.ephemeral,\n metadata: instance.metadata,\n status: 'UP',\n lastHeartbeat,\n version: instance.metadata?.version || 'unknown',\n };\n },\n );\n\n // 如果需要按心跳时间排序\n if (sortByHeartbeat) {\n filteredInstances.sort((a, b) => {\n // 按最后心跳时间降序排列(最近心跳的在前)\n const timeA = typeof a.lastHeartbeat === 'number' ? a.lastHeartbeat : new Date(a.lastHeartbeat).getTime();\n const timeB = typeof b.lastHeartbeat === 'number' ? b.lastHeartbeat : new Date(b.lastHeartbeat).getTime();\n return timeB - timeA;\n });\n\n logger.debug(`🔄 已按心跳时间排序 ${filteredInstances.length} 个实例`);\n\n // 在调试模式下输出排序后的实例信息\n if (filteredInstances.length > 0) {\n logger.debug(`📊 实例心跳时间排序结果:`);\n filteredInstances.forEach((instance, index) => {\n const heartbeatTime = typeof instance.lastHeartbeat === 'number'\n ? new Date(instance.lastHeartbeat).toISOString()\n : instance.lastHeartbeat;\n logger.debug(` ${index + 1}. ${instance.ip}:${instance.port} - 心跳时间: ${heartbeatTime}`);\n });\n }\n }\n\n logger.info(`✅ 从服务器查询到 ${filteredInstances.length} 个属于泳道 ${targetLaneId} 的健康实例${sortByHeartbeat ? '(已按心跳时间排序)' : ''}`);\n return filteredInstances;\n } else {\n logger.warn(`⚠️ Nacos 实例查询响应格式不正确或无实例列表:`, response.data);\n return [];\n }\n } catch (error) {\n logger.error(`❌ Nacos 实例查询失败: ${error instanceof Error ? error.message : String(error)}`);\n if (axios.isAxiosError(error) && error.response) {\n logger.error(`🔴 HTTP状态码: ${error.response.status}, 响应: ${JSON.stringify(error.response.data)}`);\n }\n return [];\n }\n}\n\n/**\n * 从 Nacos 获取指定泳道的健康服务实例(带缓存)\n * 查询 Nacos 获取指定服务名称和泳道 ID 的健康实例列表\n * 支持按心跳时间排序,优先选择最近心跳的实例\n * 使用缓存和请求去重机制提高性能\n *\n * @param serviceName 服务名称\n * @param targetLaneId 目标泳道ID\n * @param sortByHeartbeat 是否按心跳时间排序(最近心跳的在前)\n * @returns 健康服务实例列表\n */\nexport async function getNacosLaneInstances(\n serviceName: string,\n targetLaneId: string,\n sortByHeartbeat: boolean = true\n): Promise<ServiceInstanceInfo[]> {\n const config = getConfig();\n const cacheKey = getCacheKey(serviceName, targetLaneId);\n const cacheTtl = config.nacosCacheTtl;\n\n // 使用 console.log 确保日志一定会输出\n console.log(`[multi-lane-manager] 🔍 获取 Nacos 实例,服务名: ${serviceName}, 目标泳道: ${targetLaneId}`);\n logger.debug(`🔍 获取 Nacos 实例,服务名: ${serviceName}, 目标泳道: ${targetLaneId}, 缓存TTL: ${cacheTtl}ms`);\n\n // 定期清理过期缓存\n cleanExpiredCache(cacheTtl);\n\n // 检查缓存\n const cachedItem = nacosInstanceCache.get(cacheKey);\n if (cachedItem) {\n // 如果有正在进行的请求,等待该请求完成(请求去重)\n if (cachedItem.promise) {\n logger.debug(`⏳ 发现正在进行的请求,等待完成: ${cacheKey}`);\n try {\n return await cachedItem.promise;\n } catch (error) {\n // 如果正在进行的请求失败,移除缓存项并继续执行新请求\n logger.warn(`⚠️ 正在进行的请求失败,移除缓存: ${cacheKey}, 错误: ${error}`);\n nacosInstanceCache.delete(cacheKey);\n }\n }\n // 如果缓存有效,直接返回缓存数据\n else if (isCacheValid(cachedItem, cacheTtl)) {\n logger.debug(`💾 使用缓存数据: ${cacheKey}, 缓存时间: ${new Date(cachedItem.timestamp).toISOString()}`);\n return cachedItem.data;\n }\n // 缓存过期,移除缓存项\n else {\n logger.debug(`⏰ 缓存已过期,移除: ${cacheKey}`);\n nacosInstanceCache.delete(cacheKey);\n }\n }\n\n // 创建新的请求 Promise\n logger.debug(`🌐 创建新的 Nacos 查询请求: ${cacheKey}`);\n const requestPromise = fetchNacosLaneInstancesFromServer(serviceName, targetLaneId, sortByHeartbeat);\n\n // 将 Promise 存储到缓存中(用于请求去重)\n nacosInstanceCache.set(cacheKey, {\n data: [], // 临时空数据\n timestamp: Date.now(),\n promise: requestPromise,\n });\n\n try {\n // 等待请求完成\n const result = await requestPromise;\n\n // 更新缓存,移除 Promise\n nacosInstanceCache.set(cacheKey, {\n data: result,\n timestamp: Date.now(),\n });\n\n logger.debug(`✅ 请求完成,已缓存结果: ${cacheKey}, 实例数量: ${result.length}`);\n return result;\n } catch (error) {\n // 请求失败,移除缓存项\n nacosInstanceCache.delete(cacheKey);\n logger.error(`❌ Nacos 查询请求失败,移除缓存: ${cacheKey}, 错误: ${error}`);\n throw error;\n }\n}\n\n/**\n * 清理所有 Nacos 实例查询缓存\n * 用于手动清理缓存或在配置变更时重置缓存\n */\nexport function clearNacosInstanceCache(): void {\n const cacheSize = nacosInstanceCache.size;\n nacosInstanceCache.clear();\n logger.info(`🗑️ 已清理所有 Nacos 实例缓存,清理了 ${cacheSize} 个缓存项`);\n}\n\n/**\n * 获取当前缓存状态信息\n * 用于调试和监控\n */\nexport function getNacosCacheStats(): {\n totalItems: number;\n validItems: number;\n expiredItems: number;\n pendingRequests: number;\n} {\n const config = getConfig();\n const cacheTtl = config.nacosCacheTtl;\n const now = Date.now();\n\n let validItems = 0;\n let expiredItems = 0;\n let pendingRequests = 0;\n\n for (const item of nacosInstanceCache.values()) {\n if (item.promise) {\n pendingRequests++;\n } else if (now - item.timestamp < cacheTtl) {\n validItems++;\n } else {\n expiredItems++;\n }\n }\n\n return {\n totalItems: nacosInstanceCache.size,\n validItems,\n expiredItems,\n pendingRequests,\n };\n}\n"],"mappings":"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","names":[]}