multi-lane-manager

1.5.6 • Public • Published

Multi-Lane Manager

Multi-Lane Manager 是一个 Nuxt 模块,提供基于 Nacos 的服务注册、发现和请求路由功能,用于在 Nuxt 应用中实现多泳道架构。

多泳道架构实现原理

架构概览

多泳道架构通过 Nacos 服务注册与发现和请求拦截分发两大核心机制实现,使同一服务的不同实例能够并行运行并智能路由请求。

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│                           多泳道架构实现原理                              │
│                                                                         │
├─────────────────┐                              ┌─────────────────────────┤
│                 │                              │                         │
│  Nacos服务注册   │◄──────────┐  ┌──────────────►│  请求拦截与分发          │
│                 │           │  │              │                         │
└─────────────────┘           │  │              └─────────────────────────┘
        │                     │  │                         │
        ▼                     │  │                         ▼
┌─────────────────┐           │  │              ┌─────────────────────────┐
│ 服务启动自动注册  │           │  │              │ HTTP请求拦截            │
└─────────────────┘           │  │              └─────────────────────────┘
        │                     │  │                         │
        ▼                     │  │                         ▼
┌─────────────────┐           │  │              ┌─────────────────────────┐
│ 心跳维持         │           │  │              │ 泳道ID提取              │
└─────────────────┘           │  │              └─────────────────────────┘
        │                     │  │                         │
        ▼                     │  │                         ▼
┌─────────────────┐           │  │              ┌─────────────────────────┐
│ 优雅下线         │           │  │              │ 目标实例查询            │
└─────────────────┘           │  │              └─────────────────────────┘
        │                     │  │                         │
        ▼                     │  │                         ▼
┌─────────────────┐           │  │              ┌─────────────────────────┐
│ 元数据管理       │───────────┘  └──────────────│ 请求转发                │
└─────────────────┘                             └─────────────────────────┘

1. Nacos服务注册与发现

服务注册流程

  • 应用启动时,自动向Nacos注册当前服务实例
  • 注册信息包含:服务名称、IP地址、端口、泳道ID(作为元数据)
  • 启动定时任务,定期向Nacos发送心跳,维持实例健康状态
  • 应用关闭时,自动向Nacos注销服务实例

服务发现机制

  • 根据服务名称和目标泳道ID查询Nacos服务实例
  • 支持按心跳时间排序,优先选择最近心跳的健康实例
  • 使用负载均衡策略(默认轮询)选择目标实例
  • 支持实例健康检查,只选择健康的实例

2. 请求拦截与分发

请求拦截

  • 通过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 泳道

安装步骤

1. 安装 Multi-Lane Manager 模块

在 Nuxt 项目中安装 Multi-Lane Manager 模块:

# 使用 npm
npm install multi-lane-manager

# 使用 yarn
yarn add multi-lane-manager

# 使用 pnpm
pnpm add multi-lane-manager

2. 配置 Nuxt 项目

nuxt.config.ts 文件中添加模块:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    // 其他模块...
    'multi-lane-manager/nuxt'
  ],
})

3. 配置环境变量

创建 .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  # 日志级别

4. 启动 Nacos 服务器

确保 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

5. 启动应用

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 泳道

多泳道管理器引入了 "baseline" 泳道的概念,作为默认的基准泳道:

  1. 默认泳道ID:如果应用启用了泳道功能但没有设置泳道ID,则默认使用 "baseline" 作为泳道ID
  2. 智能路由:当收到一个没有指定泳道ID的请求时:
    • 首先查询 Nacos 是否有 "baseline" 泳道的实例
    • 如果有,则将请求路由到 "baseline" 泳道的实例
    • 如果没有,则在当前实例处理请求

这种设计有以下优势:

  • 提供了一个稳定的基准环境(baseline)
  • 未指定泳道的请求默认使用基准环境,确保一致的用户体验
  • 当基准环境不可用时,自动降级到当前实例处理,提高系统可用性

