Multi-Lane Manager 是一个 Nuxt 模块,提供基于 Nacos 的服务注册、发现和请求路由功能,用于在 Nuxt 应用中实现多泳道架构。
多泳道架构通过 Nacos 服务注册与发现和请求拦截分发两大核心机制实现,使同一服务的不同实例能够并行运行并智能路由请求。
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ 多泳道架构实现原理 │
│ │
├─────────────────┐ ┌─────────────────────────┤
│ │ │ │
│ Nacos服务注册 │◄──────────┐ ┌──────────────►│ 请求拦截与分发 │
│ │ │ │ │ │
└─────────────────┘ │ │ └─────────────────────────┘
│ │ │ │
▼ │ │ ▼
┌─────────────────┐ │ │ ┌─────────────────────────┐
│ 服务启动自动注册 │ │ │ │ HTTP请求拦截 │
└─────────────────┘ │ │ └─────────────────────────┘
│ │ │ │
▼ │ │ ▼
┌─────────────────┐ │ │ ┌─────────────────────────┐
│ 心跳维持 │ │ │ │ 泳道ID提取 │
└─────────────────┘ │ │ └─────────────────────────┘
│ │ │ │
▼ │ │ ▼
┌─────────────────┐ │ │ ┌─────────────────────────┐
│ 优雅下线 │ │ │ │ 目标实例查询 │
└─────────────────┘ │ │ └─────────────────────────┘
│ │ │ │
▼ │ │ ▼
┌─────────────────┐ │ │ ┌─────────────────────────┐
│ 元数据管理 │───────────┘ └──────────────│ 请求转发 │
└─────────────────┘ └─────────────────────────┘
服务注册流程:
- 应用启动时,自动向Nacos注册当前服务实例
- 注册信息包含:服务名称、IP地址、端口、泳道ID(作为元数据)
- 启动定时任务,定期向Nacos发送心跳,维持实例健康状态
- 应用关闭时,自动向Nacos注销服务实例
服务发现机制:
- 根据服务名称和目标泳道ID查询Nacos服务实例
- 支持按心跳时间排序,优先选择最近心跳的健康实例
- 使用负载均衡策略(默认轮询)选择目标实例
- 支持实例健康检查,只选择健康的实例
请求拦截:
- 通过Nitro插件和服务器中间件拦截所有HTTP请求
- 支持拦截静态资源请求(如CSS/JS/HTML)和API请求
泳道识别:
- 从请求头(默认X-Lane-ID)或Cookie中提取目标泳道ID
- 如果未指定泳道ID,尝试路由到baseline泳道
- 如果当前实例已是目标泳道,直接在本地处理请求
请求路由:
- 根据目标泳道ID查询Nacos获取目标实例
- 如果找到目标实例,将请求转发到目标实例
- 如果未找到目标实例但指定了baseline泳道,尝试查找baseline泳道实例
- 如果未找到任何匹配实例,在当前实例处理请求
请求转发:
- 保留原始请求的方法、路径、查询参数和请求体
- 添加代理相关的请求头(X-Forwarded-For等)
- 将目标实例的响应返回给客户端
- 支持调试模式,提供详细的请求处理信息
故障转移与健壮性:
- 自动检测实例故障(连接失败、超时、502/503/504错误)
- 智能故障转移到其他健康实例
- 实例黑名单机制,避免重复请求故障实例
- 自动重试机制,提高请求成功率
- 实例健康状态自动恢复
多泳道架构是一种微服务部署模式,允许同一个服务的多个实例在不同的环境或配置下并行运行。每个"泳道"代表一个独立的服务实例集合,具有自己的配置和状态。
多泳道架构的优势:
- 支持多版本并行部署
- 便于 A/B 测试和灰度发布
- 隔离测试环境和生产环境
- 支持按团队或功能划分服务实例
- 🚀 服务自动注册:应用启动时自动向 Nacos 注册服务
- 🔄 心跳维持:自动维持与 Nacos 的心跳连接
- 🔍 服务发现:基于泳道 ID 发现目标服务实例
- 🔀 请求转发:自动将请求转发到目标泳道的服务实例
- 🛑 优雅下线:应用关闭时自动注销服务
- 🔄 智能路由:未指定泳道 ID 的请求会优先路由到 baseline 泳道
在 Nuxt 项目中安装 Multi-Lane Manager 模块:
# 使用 npm
npm install multi-lane-manager
# 使用 yarn
yarn add multi-lane-manager
# 使用 pnpm
pnpm add multi-lane-manager
在 nuxt.config.ts
文件中添加模块:
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
// 其他模块...
'multi-lane-manager/nuxt'
],
})
创建 .env
文件或在启动命令中设置以下环境变量:
环境变量 | 必须/可选 | 默认值 | 说明 |
---|---|---|---|
服务标识配置 | |||
SERVICE_NAME |
必须 | 无 | 服务名称,用于在 Nacos 中注册服务。如果未设置,泳道功能将被禁用。可以通过以下方式获取:1) 此环境变量,2) 其他环境变量(NITRO_APP_NAME, APP_NAME, npm_package_name),3) package.json 的 name 字段 |
NITRO_APP_NAME |
可选 | 无 | Nitro 应用名称环境变量,当 SERVICE_NAME 未设置时使用 |
APP_NAME |
可选 | 无 | 应用名称环境变量,当 SERVICE_NAME 和 NITRO_APP_NAME 未设置时使用 |
npm_package_name |
可选 | 无 | npm 运行时提供的 package.json 中的 name 字段,当其他环境变量未设置时使用 |
泳道配置 | |||
LANE_ENABLE |
可选 | false | 是否启用泳道功能 |
LANE_ID |
可选 | baseline | 当前服务的泳道 ID |
LANE_TARGET_HEADER |
可选 | X-Lane-ID | 目标泳道请求头键名 |
LANE_COOKIE_ENABLE |
可选 | true | 是否启用从 cookie 中检测泳道 ID |
Nacos 服务器配置 | |||
LANE_SERVER |
可选 | localhost:8848 | Nacos 服务器地址和端口(格式:host:port) |
LANE_NAMESPACE |
可选 | public | Nacos 命名空间 |
LANE_GROUP_NAME |
可选 | DEFAULT_GROUP | Nacos 分组名称 |
心跳配置 | |||
LANE_HEARTBEAT_INTERVAL |
可选 | 5000 | 心跳间隔(毫秒),建议与 Java 客户端保持一致 |
LANE_INSTANCE_TTL |
可选 | 15000 | 实例过期时间(毫秒),建议为心跳间隔的 3 倍 |
端口配置 | |||
NITRO_PORT |
可选 | 3000 | Nitro 服务器端口,优先级高于 PORT |
PORT |
可选 | 3000 | 服务器端口,当 NITRO_PORT 未设置时使用 |
主机配置 | |||
HOST |
可选 | 自动检测 | 服务IP地址,支持自动检测容器IP |
POD_IP |
可选 | 无 | Kubernetes环境下的Pod IP地址 |
CONTAINER_IP |
可选 | 无 | 容器环境下的IP地址 |
超时配置 | |||
LANE_PROXY_TIMEOUT |
可选 | 15000 | 代理请求超时时间(毫秒) |
LANE_REGISTRATION_TIMEOUT |
可选 | 5000 | 注册请求超时时间(毫秒) |
日志配置 | |||
LOG_LEVEL |
可选 | info | 日志级别,可选值: silent, fatal, error, warn, info, success, debug, trace, verbose |
LANE_LOG_LEVEL |
可选 | 同 LOG_LEVEL | 泳道管理器专用日志级别,优先级低于 LOG_LEVEL |
# 必须配置 - 服务标识
SERVICE_NAME=your-service-name # 服务名称(或通过 package.json 提供)
# 泳道配置
LANE_ENABLE=true
LANE_ID=dev-lane # 当前服务的泳道 ID
LANE_TARGET_HEADER=X-Lane-ID # 目标泳道请求头键名
# Nacos 配置
LANE_SERVER=localhost:8848 # Nacos 服务器地址和端口
LANE_NAMESPACE=public # Nacos 命名空间
LANE_GROUP_NAME=DEFAULT_GROUP # Nacos 分组名称
# 心跳配置
LANE_HEARTBEAT_INTERVAL=5000 # 心跳间隔(毫秒)
LANE_INSTANCE_TTL=15000 # 实例过期时间(毫秒)
# 端口配置
NITRO_PORT=3000 # Nitro 服务器端口
# 主机配置(可选,支持自动检测)
# HOST=192.168.1.100 # 手动指定IP地址
# POD_IP=10.244.0.10 # Kubernetes Pod IP(通常由K8s自动注入)
# CONTAINER_IP=172.17.0.2 # 容器IP地址
# 日志配置
LOG_LEVEL=info # 日志级别
确保 Nacos 服务器已经启动并可访问。如果没有 Nacos 服务器,可以使用 Docker 快速启动一个:
docker run --name nacos-standalone -e MODE=standalone -e JVM_XMS=512m -e JVM_XMX=512m -e JVM_XMN=256m -p 8848:8848 -d nacos/nacos-server:latest
npm run dev
启动后,应用将自动向 Nacos 注册服务,并开始发送心跳。
# 启动 dev-lane 泳道实例
NITRO_PORT=3001 LANE_ENABLE=true LANE_ID=dev-lane SERVICE_NAME=your-service npm run dev
# 启动 test-lane 泳道实例
NITRO_PORT=3002 LANE_ENABLE=true LANE_ID=test-lane SERVICE_NAME=your-service npm run dev
# 向 dev-lane 发送请求,但目标泳道为 test-lane
curl -H "X-Lane-ID: test-lane" http://localhost:3001/api/some-endpoint
# 向 test-lane 发送请求,但目标泳道为 dev-lane
curl -H "X-Lane-ID: dev-lane" http://localhost:3002/api/some-endpoint
# 不指定泳道ID的请求(将尝试路由到 baseline 泳道)
curl http://localhost:3001/api/some-endpoint
多泳道管理器引入了 "baseline" 泳道的概念,作为默认的基准泳道:
- 默认泳道ID:如果应用启用了泳道功能但没有设置泳道ID,则默认使用 "baseline" 作为泳道ID
-
智能路由:当收到一个没有指定泳道ID的请求时:
- 首先查询 Nacos 是否有 "baseline" 泳道的实例
- 如果有,则将请求路由到 "baseline" 泳道的实例
- 如果没有,则在当前实例处理请求
这种设计有以下优势:
- 提供了一个稳定的基准环境(baseline)
- 未指定泳道的请求默认使用基准环境,确保一致的用户体验
- 当基准环境不可用时,自动降级到当前实例处理,提高系统可用性
在生产环境中部署多泳道架构时,建议:
-
使用环境变量注入配置:
# 生产环境启动命令示例 NODE_ENV=production LANE_ENABLE=true LANE_ID=prod-lane SERVICE_NAME=your-service LANE_SERVER=nacos.example.com:8848 node .output/server/index.mjs
-
使用 PM2 或 Docker 管理进程:
# PM2 示例 pm2 start ecosystem.config.js # Docker 示例 docker run -e LANE_ENABLE=true -e LANE_ID=prod-lane -e SERVICE_NAME=your-service -e LANE_SERVER=nacos.example.com:8848 -p 3000:3000 your-service-image
-
配置健康检查: 确保监控系统可以检查服务的健康状态,包括 Nacos 连接状态。
-
设置合理的心跳间隔和过期时间: 在生产环境中,建议将心跳间隔设置为 5-10 秒,过期时间设置为心跳间隔的 2-3 倍。
在 Docker 容器中部署时,multi-lane-manager 会自动检测容器的IP地址:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# 设置环境变量
ENV LANE_ENABLE=true
ENV LANE_ID=prod-lane
ENV SERVICE_NAME=your-service
ENV LANE_SERVER=nacos.example.com:8848
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
启动容器:
docker run -d \
--name your-service-prod \
-e LANE_ENABLE=true \
-e LANE_ID=prod-lane \
-e SERVICE_NAME=your-service \
-e LANE_SERVER=nacos.example.com:8848 \
-p 3000:3000 \
your-service-image
在 Kubernetes 中部署时,建议使用 Pod IP 作为服务注册地址:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: your-service
spec:
replicas: 3
selector:
matchLabels:
app: your-service
template:
metadata:
labels:
app: your-service
spec:
containers:
- name: your-service
image: your-service-image:latest
ports:
- containerPort: 3000
env:
# 泳道配置
- name: LANE_ENABLE
value: "true"
- name: LANE_ID
value: "prod-lane"
- name: SERVICE_NAME
value: "your-service"
# Nacos 配置
- name: LANE_SERVER
value: "nacos.example.com:8848"
# Pod IP 自动注入
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
# 其他配置
- name: NITRO_PORT
value: "3000"
- name: LOG_LEVEL
value: "info"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# 健康检查
livenessProbe:
httpGet:
path: /api/lane-manager/health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/lane-manager/health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
multi-lane-manager 采用完全自动化的IP检测机制,无需手动配置:
-
网络接口自动检测(核心逻辑):
- 优先检测容器网络接口:
eth0
,eth1
,ens3
,ens4
- 然后检测物理/虚拟机接口:
ens160
,enp0s3
,en0
等 - 智能过滤Docker桥接网络和虚拟接口
- 优先选择私有网络IP(10.x.x.x, 172.16-31.x.x, 192.168.x.x)
- 优先检测容器网络接口:
-
Kubernetes环境变量(备选方案):
- 当网络接口检测失败时,使用
POD_IP
环境变量 - 通常由Downward API自动注入
- 当网络接口检测失败时,使用
-
其他容器环境变量(最后备选):
- 当前两种方法都失败时,使用
CONTAINER_IP
环境变量
- 当前两种方法都失败时,使用
-
智能过滤机制:
- 自动跳过内部地址(127.x.x.x)和Docker桥接网络(172.17.x.x)
- 过滤虚拟接口(docker0, br-, veth等)
- 优先选择私有网络IP,符合容器环境实际情况
-
容错机制:
- 如果所有检测方法都失败,提供详细的错误信息和解决建议
- 仅在极端情况下才回退到localhost
-
完全自动化:无需手动配置IP地址,让系统自动检测
-
Kubernetes Pod IP注入:使用Downward API自动注入Pod IP
env: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP
-
健康检查:配置 liveness 和 readiness 探针
-
资源限制:设置合适的 CPU 和内存限制
-
日志级别:生产环境使用
info
级别,调试时使用debug
-
优雅关闭:确保容器正确处理 SIGTERM 信号
-
网络策略:确保容器能够访问Nacos服务器
-
监控告警:监控服务注册状态和心跳健康
可以通过 Nacos 控制台查看服务注册情况,通常访问 http://nacos-server:8848/nacos/
并登录(默认用户名/密码:nacos/nacos)。
检查以下几点:
- 确保目标泳道的服务实例已注册并健康
- 检查网络连接是否正常
- 查看日志中的错误信息
- 使用调试头获取详细信息(见下文)
可以通过添加 X-Lane-Debug
请求头来获取详细的调试信息。当请求中包含此头时,响应中会包含 X-Lane-Detail
头,其中包含请求处理的详细信息。
# 使用 curl 发送带调试头的请求
curl -H "X-Lane-Debug: true" -H "X-Lane-ID: test-lane" http://localhost:3000/api/some-endpoint
响应头中的 X-Lane-Detail
包含以下信息:
- 请求时间和路径
- 当前泳道和服务名称
- 目标泳道信息(如果有)
- 请求处理方式(本地处理或跨泳道转发)
- 如果是跨泳道请求,还包括目标实例信息和负载均衡策略
- 如果发生错误,包含错误信息
这对于排查泳道路由和转发问题非常有用。
可以通过修改 nuxt.config.ts
中的配置选项自定义请求转发逻辑:
multiLaneManager: {
// 自定义目标泳道请求头键名
targetLaneHeaderKey: 'x-custom-lane',
// 自定义代理请求超时时间
proxyTimeout: 30000,
}
在 CI/CD 流程中,可以为每个分支或环境配置不同的泳道 ID,例如:
-
main
分支 →prod-lane
-
develop
分支 →dev-lane
- 特性分支 →
feature-{branch-name}-lane
这样可以实现按分支或环境隔离服务实例。
Nitro 插件是 Nuxt 3 中一种强大的扩展机制,允许您在服务器启动时执行代码、添加中间件、注册钩子等。Multi-Lane Manager 使用 Nitro 插件来实现服务注册和健康检查功能。
💡 提示:Nitro 插件与 Nuxt 插件不同。Nitro 插件在服务器端运行,而 Nuxt 插件可以在客户端和服务器端运行。Nitro 插件更适合处理服务器端的任务,如服务注册、数据库连接等。
Nitro 插件是一个导出默认函数的 JavaScript/TypeScript 文件,该函数接收 nitroApp
对象作为参数:
// my-nitro-plugin.ts
export default (nitroApp) => {
// 插件代码
}
Nitro 插件在服务器启动过程中被加载和执行,具体时机如下:
- 加载阶段:模块代码被加载到内存中
- 初始化阶段:插件默认导出函数被执行
- 运行阶段:服务器运行期间,插件注册的钩子会在相应事件发生时被调用
- 关闭阶段:服务器关闭时,插件可以执行清理操作
下图展示了 Nitro 插件的生命周期:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │ │ │
│ 加载阶段 │────▶│ 初始化阶段 │────▶│ 运行阶段 │────▶│ 关闭阶段 │
│ │ │ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 模块代码被加载 │ │ 插件默认函数 │ │ 钩子函数被调用 │ │ 清理资源 │
│ 到内存中 │ │ 被执行 │ │ (request等) │ │ 注销服务 │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
在 Nuxt 模块中注册 Nitro 插件的方法:
// 在 Nuxt 模块的 setup 函数中
nuxt.hook('nitro:config', (nitroConfig) => {
// 确保 plugins 数组存在
nitroConfig.plugins = nitroConfig.plugins || [];
// 添加插件
nitroConfig.plugins.push(resolve('./runtime/my-nitro-plugin'));
});
Nitro 插件可以使用以下常用钩子:
-
request
:处理每个请求 -
close
:服务器关闭时执行清理操作 -
error
:处理服务器错误
// 注册请求钩子
nitroApp.hooks.hook('request', async (event) => {
// 处理请求
});
// 注册关闭钩子
nitroApp.hooks.hook('close', async () => {
// 执行清理操作
});
// 注册错误钩子
nitroApp.hooks.hook('error', async (error, event) => {
// 处理错误
});
- 模块化设计:将插件代码分解为多个专门的函数,提高可维护性
- 错误处理:使用 try/catch 捕获异常,避免插件错误导致服务器崩溃
- 资源清理:在服务器关闭时释放资源,如关闭连接、取消定时器等
- 日志记录:使用日志系统记录插件的运行状态和错误信息
- 类型安全:使用 TypeScript 类型定义,提高代码质量和开发体验
调试 Nitro 插件可以使用以下方法:
日志输出:在插件中添加详细的日志输出,记录插件的执行流程和状态
logger.info('Nitro 插件初始化');
logger.debug('配置:', config);
环境变量:使用环境变量控制插件的行为,如启用/禁用功能、设置日志级别等
if (process.env.DEBUG_NITRO_PLUGIN === 'true') {
logger.level = 'debug';
}
健康检查端点:添加健康检查端点,返回插件的状态信息
nitroApp.hooks.hook('request', (event) => {
if (event.path === '/api/plugin-status') {
event.node.res.end(JSON.stringify({
status: 'running',
uptime: process.uptime(),
memory: process.memoryUsage(),
// 其他状态信息
}));
}
});
错误监控:捕获并记录插件中的错误,便于排查问题
try {
// 插件代码
} catch (error) {
logger.error('插件错误:', error);
// 可以将错误发送到监控系统
}
packages/multi-lane-manager/
├── src/
│ ├── index.ts # 主入口文件
│ ├── types.ts # 类型定义
│ ├── module.ts # Nuxt 模块定义
│ ├── runtime/
│ │ ├── nitro-plugin.ts # Nitro 插件(服务注册和健康检查)
│ │ ├── server-middleware.ts # 服务器中间件(请求转发)
│ │ └── server-utils.ts # 服务器工具函数
│ └── utils/
│ ├── config.ts # 配置管理
│ ├── logger.ts # 日志管理
│ ├── nacos.ts # Nacos 服务管理
│ └── proxy.ts # 请求转发
└── README.md # 文档
Nuxt 模块定义文件,负责注册 Nitro 插件和服务器中间件。
// module.ts
export default defineNuxtModule({
meta: {
name: "multi-lane-manager",
configKey: "multiLaneManager",
},
setup(_options, nuxt) {
// 添加 Nitro 插件
nuxt.hook('nitro:config', (nitroConfig) => {
nitroConfig.plugins = nitroConfig.plugins || [];
nitroConfig.plugins.push(resolve('./runtime/nitro-plugin'));
});
// 添加服务器中间件
addServerHandler({
handler: resolve('./runtime/server-middleware'),
middleware: true,
});
},
});
Nitro 插件文件,负责服务注册、心跳维持和健康检查。
// nitro-plugin.ts
export default (nitroApp) => {
// 立即注册服务
(async () => {
try {
const port = getServerPort();
await registerServiceInstance(port);
// 设置优雅下线
} catch (error) {
// 错误处理
}
})();
// 添加健康检查路由
nitroApp.hooks.hook('request', (event) => {
if (event.path === '/api/lane-manager/health') {
// 返回健康状态
}
});
};
服务器中间件文件,负责跨泳道请求转发。
// server-middleware.ts
import { createServerMiddleware } from './server-utils';
// 导出中间件函数
export default createServerMiddleware();
Nacos 服务管理文件,负责服务注册、注销、心跳维持和服务发现。
// nacos.ts
export async function registerServiceInstance(port: number): Promise<boolean> {
// 向 Nacos 注册服务实例
}
export async function deregisterServiceInstance(port: number): Promise<boolean> {
// 从 Nacos 注销服务实例
}
export async function sendHeartbeat(port: number): Promise<boolean> {
// 向 Nacos 发送心跳
}
export async function getNacosLaneInstances(
serviceName: string,
targetLaneId: string
): Promise<ServiceInstanceInfo[]> {
// 从 Nacos 获取指定泳道的服务实例
}
// 服务实例信息
interface ServiceInstanceInfo {
ip: string;
port: number;
serviceName: string;
clusterName: string;
ephemeral: boolean;
metadata: {
laneId: string;
[key: string]: string;
};
status: "UP" | "DOWN" | "UNKNOWN";
lastHeartbeat: number | string;
version?: string;
healthy?: boolean;
}
// 泳道信息
interface LaneInfo {
id: string;
instances: ServiceInstanceInfo[];
}
// 泳道管理器配置
interface LaneManagerConfig {
// Nacos 服务器配置
nacosServerAddr: string;
nacosServerPort: string;
nacosUrl: string;
nacosNamespace: string;
nacosGroupName: string;
// 服务配置
serviceName: string;
currentLaneId: string;
host: string;
port: number | null;
// 泳道配置
targetLaneHeaderKey: string; // 小写形式
isLaneEnabled: boolean;
// 超时配置
proxyTimeout: number;
registrationTimeout: number;
heartbeatInterval: number; // 心跳间隔,单位毫秒
instanceTtl: number; // 实例过期时间,单位毫秒
// 元数据
metadata: Record<string, any>;
}
// 创建服务器中间件
function createServerMiddleware(): (event: H3Event) => Promise<void>;
// 注册服务实例
function registerServiceInstance(port: number): Promise<boolean>;
// 注销服务实例
function deregisterServiceInstance(port: number): Promise<boolean>;
// 获取指定泳道的服务实例
function getNacosLaneInstances(serviceName: string, targetLaneId: string): Promise<ServiceInstanceInfo[]>;
// 获取服务器端口
function getServerPort(): number;
// 创建日志管理器
function createLogger(options?: LoggerOptions): ConsolaInstance;
// 获取配置
function getConfig(): LaneManagerConfig;
// 更新配置端口
function updateConfigPort(port: number): void;
-
服务注册流程:
- 应用启动时,Nitro 插件自动执行
- 获取服务器端口和配置信息
- 向 Nacos 发送注册请求
- 启动心跳定时器
- 设置进程退出时的注销逻辑
-
心跳维持流程:
- 服务注册成功后,自动启动心跳定时器
- 定期向 Nacos 发送心跳请求
- 心跳间隔由
NACOS_HEARTBEAT_INTERVAL
环境变量指定,默认为 6000 毫秒(6 秒) - 如果心跳失败,会记录错误日志但不会停止心跳
-
跨泳道请求转发流程:
- 服务器中间件拦截所有请求
- 检查请求头中是否包含目标泳道 ID
- 如果目标泳道 ID 与当前泳道 ID 不同,则进行转发
- 从 Nacos 获取目标泳道的服务实例
- 使用负载均衡策略选择一个实例
- 将请求转发到目标实例
- 将目标实例的响应返回给客户端
-
优雅下线流程:
- 监听进程退出事件(SIGINT、SIGTERM、beforeExit)
- 监听 Nitro 关闭事件
- 在这些事件发生时,向 Nacos 发送注销请求
- 确保服务实例被正确移除
当请求头中包含 X-Target-Lane
且值与当前服务的泳道 ID 不同时,模块会自动将请求转发到目标泳道的服务实例。
例如,向当前服务发送请求,但指定目标泳道为 test-lane
:
curl -H "X-Target-Lane: test-lane" http://localhost:3000/api/some-endpoint
模块会:
- 检测到这是一个跨泳道请求
- 从 Nacos 获取
test-lane
泳道的服务实例 - 将请求转发到目标实例
- 将目标实例的响应返回给客户端
应用关闭时,模块会自动向 Nacos 发送注销请求,确保服务实例被正确移除。
Nitro 插件是 Nuxt 3 中一种强大的扩展机制,允许您在服务器启动时执行代码、添加中间件、注册钩子等。Multi-Lane Manager 使用 Nitro 插件来实现服务注册和健康检查功能。
💡 提示:Nitro 插件与 Nuxt 插件不同。Nitro 插件在服务器端运行,而 Nuxt 插件可以在客户端和服务器端运行。Nitro 插件更适合处理服务器端的任务,如服务注册、数据库连接等。
Nitro 插件是一个导出默认函数的 JavaScript/TypeScript 文件,该函数接收 nitroApp
对象作为参数:
// my-nitro-plugin.ts
export default (nitroApp) => {
// 插件代码
}
Nitro 插件在服务器启动过程中被加载和执行,具体时机如下:
- 加载阶段:模块代码被加载到内存中
- 初始化阶段:插件默认导出函数被执行
- 运行阶段:服务器运行期间,插件注册的钩子会在相应事件发生时被调用
- 关闭阶段:服务器关闭时,插件可以执行清理操作
下图展示了 Nitro 插件的生命周期:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │ │ │
│ 加载阶段 │────▶│ 初始化阶段 │────▶│ 运行阶段 │────▶│ 关闭阶段 │
│ │ │ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 模块代码被加载 │ │ 插件默认函数 │ │ 钩子函数被调用 │ │ 清理资源 │
│ 到内存中 │ │ 被执行 │ │ (request等) │ │ 注销服务 │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
在 Nuxt 模块中注册 Nitro 插件的方法:
// 在 Nuxt 模块的 setup 函数中
nuxt.hook('nitro:config', (nitroConfig) => {
// 确保 plugins 数组存在
nitroConfig.plugins = nitroConfig.plugins || [];
// 添加插件
nitroConfig.plugins.push(resolve('./runtime/my-nitro-plugin'));
});
Nitro 插件可以使用以下常用钩子:
-
request
:处理每个请求 -
close
:服务器关闭时执行清理操作 -
error
:处理服务器错误
// 注册请求钩子
nitroApp.hooks.hook('request', async (event) => {
// 处理请求
});
// 注册关闭钩子
nitroApp.hooks.hook('close', async () => {
// 执行清理操作
});
// 注册错误钩子
nitroApp.hooks.hook('error', async (error, event) => {
// 处理错误
});
以下是 Multi-Lane Manager 中服务注册插件的简化版本:
// nitro-plugin.ts
import { getConfig, getGlobalState } from '../utils/config';
import { registerServiceInstance, deregisterServiceInstance } from '../utils/nacos';
import { getServerPort } from './server-utils';
export default (nitroApp) => {
// 获取配置
const config = getConfig();
const globalState = getGlobalState();
// 立即注册服务
(async () => {
try {
// 获取服务器端口
const port = getServerPort();
// 注册服务实例
const success = await registerServiceInstance(port);
if (success) {
// 设置服务器关闭时的注销逻辑
const gracefulShutdown = async () => {
await deregisterServiceInstance(port);
};
// 监听 Nitro 关闭事件
nitroApp.hooks.hook('close', async () => {
await gracefulShutdown();
});
// 监听进程退出事件
process.on('SIGINT', async () => {
await gracefulShutdown();
process.exit(0);
});
process.on('SIGTERM', async () => {
await gracefulShutdown();
process.exit(0);
});
}
} catch (error) {
console.error('服务注册错误:', error);
}
})();
// 添加健康检查路由
nitroApp.hooks.hook('request', async (event) => {
if (event.path === '/api/lane-manager/health') {
// 返回健康状态信息
event.node.res.end(JSON.stringify({
status: 'ok',
serviceName: config.serviceName,
laneId: config.currentLaneId,
timestamp: new Date().toISOString()
}));
}
});
};
- 模块化设计:将插件代码分解为多个专门的函数,提高可维护性
- 错误处理:使用 try/catch 捕获异常,避免插件错误导致服务器崩溃
- 资源清理:在服务器关闭时释放资源,如关闭连接、取消定时器等
- 日志记录:使用日志系统记录插件的运行状态和错误信息
- 类型安全:使用 TypeScript 类型定义,提高代码质量和开发体验
调试 Nitro 插件可以使用以下方法:
日志输出:在插件中添加详细的日志输出,记录插件的执行流程和状态
logger.info('Nitro 插件初始化');
logger.debug('配置:', config);
环境变量:使用环境变量控制插件的行为,如启用/禁用功能、设置日志级别等
if (process.env.DEBUG_NITRO_PLUGIN === 'true') {
logger.level = 'debug';
}
健康检查端点:添加健康检查端点,返回插件的状态信息
nitroApp.hooks.hook('request', (event) => {
if (event.path === '/api/plugin-status') {
event.node.res.end(JSON.stringify({
status: 'running',
uptime: process.uptime(),
memory: process.memoryUsage(),
// 其他状态信息
}));
}
});
错误监控:捕获并记录插件中的错误,便于排查问题
try {
// 插件代码
} catch (error) {
logger.error('插件错误:', error);
// 可以将错误发送到监控系统
}
在 nuxt.config.ts
中可以配置以下选项:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['multi-lane-manager/nuxt'],
multiLaneManager: {
// 是否启用泳道功能
isLaneEnabled: true,
// 当前泳道 ID
currentLaneId: 'dev-lane',
// 服务名称
serviceName: 'your-service-name',
// Nacos 服务器地址
nacosServerAddr: 'localhost',
// Nacos 服务器端口
nacosServerPort: '8848',
// Nacos 命名空间
nacosNamespace: 'public',
// Nacos 分组名称
nacosGroupName: 'DEFAULT_GROUP',
// 主机名
host: 'localhost',
// 目标泳道请求头键名
targetLaneHeaderKey: 'x-target-lane',
// 代理请求超时时间(毫秒)
proxyTimeout: 15000,
// 注册请求超时时间(毫秒)
registrationTimeout: 5000,
// 心跳间隔(毫秒)
heartbeatInterval: 6000,
// 实例过期时间(毫秒)
instanceTtl: 10000
}
## 多泳道部署示例
### 部署多个泳道实例
```bash
# 启动 dev-lane 泳道实例
NITRO_PORT=3001 LANE_ENABLE=true LANE_ID=dev-lane SERVICE_NAME=your-service npm run dev
# 启动 test-lane 泳道实例
NITRO_PORT=3002 LANE_ENABLE=true LANE_ID=test-lane SERVICE_NAME=your-service npm run dev
### 测试跨泳道请求
```bash
# 向 dev-lane 发送请求,但目标泳道为 test-lane
curl -H "X-Lane-ID: test-lane" http://localhost:3001/api/some-endpoint
# 向 test-lane 发送请求,但目标泳道为 dev-lane
curl -H "X-Lane-ID: dev-lane" http://localhost:3002/api/some-endpoint
# 不指定泳道ID的请求(将尝试路由到 baseline 泳道)
curl http://localhost:3001/api/some-endpoint
多泳道管理器引入了 "baseline" 泳道的概念,作为默认的基准泳道:
- 默认泳道ID:如果应用启用了泳道功能但没有设置泳道ID,则默认使用 "baseline" 作为泳道ID
-
智能路由:当收到一个没有指定泳道ID的请求时:
- 首先查询 Nacos 是否有 "baseline" 泳道的实例
- 如果有,则将请求路由到 "baseline" 泳道的实例
- 如果没有,则在当前实例处理请求
这种设计有以下优势:
- 提供了一个稳定的基准环境(baseline)
- 未指定泳道的请求默认使用基准环境,确保一致的用户体验
- 当基准环境不可用时,自动降级到当前实例处理,提高系统可用性
-
服务注册失败
- 检查 Nacos 服务器是否正常运行
- 检查环境变量是否正确设置
- 检查网络连接是否正常
-
心跳发送失败
- 检查 Nacos 服务器是否正常运行
- 检查网络连接是否正常
- 检查服务实例是否已注册
-
跨泳道请求转发失败
- 检查目标泳道的服务实例是否存在
- 检查目标泳道的服务实例是否健康
- 检查网络连接是否正常
-
实例故障转移
- 系统会自动检测实例故障并进行故障转移
- 故障实例会被加入黑名单,避免重复请求
- 黑名单实例会在30秒后自动恢复检测
- 可通过日志查看故障转移过程
multi-lane-manager 内置了完善的故障转移机制来处理发版时的实例不可用问题:
系统会自动检测以下类型的故障:
-
连接错误:
ECONNREFUSED
、ENOTFOUND
、ETIMEDOUT
- HTTP错误:502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout
-
请求超时:
ECONNABORTED
- 实例排序:按心跳时间对实例进行排序,最近心跳的实例优先
- 主实例请求:首先尝试请求选定的目标实例
- 故障检测:如果请求失败且符合故障转移条件,记录故障
- 实例选择:从同泳道的其他健康实例中选择备用实例
- 重试请求:自动转发到备用实例
- 黑名单管理:连续故障的实例会被加入黑名单
const FAILURE_CONFIG = {
MAX_RETRY_ATTEMPTS: 2, // 最大重试次数
FAILURE_THRESHOLD: 3, // 故障阈值(连续失败次数)
BLACKLIST_DURATION: 30000, // 黑名单持续时间(30秒)
CONNECTION_TIMEOUT: 5000, // 连接超时时间(5秒)
RETRY_DELAY: 1000, // 重试延迟(1秒)
};
- 优雅下线:在发版时确保旧实例能够优雅地从Nacos注销
- 健康检查:配置合适的健康检查端点
- 监控告警:监控故障转移频率,及时发现问题
- 日志分析:通过日志分析故障原因和转移效果
为了提高服务可用性,multi-lane-manager 实现了基于心跳时间的实例排序机制:
-
心跳时间戳记录:每次发送心跳时,在元数据中记录时间戳
metadata: { lastHeartbeat: "2024-01-15T10:30:00.000Z", // ISO格式时间戳 heartbeatTimestamp: "1705312200000", // 毫秒时间戳 laneId: "prod-lane" }
-
实例排序:查询实例时按心跳时间降序排列(最近心跳的在前)
-
优先选择:负载均衡时优先选择最近心跳的实例
-
故障转移:故障转移时也优先选择最近心跳的备用实例
- 提高可用性:优先选择最活跃的实例,减少请求失败率
- 快速故障检测:心跳时间较老的实例可能即将下线
- 平滑发版:新实例的心跳时间更新,会被优先选择
- 自动恢复:实例恢复后心跳时间更新,重新获得优先级
// 获取实例时启用心跳时间排序(默认启用)
const instances = await getNacosLaneInstances(serviceName, laneId, true);
// 禁用心跳时间排序
const instances = await getNacosLaneInstances(serviceName, laneId, false);
启用调试模式时,可以查看实例的心跳时间排序结果:
📊 实例心跳时间排序结果:
1. 192.168.1.10:3000 - 心跳时间: 2024-01-15T10:30:00.000Z
2. 192.168.1.11:3000 - 心跳时间: 2024-01-15T10:29:55.000Z
3. 192.168.1.12:3000 - 心跳时间: 2024-01-15T10:29:50.000Z
模块提供了详细的日志系统,可以通过环境变量 LOG_LEVEL
或 LANE_LOG_LEVEL
控制日志输出级别:
# 设置日志级别
LOG_LEVEL=info # 可选值: silent, fatal, error, warn, info, success, debug, trace, verbose
支持的日志级别(从低到高):
-
silent
:不输出任何日志 -
fatal
:只输出致命错误 -
error
:输出错误信息 -
warn
:输出警告和错误信息 -
info
:输出重要信息、警告和错误(默认级别) -
success
:输出成功信息和 info 级别以上的日志 -
debug
:输出调试信息和 info 级别以上的日志 -
trace
:输出跟踪信息和 debug 级别以上的日志 -
verbose
:输出所有详细日志
日志格式示例:
[泳道管理] [INFO] 服务器中间件初始化: 启用状态=true, 当前泳道ID=dev-lane
[泳道管理] [DEBUG] 从环境变量获取服务器端口: 3000
[泳道管理] [ERROR] 服务注册错误: 连接超时
在开发环境中,建议使用 debug
或 trace
级别以获取更详细的信息;在生产环境中,建议使用 info
或 warn
级别以减少日志量。
// 服务实例信息
interface ServiceInstanceInfo {
ip: string;
port: number;
serviceName: string;
clusterName: string;
ephemeral: boolean;
metadata: {
laneId: string;
[key: string]: string;
};
status: "UP" | "DOWN" | "UNKNOWN";
lastHeartbeat: number | string;
version?: string;
healthy?: boolean;
}
// 泳道信息
interface LaneInfo {
id: string;
instances: ServiceInstanceInfo[];
}
// 泳道管理器配置
interface LaneManagerConfig {
// Nacos 服务器配置
nacosServerAddr: string;
nacosServerPort: string;
nacosUrl: string;
nacosNamespace: string;
nacosGroupName: string;
// 服务配置
serviceName: string;
currentLaneId: string;
host: string;
port: number | null;
// 泳道配置
targetLaneHeaderKey: string; // 小写形式
isLaneEnabled: boolean;
// 超时配置
proxyTimeout: number;
registrationTimeout: number;
heartbeatInterval: number; // 心跳间隔,单位毫秒
instanceTtl: number; // 实例过期时间,单位毫秒
// 元数据
metadata: Record<string, any>;
}
// 日志选项
interface LoggerOptions {
prefix?: string;
level?: number | string;
timestamps?: boolean;
}
// 创建服务器中间件
function createServerMiddleware(): (event: H3Event) => Promise<void>;
// 注册服务实例
function registerServiceInstance(port: number): Promise<boolean>;
// 注销服务实例
function deregisterServiceInstance(port: number): Promise<boolean>;
// 获取指定泳道的服务实例
function getNacosLaneInstances(serviceName: string, targetLaneId: string): Promise<ServiceInstanceInfo[]>;
// 获取服务器端口
function getServerPort(): number;
// 创建日志管理器
function createLogger(options?: LoggerOptions): ConsolaInstance;
// 获取配置
function getConfig(): LaneManagerConfig;
// 更新配置端口
function updateConfigPort(port: number): void;
packages/multi-lane-manager/
├── src/
│ ├── index.ts # 主入口文件
│ ├── types.ts # 类型定义
│ ├── module.ts # Nuxt 模块定义
│ ├── runtime/
│ │ ├── nitro-plugin.ts # Nitro 插件(服务注册和健康检查)
│ │ ├── server-middleware.ts # 服务器中间件(请求转发)
│ │ └── server-utils.ts # 服务器工具函数
│ └── utils/
│ ├── config.ts # 配置管理
│ ├── logger.ts # 日志管理
│ ├── nacos.ts # Nacos 服务管理
│ └── proxy.ts # 请求转发
└── README.md # 文档
Nuxt 模块定义文件,负责注册 Nitro 插件和服务器中间件。
// module.ts
export default defineNuxtModule({
meta: {
name: "multi-lane-manager",
configKey: "multiLaneManager",
},
setup(_options, nuxt) {
// 添加 Nitro 插件
nuxt.hook('nitro:config', (nitroConfig) => {
nitroConfig.plugins = nitroConfig.plugins || [];
nitroConfig.plugins.push(resolve('./runtime/nitro-plugin'));
});
// 添加服务器中间件
addServerHandler({
handler: resolve('./runtime/server-middleware'),
middleware: true,
});
},
});
Nitro 插件文件,负责服务注册、心跳维持和健康检查。
// nitro-plugin.ts
export default (nitroApp) => {
// 立即注册服务
(async () => {
try {
const port = getServerPort();
await registerServiceInstance(port);
// 设置优雅下线
} catch (error) {
// 错误处理
}
})();
// 添加健康检查路由
nitroApp.hooks.hook('request', (event) => {
if (event.path === '/api/lane-manager/health') {
// 返回健康状态
}
});
};
服务器中间件文件,负责跨泳道请求转发。
// server-middleware.ts
import { createServerMiddleware } from './server-utils';
// 导出中间件函数
export default createServerMiddleware();
Nacos 服务管理文件,负责服务注册、注销、心跳维持和服务发现。
// nacos.ts
export async function registerServiceInstance(port: number): Promise<boolean> {
// 向 Nacos 注册服务实例
}
export async function deregisterServiceInstance(port: number): Promise<boolean> {
// 从 Nacos 注销服务实例
}
export async function sendHeartbeat(port: number): Promise<boolean> {
// 向 Nacos 发送心跳
}
export async function getNacosLaneInstances(
serviceName: string,
targetLaneId: string
): Promise<ServiceInstanceInfo[]> {
// 从 Nacos 获取指定泳道的服务实例
}
ISC