multi-lane-manager
Version:
Nacos 泳道管理与请求路由组件
569 lines (471 loc) • 24.9 kB
text/typescript
import type { H3Event } from "h3";
import type { ServiceInstanceInfo } from "../types";
import { getConfig, getGlobalState, updateConfigPort } from "../utils/config";
import {
DEFAULT_PORT,
DEFAULT_LANE_TARGET_HEADER,
HEADER_LANE_DEBUG,
HEADER_LANE_DETAIL,
getSafeHeaderValue
} from "../utils/defaults";
import { logger } from "../utils/logger";
import { deregisterServiceInstance, getNacosLaneInstances, registerServiceInstance, startHeartbeat, stopHeartbeat } from "../utils/nacos";
import { LoadBalanceStrategy, proxyRequestWithFailover, selectInstance } from "../utils/proxy";
/**
* 从cookie字符串中解析指定key的值
*
* @param cookieHeader cookie字符串,格式如 "key1=value1; key2=value2"
* @param key 要查找的cookie键名
* @returns 找到的cookie值,如果未找到则返回undefined
*/
function parseCookieValue(cookieHeader: string, key: string): string | undefined {
console.log(`[multi-lane-manager] 解析Cookie: cookieHeader=${cookieHeader}, key=${key}`);
if (!cookieHeader || !key) {
console.log(`[multi-lane-manager] Cookie解析失败: cookieHeader或key为空`);
return undefined;
}
// 将cookie字符串分割成键值对数组
const cookies = cookieHeader.split(';');
console.log(`[multi-lane-manager] Cookie分割结果: ${JSON.stringify(cookies)}`);
// 查找匹配的cookie
for (const cookie of cookies) {
console.log(`[multi-lane-manager] 处理Cookie片段: ${cookie}`);
const parts = cookie.split('=');
if (parts.length < 2) {
console.log(`[multi-lane-manager] 无效的Cookie格式: ${cookie}`);
continue;
}
const cookieKey = parts[0].trim();
const cookieValue = parts.slice(1).join('=').trim(); // 处理值中可能包含等号的情况
console.log(`[multi-lane-manager] Cookie键值对: key=${cookieKey}, value=${cookieValue}`);
// 不区分大小写比较cookie键名
if (cookieKey.toLowerCase() === key.toLowerCase()) {
console.log(`[multi-lane-manager] 找到匹配的Cookie: ${cookieKey}=${cookieValue}`);
return cookieValue;
}
}
console.log(`[multi-lane-manager] 未找到匹配的Cookie: ${key}`);
return undefined;
}
/**
* 从环境变量或 Nitro 配置中获取服务器端口
* 优先使用环境变量中的端口配置
*
* @returns 服务器端口
*/
export function getServerPort(): number {
// 优先使用环境变量中的端口
const port = parseInt(process.env.NITRO_PORT || process.env.PORT || String(DEFAULT_PORT), 10);
logger.debug(`🔍 从环境变量获取服务器端口: ${port}`);
return port;
}
/**
* 自动检测和注册服务实例
* 这个函数主要负责服务注册和设置进程退出处理程序
*
* @returns 是否尝试了注册
*/
export function detectPortAndRegisterOnFirstRequest(): boolean {
logger.info(`====== 🚀 开始端口检测和服务注册流程 ======`);
// 获取配置
logger.debug(`📋 正在加载配置...`);
const config = getConfig();
try {
// 检查泳道功能是否启用
logger.debug(`🔍 检查泳道功能是否启用: ${config.isLaneEnabled}`);
if (!config.isLaneEnabled) {
logger.debug(`🚫 泳道功能未启用,跳过初始注册`);
return false;
}
// 获取服务器端口
const serverPort = getServerPort();
logger.info(`🔌 检测到服务器端口: ${serverPort}`);
// 检查全局注册状态
const globalState = getGlobalState();
if (!globalState._laneMgrRegistered) {
logger.info(`🆕 首次检测到端口 ${serverPort},准备注册服务...`);
// 设置全局注册状态
globalState._laneMgrRegistered = true;
globalState._laneMgrPort = serverPort;
// 更新配置中的端口
updateConfigPort(serverPort);
// 注册服务实例
registerServiceInstance(serverPort)
.then((success) => {
if (success) {
logger.info(`✅ 服务注册成功,设置进程退出处理程序`);
// 创建优雅退出处理函数
const gracefulShutdown = async () => {
logger.info(`🛑 应用退出/信号,正在注销服务,端口=${serverPort}...`);
await deregisterServiceInstance(serverPort);
};
// 注册进程退出事件处理程序
process.on("beforeExit", gracefulShutdown);
process.on("SIGINT", async () => {
logger.info("🔴 收到 SIGINT 信号,执行注销...");
await gracefulShutdown();
process.exit(0);
});
process.on("SIGTERM", async () => {
logger.info("🔴 收到 SIGTERM 信号,执行注销...");
await gracefulShutdown();
process.exit(0);
});
} else {
logger.warn(`⚠️ 服务注册失败,不设置退出处理程序`);
}
})
.catch((err) => {
logger.error(`❌ 注册过程中出错: ${err.message}`, err.stack);
});
logger.info(`====== ✅ 端口检测和服务注册流程完成,返回 true ======`);
return true;
} else {
logger.info(`ℹ️ 服务已注册 (端口: ${globalState._laneMgrPort}),跳过初始注册`);
}
logger.info(`====== ✅ 端口检测和服务注册流程完成,返回 false ======`);
return false;
} catch (error) {
logger.error(
`❌ 端口检测或初始注册错误: ${error instanceof Error ? error.message : String(error)}`,
error instanceof Error ? error.stack : "",
);
return false;
}
}
/**
* 创建服务器中间件以处理跨泳道代理
* 这个中间件负责检测请求的目标泳道,并在必要时将请求转发到目标泳道的实例
* 注意:服务注册逻辑已移至 Nitro 插件
*
* @returns 中间件处理函数
*/
export function createServerMiddleware() {
const config = getConfig();
// 使用 console.log 确保日志一定会输出
console.log(`[multi-lane-manager] 🔧 服务器中间件初始化: 启用状态=${config.isLaneEnabled}, 当前泳道ID=${config.currentLaneId}, 服务名=${config.serviceName}, 目标泳道Header=${config.targetLaneHeaderKey || DEFAULT_LANE_TARGET_HEADER.toLowerCase()}, Cookie检测=${config.isLaneCookieEnabled ? '启用' : '禁用'}`);
logger.info(
`🔧 服务器中间件初始化: 启用状态=${config.isLaneEnabled}, 当前泳道ID=${config.currentLaneId}, 服务名=${config.serviceName}, 目标泳道Header=${config.targetLaneHeaderKey || DEFAULT_LANE_TARGET_HEADER.toLowerCase()}, Cookie检测=${config.isLaneCookieEnabled ? '启用' : '禁用'}`,
);
return async (event: H3Event) => {
// 检查是否启用了调试模式
const debugHeaderKey = HEADER_LANE_DEBUG.toLowerCase();
const debugHeaderValue = event.node.req.headers[debugHeaderKey];
const isDebugMode = !!debugHeaderValue;
// 记录调试模式检测过程
console.log(`[multi-lane-manager] 调试模式检测: headerKey=${HEADER_LANE_DEBUG}, headerKeyLower=${debugHeaderKey}, value=${debugHeaderValue}, isDebugMode=${isDebugMode}`);
const debugInfo: string[] = [];
// 如果启用了调试模式,收集调试信息
if (isDebugMode) {
debugInfo.push(`请求时间: ${new Date().toISOString()}`);
debugInfo.push(`请求路径: ${event.node.req.method} ${event.node.req.url || ""}`);
debugInfo.push(`当前泳道: ${config.currentLaneId}`);
debugInfo.push(`服务名称: ${config.serviceName}`);
}
// 检查请求是否已经被处理过
if (event.context._laneManagerHandled) {
if (isDebugMode) debugInfo.push(`状态: 请求已被泳道管理器处理,跳过`);
logger.debug(`⏭️ 请求已被泳道管理器处理,跳过`);
return;
}
const requestPath = event.node.req.url || "";
// 特别记录 /_nuxt/ 路径的请求
if (event.path.startsWith('/_nuxt/')) {
console.log(`[multi-lane-manager] Intercepting _nuxt request2: ${event.path}`);
logger.info(`🔍 拦截到静态资源请求: ${event.path}`);
}
// 特别记录 /api/ 路径的请求
if (event.path.startsWith('/api/')) {
console.log(`[multi-lane-manager] Intercepting API request: ${event.path}`);
logger.info(`🔍 拦截到API请求: ${event.path}, 方法: ${event.node.req.method}, 头部: ${JSON.stringify(event.node.req.headers)}`);
}
// 强制使用 console.log 确保日志一定会输出
console.log(`[multi-lane-manager] 📥 中间件接收请求: ${event.node.req.method} ${requestPath}`);
logger.debug(`📥 中间件接收请求: ${event.node.req.method} ${requestPath}`);
if (!config.isLaneEnabled) {
if (isDebugMode) debugInfo.push(`状态: 泳道功能未启用,跳过代理逻辑`);
logger.debug(`🚫 泳道功能未启用,跳过代理逻辑`);
// 如果启用了调试模式,添加调试响应头
if (isDebugMode && !event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
return;
}
// 标记请求正在被处理
event.context._laneManagerProcessing = true;
// 获取目标泳道ID
const headerKey = config.targetLaneHeaderKey || DEFAULT_LANE_TARGET_HEADER;
// 记录请求头信息,帮助调试
console.log(`[multi-lane-manager] 请求头信息:`, event.node.req.headers);
// HTTP 请求头是不区分大小写的,在 Node.js 中会被自动转换为小写
const headerKeyLower = headerKey.toLowerCase();
// 1. 首先尝试从header中获取泳道ID
const requestTargetLaneIdHeaderValue = event.node.req.headers[headerKeyLower];
let requestTargetLaneId = Array.isArray(requestTargetLaneIdHeaderValue)
? requestTargetLaneIdHeaderValue[0]?.trim()
: requestTargetLaneIdHeaderValue?.trim();
// 记录泳道ID获取过程
console.log(`[multi-lane-manager] 尝试从请求头获取泳道ID: headerKey=${headerKey}, headerKeyLower=${headerKeyLower}, value=${requestTargetLaneIdHeaderValue}`);
if (isDebugMode) {
debugInfo.push(`目标泳道Header键: ${headerKey} (实际查找: ${headerKeyLower})`);
}
// 2. 如果header中没有泳道ID,且启用了cookie功能,则尝试从cookie中获取
if (!requestTargetLaneId && config.isLaneCookieEnabled) {
const cookieHeader = event.node.req.headers.cookie;
if (cookieHeader) {
// 记录 Cookie 信息,帮助调试
console.log(`[multi-lane-manager] Cookie信息: ${cookieHeader}`);
// 从cookie字符串中解析出目标泳道ID
// 使用与header完全相同的键名,不再移除'x-'前缀
const cookieKey = headerKey;
const cookieValue = parseCookieValue(cookieHeader, cookieKey);
// 记录 Cookie 解析过程
console.log(`[multi-lane-manager] 尝试从Cookie获取泳道ID: cookieKey=${cookieKey}, value=${cookieValue}`);
if (cookieValue) {
requestTargetLaneId = cookieValue.trim();
console.log(`[multi-lane-manager] 🍪 从Cookie中获取到泳道ID: ${requestTargetLaneId}, Cookie键名: ${cookieKey}`);
logger.debug(`🍪 从Cookie中获取到泳道ID: ${requestTargetLaneId}, Cookie键名: ${cookieKey}`);
if (isDebugMode) debugInfo.push(`泳道ID来源: Cookie, 值: ${requestTargetLaneId}`);
} else if (isDebugMode) {
debugInfo.push(`Cookie中未找到泳道ID, Cookie键: ${cookieKey}`);
}
} else if (isDebugMode) {
debugInfo.push(`请求中没有Cookie`);
}
} else if (requestTargetLaneId) {
console.log(`[multi-lane-manager] 🔤 从Header中获取到泳道ID: ${requestTargetLaneId}`);
logger.debug(`🔤 从Header中获取到泳道ID: ${requestTargetLaneId}`);
if (isDebugMode) debugInfo.push(`泳道ID来源: Header, 值: ${requestTargetLaneId}`);
} else if (isDebugMode) {
debugInfo.push(`未找到泳道ID`);
}
// 处理请求的泳道路由逻辑
if (requestTargetLaneId && requestTargetLaneId !== config.currentLaneId) {
// 1. 如果请求指定了目标泳道,且与当前泳道不同,则进行代理
// 确定泳道ID的来源
const sourceType = event.node.req.headers[headerKey] ? 'Header' : 'Cookie';
// 使用 console.log 确保日志一定会输出
console.log(`[multi-lane-manager] 🔀 检测到跨泳道请求: 当前泳道=${config.currentLaneId}, 目标泳道=${requestTargetLaneId} (来源: ${sourceType}), 路径: ${event.path}`);
logger.info(`🔀 检测到跨泳道请求: 当前泳道=${config.currentLaneId}, 目标泳道=${requestTargetLaneId} (来源: ${sourceType}), 路径: ${event.path}`);
if (isDebugMode) {
debugInfo.push(`处理: 跨泳道请求`);
debugInfo.push(`目标泳道: ${requestTargetLaneId}`);
}
} else if (!requestTargetLaneId) {
// 2. 如果请求没有指定泳道ID,尝试路由到 baseline 泳道
logger.debug(`🔍 请求未指定泳道ID,尝试查找 baseline 泳道实例`);
if (isDebugMode) {
debugInfo.push(`处理: 未指定泳道ID,尝试路由到 baseline 泳道`);
}
// 如果当前已经是 baseline 泳道,直接在本地处理
if (config.currentLaneId === 'baseline') {
logger.debug(`✅ 当前已是 baseline 泳道,在本地处理请求`);
if (isDebugMode) {
debugInfo.push(`状态: 当前已是 baseline 泳道,在本地处理`);
// 如果启用了调试模式,添加调试响应头
if (!event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// 清除处理中标记,但不标记为已处理,因为请求将由后续中间件处理
event.context._laneManagerProcessing = false;
return;
}
// 设置目标泳道ID为 baseline
requestTargetLaneId = 'baseline';
if (isDebugMode) {
debugInfo.push(`目标泳道: ${requestTargetLaneId} (默认)`);
}
}
// 如果有目标泳道ID(可能是请求中指定的,也可能是默认的 baseline),且与当前泳道不同,则尝试进行代理
if (requestTargetLaneId && requestTargetLaneId !== config.currentLaneId) {
try {
// 获取目标泳道的服务实例(按心跳时间排序)
const targetInstances = await getNacosLaneInstances(config.serviceName, requestTargetLaneId, true);
if (isDebugMode) {
debugInfo.push(`找到实例数量: ${targetInstances.length}`);
}
if (targetInstances.length > 0) {
// 使用负载均衡策略选择一个实例
const targetInstance = selectInstance(
targetInstances,
config.serviceName,
LoadBalanceStrategy.ROUND_ROBIN
);
if (isDebugMode) {
debugInfo.push(`选择实例: ${targetInstance.ip}:${targetInstance.port}`);
debugInfo.push(`负载均衡策略: ROUND_ROBIN`);
// 添加调试响应头
if (!event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// 转发请求到目标实例(使用故障转移)
await proxyRequestWithFailover(event, targetInstance, isDebugMode, targetInstances);
return;
} else {
// 如果是尝试路由到 baseline 但没找到实例,则在当前实例处理
if (requestTargetLaneId === 'baseline' && !event.node.req.headers[headerKey]) {
logger.info(`🔄 未找到 baseline 泳道实例,在当前泳道 ${config.currentLaneId} 处理请求`);
if (isDebugMode) {
debugInfo.push(`状态: 未找到 baseline 泳道实例,在当前泳道处理`);
// 如果启用了调试模式,添加调试响应头
if (!event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// 清除处理中标记,但不标记为已处理,因为请求将由后续中间件处理
event.context._laneManagerProcessing = false;
return;
}
// 对于明确指定了泳道ID的请求,如果找不到实例,尝试查找 baseline 泳道
logger.warn(`⚠️ 未找到泳道 ${requestTargetLaneId} 的健康实例,尝试查找 baseline 泳道`);
if (isDebugMode) {
debugInfo.push(`警告: 未找到泳道 ${requestTargetLaneId} 的健康实例,尝试查找 baseline 泳道`);
}
// 如果当前实例已经是 baseline 泳道,直接在本地处理,避免无限循环
if (config.currentLaneId === 'baseline') {
console.log(`[multi-lane-manager] 当前实例已是 baseline 泳道,直接本地处理请求,避免无限转发`);
logger.info(`🔄 当前实例已是 baseline 泳道,直接本地处理请求,避免无限转发`);
if (isDebugMode) {
debugInfo.push(`状态: 当前实例已是 baseline 泳道,直接本地处理请求,避免无限转发`);
// 如果启用了调试模式,添加调试响应头
if (!event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// 清除处理中标记,但不标记为已处理,因为请求将由后续中间件处理
event.context._laneManagerProcessing = false;
return;
}
// 如果当前请求的泳道不是 baseline,则尝试查找 baseline 泳道
if (requestTargetLaneId !== 'baseline') {
console.log(`[multi-lane-manager] 尝试查找 baseline 泳道实例`);
// 获取 baseline 泳道的服务实例(按心跳时间排序)
const baselineInstances = await getNacosLaneInstances(config.serviceName, 'baseline', true);
if (baselineInstances.length > 0) {
console.log(`[multi-lane-manager] 找到 baseline 泳道实例,将请求转发到 baseline 泳道`);
logger.info(`✅ 找到 baseline 泳道实例,将请求转发到 baseline 泳道`);
if (isDebugMode) {
debugInfo.push(`状态: 找到 baseline 泳道实例,将请求转发到 baseline 泳道`);
}
// 使用负载均衡策略选择一个实例
const baselineInstance = selectInstance(
baselineInstances,
config.serviceName,
LoadBalanceStrategy.ROUND_ROBIN
);
if (isDebugMode) {
debugInfo.push(`选择实例: ${baselineInstance.ip}:${baselineInstance.port}`);
debugInfo.push(`负载均衡策略: ROUND_ROBIN`);
// 添加调试响应头
if (!event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// 转发请求到 baseline 实例(使用故障转移)
await proxyRequestWithFailover(event, baselineInstance, isDebugMode, baselineInstances);
return;
} else {
console.log(`[multi-lane-manager] 未找到 baseline 泳道实例,在当前泳道处理请求`);
logger.info(`🔄 未找到 baseline 泳道实例,在当前泳道 ${config.currentLaneId} 处理请求`);
if (isDebugMode) {
debugInfo.push(`状态: 未找到 baseline 泳道实例,在当前泳道处理请求`);
}
// 清除处理中标记,但不标记为已处理,因为请求将由后续中间件处理
event.context._laneManagerProcessing = false;
// 如果启用了调试模式,添加调试响应头
if (isDebugMode && !event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
return;
}
} else {
// 如果当前请求的泳道已经是 baseline,但仍然没有找到实例,则在当前泳道处理
console.log(`[multi-lane-manager] 已经是 baseline 泳道但未找到实例,在当前泳道处理请求`);
logger.info(`🔄 已经是 baseline 泳道但未找到实例,在当前泳道 ${config.currentLaneId} 处理请求`);
if (isDebugMode) {
debugInfo.push(`状态: 已经是 baseline 泳道但未找到实例,在当前泳道处理请求`);
}
// 清除处理中标记,但不标记为已处理,因为请求将由后续中间件处理
event.context._laneManagerProcessing = false;
// 如果启用了调试模式,添加调试响应头
if (isDebugMode && !event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
return;
}
}
} catch (e) {
logger.error(`❌ 处理跨泳道请求时发生错误: ${e instanceof Error ? e.message : String(e)}`);
if (isDebugMode) {
debugInfo.push(`错误: ${e instanceof Error ? e.message : String(e)}`);
}
if (!event.node.res.headersSent) {
// 如果启用了调试模式,添加调试响应头
if (isDebugMode) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
event.node.res.statusCode = 500; // Internal Server Error
event.node.res.setHeader("Content-Type", "text/plain");
event.node.res.end("处理泳道请求时发生内部服务器错误。");
}
// 标记请求已处理
event.context._laneManagerHandled = true;
return;
}
}
logger.debug(`✅ 请求在当前泳道 (${config.currentLaneId}) 处理或无目标泳道指定,将由后续处理程序处理。`);
if (isDebugMode) {
debugInfo.push(`处理: 在当前泳道处理请求`);
// 如果启用了调试模式,添加调试响应头
if (!event.node.res.headersSent) {
try {
event.node.res.setHeader(HEADER_LANE_DETAIL, getSafeHeaderValue(debugInfo));
} catch (error) {
logger.error(`❌ 设置调试响应头时出错: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// 清除处理中标记,但不标记为已处理,因为请求将由后续中间件处理
event.context._laneManagerProcessing = false;
};
}
// 导出 Nacos 相关函数,方便其他模块使用
export { getNacosLaneInstances };