生产环境部署

在生产环境中部署多泳道架构时,建议:

  1. 使用环境变量注入配置

    # 生产环境启动命令示例
    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
  2. 使用 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
  3. 配置健康检查: 确保监控系统可以检查服务的健康状态,包括 Nacos 连接状态。

  4. 设置合理的心跳间隔和过期时间: 在生产环境中,建议将心跳间隔设置为 5-10 秒,过期时间设置为心跳间隔的 2-3 倍。

容器环境部署

Docker 部署

在 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 部署

在 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

IP 地址自动检测机制

multi-lane-manager 采用完全自动化的IP检测机制,无需手动配置:

  1. 网络接口自动检测(核心逻辑)

    • 优先检测容器网络接口:eth0, eth1, ens3, ens4
    • 然后检测物理/虚拟机接口:ens160, enp0s3, en0
    • 智能过滤Docker桥接网络和虚拟接口
    • 优先选择私有网络IP(10.x.x.x, 172.16-31.x.x, 192.168.x.x)
  2. Kubernetes环境变量(备选方案)

    • 当网络接口检测失败时,使用 POD_IP 环境变量
    • 通常由Downward API自动注入
  3. 其他容器环境变量(最后备选)

    • 当前两种方法都失败时,使用 CONTAINER_IP 环境变量
  4. 智能过滤机制

    • 自动跳过内部地址(127.x.x.x)和Docker桥接网络(172.17.x.x)
    • 过滤虚拟接口(docker0, br-, veth等)
    • 优先选择私有网络IP,符合容器环境实际情况
  5. 容错机制

    • 如果所有检测方法都失败,提供详细的错误信息和解决建议
    • 仅在极端情况下才回退到localhost

容器环境最佳实践

  1. 完全自动化:无需手动配置IP地址,让系统自动检测

  2. Kubernetes Pod IP注入:使用Downward API自动注入Pod IP

    env:
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
  3. 健康检查:配置 liveness 和 readiness 探针

  4. 资源限制:设置合适的 CPU 和内存限制

  5. 日志级别:生产环境使用 info 级别,调试时使用 debug

  6. 优雅关闭:确保容器正确处理 SIGTERM 信号

  7. 网络策略:确保容器能够访问Nacos服务器

  8. 监控告警:监控服务注册状态和心跳健康

常见问题解答

1. 如何查看服务是否成功注册到 Nacos?

可以通过 Nacos 控制台查看服务注册情况,通常访问 http://nacos-server:8848/nacos/ 并登录(默认用户名/密码:nacos/nacos)。

2. 如何处理跨泳道请求失败?

检查以下几点:

  • 确保目标泳道的服务实例已注册并健康
  • 检查网络连接是否正常
  • 查看日志中的错误信息
  • 使用调试头获取详细信息(见下文)

3. 如何调试泳道请求?

可以通过添加 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 包含以下信息:

  • 请求时间和路径
  • 当前泳道和服务名称
  • 目标泳道信息(如果有)
  • 请求处理方式(本地处理或跨泳道转发)
  • 如果是跨泳道请求,还包括目标实例信息和负载均衡策略
  • 如果发生错误,包含错误信息

这对于排查泳道路由和转发问题非常有用。

4. 如何自定义请求转发逻辑?

可以通过修改 nuxt.config.ts 中的配置选项自定义请求转发逻辑:

multiLaneManager: {
  // 自定义目标泳道请求头键名
  targetLaneHeaderKey: 'x-custom-lane',

  // 自定义代理请求超时时间
  proxyTimeout: 30000,
}

5. 如何在 CI/CD 流程中集成多泳道架构?

在 CI/CD 流程中,可以为每个分支或环境配置不同的泳道 ID,例如:

  • main 分支 → prod-lane
  • develop 分支 → dev-lane
  • 特性分支 → feature-{branch-name}-lane

这样可以实现按分支或环境隔离服务实例。

第二部分:Nitro 插件编写指南

Nitro 插件基础

Nitro 插件是 Nuxt 3 中一种强大的扩展机制,允许您在服务器启动时执行代码、添加中间件、注册钩子等。Multi-Lane Manager 使用 Nitro 插件来实现服务注册和健康检查功能。

💡 提示:Nitro 插件与 Nuxt 插件不同。Nitro 插件在服务器端运行,而 Nuxt 插件可以在客户端和服务器端运行。Nitro 插件更适合处理服务器端的任务,如服务注册、数据库连接等。

Nitro 插件是一个导出默认函数的 JavaScript/TypeScript 文件,该函数接收 nitroApp 对象作为参数:

// my-nitro-plugin.ts
export default (nitroApp) => {
  // 插件代码
}

插件生命周期

Nitro 插件在服务器启动过程中被加载和执行,具体时机如下:

  1. 加载阶段:模块代码被加载到内存中
  2. 初始化阶段:插件默认导出函数被执行
  3. 运行阶段:服务器运行期间,插件注册的钩子会在相应事件发生时被调用
  4. 关闭阶段:服务器关闭时,插件可以执行清理操作

下图展示了 Nitro 插件的生命周期:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │                 │     │                 │     │                 │
│   加载阶段      │────▶│   初始化阶段    │────▶│    运行阶段     │────▶│    关闭阶段     │
│                 │     │                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘
       │                       │                       │                       │
       ▼                       ▼                       ▼                       ▼
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│ 模块代码被加载  │     │ 插件默认函数    │     │ 钩子函数被调用  │     │ 清理资源       │
│ 到内存中        │     │ 被执行          │     │ (request等)     │     │ 注销服务       │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘

注册 Nitro 插件

在 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) => {
  // 处理错误
});

最佳实践

  1. 模块化设计:将插件代码分解为多个专门的函数,提高可维护性
  2. 错误处理:使用 try/catch 捕获异常,避免插件错误导致服务器崩溃
  3. 资源清理:在服务器关闭时释放资源,如关闭连接、取消定时器等
  4. 日志记录:使用日志系统记录插件的运行状态和错误信息
  5. 类型安全:使用 TypeScript 类型定义,提高代码质量和开发体验

调试 Nitro 插件

调试 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                   # 文档

核心文件说明

1. module.ts

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,
    });
  },
});

2. runtime/nitro-plugin.ts

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') {
      // 返回健康状态
    }
  });
};

3. runtime/server-middleware.ts

服务器中间件文件,负责跨泳道请求转发。

// server-middleware.ts
import { createServerMiddleware } from './server-utils';

// 导出中间件函数
export default createServerMiddleware();

4. utils/nacos.ts

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;

工作流程

  1. 服务注册流程

    • 应用启动时,Nitro 插件自动执行
    • 获取服务器端口和配置信息
    • 向 Nacos 发送注册请求
    • 启动心跳定时器
    • 设置进程退出时的注销逻辑
  2. 心跳维持流程

    • 服务注册成功后,自动启动心跳定时器
    • 定期向 Nacos 发送心跳请求
    • 心跳间隔由 NACOS_HEARTBEAT_INTERVAL 环境变量指定,默认为 6000 毫秒(6 秒)
    • 如果心跳失败,会记录错误日志但不会停止心跳
  3. 跨泳道请求转发流程

    • 服务器中间件拦截所有请求
    • 检查请求头中是否包含目标泳道 ID
    • 如果目标泳道 ID 与当前泳道 ID 不同,则进行转发
    • 从 Nacos 获取目标泳道的服务实例
    • 使用负载均衡策略选择一个实例
    • 将请求转发到目标实例
    • 将目标实例的响应返回给客户端
  4. 优雅下线流程

    • 监听进程退出事件(SIGINT、SIGTERM、beforeExit)
    • 监听 Nitro 关闭事件
    • 在这些事件发生时,向 Nacos 发送注销请求
    • 确保服务实例被正确移除

请求转发示例

当请求头中包含 X-Target-Lane 且值与当前服务的泳道 ID 不同时,模块会自动将请求转发到目标泳道的服务实例。

例如,向当前服务发送请求,但指定目标泳道为 test-lane

curl -H "X-Target-Lane: test-lane" http://localhost:3000/api/some-endpoint

模块会:

  1. 检测到这是一个跨泳道请求
  2. 从 Nacos 获取 test-lane 泳道的服务实例
  3. 将请求转发到目标实例
  4. 将目标实例的响应返回给客户端

优雅下线

应用关闭时,模块会自动向 Nacos 发送注销请求,确保服务实例被正确移除。

高级配置

编写自定义 Nitro 插件

Nitro 插件是 Nuxt 3 中一种强大的扩展机制,允许您在服务器启动时执行代码、添加中间件、注册钩子等。Multi-Lane Manager 使用 Nitro 插件来实现服务注册和健康检查功能。

💡 提示:Nitro 插件与 Nuxt 插件不同。Nitro 插件在服务器端运行,而 Nuxt 插件可以在客户端和服务器端运行。Nitro 插件更适合处理服务器端的任务,如服务注册、数据库连接等。

Nitro 插件基础

Nitro 插件是一个导出默认函数的 JavaScript/TypeScript 文件,该函数接收 nitroApp 对象作为参数:

// my-nitro-plugin.ts
export default (nitroApp) => {
  // 插件代码
}

插件生命周期

Nitro 插件在服务器启动过程中被加载和执行,具体时机如下:

  1. 加载阶段:模块代码被加载到内存中
  2. 初始化阶段:插件默认导出函数被执行
  3. 运行阶段:服务器运行期间,插件注册的钩子会在相应事件发生时被调用
  4. 关闭阶段:服务器关闭时,插件可以执行清理操作

下图展示了 Nitro 插件的生命周期:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │                 │     │                 │     │                 │
│   加载阶段      │────▶│   初始化阶段    │────▶│    运行阶段     │────▶│    关闭阶段     │
│                 │     │                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘
       │                       │                       │                       │
       ▼                       ▼                       ▼                       ▼
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│ 模块代码被加载  │     │ 插件默认函数    │     │ 钩子函数被调用  │     │ 清理资源       │
│ 到内存中        │     │ 被执行          │     │ (request等)     │     │ 注销服务       │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘

注册 Nitro 插件

在 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()
      }));
    }
  });
};

最佳实践

  1. 模块化设计:将插件代码分解为多个专门的函数,提高可维护性
  2. 错误处理:使用 try/catch 捕获异常,避免插件错误导致服务器崩溃
  3. 资源清理:在服务器关闭时释放资源,如关闭连接、取消定时器等
  4. 日志记录:使用日志系统记录插件的运行状态和错误信息
  5. 类型安全:使用 TypeScript 类型定义,提高代码质量和开发体验

调试 Nitro 插件

调试 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 泳道

多泳道管理器引入了 "baseline" 泳道的概念,作为默认的基准泳道:

  1. 默认泳道ID:如果应用启用了泳道功能但没有设置泳道ID,则默认使用 "baseline" 作为泳道ID
  2. 智能路由:当收到一个没有指定泳道ID的请求时:
    • 首先查询 Nacos 是否有 "baseline" 泳道的实例
    • 如果有,则将请求路由到 "baseline" 泳道的实例
    • 如果没有,则在当前实例处理请求

这种设计有以下优势:

  • 提供了一个稳定的基准环境(baseline)
  • 未指定泳道的请求默认使用基准环境,确保一致的用户体验
  • 当基准环境不可用时,自动降级到当前实例处理,提高系统可用性

故障排除

常见问题

  1. 服务注册失败

    • 检查 Nacos 服务器是否正常运行
    • 检查环境变量是否正确设置
    • 检查网络连接是否正常
  2. 心跳发送失败

    • 检查 Nacos 服务器是否正常运行
    • 检查网络连接是否正常
    • 检查服务实例是否已注册
  3. 跨泳道请求转发失败

    • 检查目标泳道的服务实例是否存在
    • 检查目标泳道的服务实例是否健康
    • 检查网络连接是否正常
  4. 实例故障转移

    • 系统会自动检测实例故障并进行故障转移
    • 故障实例会被加入黑名单,避免重复请求
    • 黑名单实例会在30秒后自动恢复检测
    • 可通过日志查看故障转移过程

故障转移机制

multi-lane-manager 内置了完善的故障转移机制来处理发版时的实例不可用问题:

故障检测

系统会自动检测以下类型的故障:

  • 连接错误ECONNREFUSEDENOTFOUNDETIMEDOUT
  • HTTP错误:502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout
  • 请求超时ECONNABORTED

故障转移流程

  1. 实例排序:按心跳时间对实例进行排序,最近心跳的实例优先
  2. 主实例请求:首先尝试请求选定的目标实例
  3. 故障检测:如果请求失败且符合故障转移条件,记录故障
  4. 实例选择:从同泳道的其他健康实例中选择备用实例
  5. 重试请求:自动转发到备用实例
  6. 黑名单管理:连续故障的实例会被加入黑名单

配置参数

const FAILURE_CONFIG = {
  MAX_RETRY_ATTEMPTS: 2,           // 最大重试次数
  FAILURE_THRESHOLD: 3,            // 故障阈值(连续失败次数)
  BLACKLIST_DURATION: 30000,      // 黑名单持续时间(30秒)
  CONNECTION_TIMEOUT: 5000,       // 连接超时时间(5秒)
  RETRY_DELAY: 1000,              // 重试延迟(1秒)
};

最佳实践

  1. 优雅下线:在发版时确保旧实例能够优雅地从Nacos注销
  2. 健康检查:配置合适的健康检查端点
  3. 监控告警:监控故障转移频率,及时发现问题
  4. 日志分析:通过日志分析故障原因和转移效果

心跳时间排序机制

为了提高服务可用性,multi-lane-manager 实现了基于心跳时间的实例排序机制:

工作原理

  1. 心跳时间戳记录:每次发送心跳时,在元数据中记录时间戳

    metadata: {
      lastHeartbeat: "2024-01-15T10:30:00.000Z",  // ISO格式时间戳
      heartbeatTimestamp: "1705312200000",         // 毫秒时间戳
      laneId: "prod-lane"
    }
  2. 实例排序:查询实例时按心跳时间降序排列(最近心跳的在前)

  3. 优先选择:负载均衡时优先选择最近心跳的实例

  4. 故障转移:故障转移时也优先选择最近心跳的备用实例

优势

  • 提高可用性:优先选择最活跃的实例,减少请求失败率
  • 快速故障检测:心跳时间较老的实例可能即将下线
  • 平滑发版:新实例的心跳时间更新,会被优先选择
  • 自动恢复:实例恢复后心跳时间更新,重新获得优先级

配置选项

// 获取实例时启用心跳时间排序(默认启用)
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_LEVELLANE_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] 服务注册错误: 连接超时

在开发环境中,建议使用 debugtrace 级别以获取更详细的信息;在生产环境中,建议使用 infowarn 级别以减少日志量。

API 参考

类型定义

// 服务实例信息
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                   # 文档

核心文件说明

1. module.ts

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,
    });
  },
});

2. runtime/nitro-plugin.ts

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') {
      // 返回健康状态
    }
  });
};

3. runtime/server-middleware.ts

服务器中间件文件,负责跨泳道请求转发。

// server-middleware.ts
import { createServerMiddleware } from './server-utils';

// 导出中间件函数
export default createServerMiddleware();

4. utils/nacos.ts

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

Package Sidebar

Install

npm i multi-lane-manager

Weekly Downloads

1,604

Version

1.5.6

License

ISC

Unpacked Size

402 kB

Total Files

52

Last publish

Collaborators

  • microkernel