UNPKG

multi-lane-manager

Version:

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

1,574 lines (1,205 loc) 51.9 kB
# Multi-Lane Manager Multi-Lane Manager 是一个 Nuxt 模块,提供基于 Nacos 的服务注册、发现和请求路由功能,用于在 Nuxt 应用中实现多泳道架构。 ## 多泳道架构实现原理 ### 架构概览 多泳道架构通过 Nacos 服务注册与发现和请求拦截分发两大核心机制实现,使同一服务的不同实例能够并行运行并智能路由请求。 ```ascii ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ 多泳道架构实现原理 │ │ │ ├─────────────────┐ ┌─────────────────────────┤ │ │ │ │ │ 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错误) - 智能故障转移到其他健康实例 - 实例黑名单机制,避免重复请求故障实例 - 自动重试机制,提高请求成功率 - 实例健康状态自动恢复 ## 目录 - [第一部分:使用指南](#第一部分使用指南) - [什么是多泳道架构](#什么是多泳道架构) - [功能特性](#功能特性) - [安装步骤](#安装步骤) - [多泳道部署示例](#多泳道部署示例) - [生产环境部署](#生产环境部署) - [常见问题解答](#常见问题解答) - [第二部分:Nitro 插件编写指南](#第二部分nitro-插件编写指南) - [Nitro 插件基础](#nitro-插件基础) - [插件生命周期](#插件生命周期) - [注册 Nitro 插件](#注册-nitro-插件) - [常用钩子](#常用钩子) - [最佳实践](#最佳实践) - [调试 Nitro 插件](#调试-nitro-插件) - [第三部分:实现细节](#第三部分实现细节) - [模块结构](#模块结构) - [核心文件说明](#核心文件说明) - [类型定义](#类型定义) - [导出函数](#导出函数) ## 第一部分:使用指南 ## 什么是多泳道架构 多泳道架构是一种微服务部署模式,允许同一个服务的多个实例在不同的环境或配置下并行运行。每个"泳道"代表一个独立的服务实例集合,具有自己的配置和状态。 多泳道架构的优势: - 支持多版本并行部署 - 便于 A/B 测试和灰度发布 - 隔离测试环境和生产环境 - 支持按团队或功能划分服务实例 ## 功能特性 - 🚀 **服务自动注册**:应用启动时自动向 Nacos 注册服务 - 🔄 **心跳维持**:自动维持与 Nacos 的心跳连接 - 🔍 **服务发现**:基于泳道 ID 发现目标服务实例 - 🔀 **请求转发**:自动将请求转发到目标泳道的服务实例 - 🛑 **优雅下线**:应用关闭时自动注销服务 - 🔄 **智能路由**:未指定泳道 ID 的请求会优先路由到 baseline 泳道 ## 安装步骤 ### 1. 安装 Multi-Lane Manager 模块 在 Nuxt 项目中安装 Multi-Lane Manager 模块: ```bash # 使用 npm npm install multi-lane-manager # 使用 yarn yarn add multi-lane-manager # 使用 pnpm pnpm add multi-lane-manager ``` ### 2. 配置 Nuxt 项目`nuxt.config.ts` 文件中添加模块: ```typescript // 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 | #### 示例配置 ```env # 必须配置 - 服务标识 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 快速启动一个: ```bash 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. 启动应用 ```bash npm run dev ``` 启动后,应用将自动向 Nacos 注册服务,并开始发送心跳。 ## 多泳道部署示例 ### 部署多个泳道实例 ```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. **使用环境变量注入配置**```bash # 生产环境启动命令示例 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 管理进程**```bash # 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 # 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"] ``` 启动容器: ```bash 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 作为服务注册地址: ```yaml # 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 ```yaml 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` 头,其中包含请求处理的详细信息。 ```bash # 使用 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` 中的配置选项自定义请求转发逻辑: ```typescript 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` 对象作为参数: ```typescript // my-nitro-plugin.ts export default (nitroApp) => { // 插件代码 } ``` ### 插件生命周期 Nitro 插件在服务器启动过程中被加载和执行,具体时机如下: 1. **加载阶段**:模块代码被加载到内存中 2. **初始化阶段**:插件默认导出函数被执行 3. **运行阶段**:服务器运行期间,插件注册的钩子会在相应事件发生时被调用 4. **关闭阶段**:服务器关闭时,插件可以执行清理操作 下图展示了 Nitro 插件的生命周期: ```ascii ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ │ │ │ │ 加载阶段 │────▶│ 初始化阶段 │────▶│ 运行阶段 │────▶│ 关闭阶段 │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 模块代码被加载 │ │ 插件默认函数 │ │ 钩子函数被调用 │ │ 清理资源 │ │ 到内存中 │ │ 被执行 │ │ (request等) │ │ 注销服务 │ └─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` ### 注册 Nitro 插件 在 Nuxt 模块中注册 Nitro 插件的方法: ```typescript // 在 Nuxt 模块的 setup 函数中 nuxt.hook('nitro:config', (nitroConfig) => { // 确保 plugins 数组存在 nitroConfig.plugins = nitroConfig.plugins || []; // 添加插件 nitroConfig.plugins.push(resolve('./runtime/my-nitro-plugin')); }); ``` ### 常用钩子 Nitro 插件可以使用以下常用钩子: - **`request`**:处理每个请求 - **`close`**:服务器关闭时执行清理操作 - **`error`**:处理服务器错误 ```typescript // 注册请求钩子 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 插件可以使用以下方法: **日志输出**:在插件中添加详细的日志输出,记录插件的执行流程和状态 ```typescript logger.info('Nitro 插件初始化'); logger.debug('配置:', config); ``` **环境变量**:使用环境变量控制插件的行为,如启用/禁用功能、设置日志级别等 ```typescript if (process.env.DEBUG_NITRO_PLUGIN === 'true') { logger.level = 'debug'; } ``` **健康检查端点**:添加健康检查端点,返回插件的状态信息 ```typescript 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(), // 其他状态信息 })); } }); ``` **错误监控**:捕获并记录插件中的错误,便于排查问题 ```typescript try { // 插件代码 } catch (error) { logger.error('插件错误:', error); // 可以将错误发送到监控系统 } ``` ## 第三部分:实现细节 ### 模块结构 ```text 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 插件和服务器中间件。 ```typescript // 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 插件文件,负责服务注册、心跳维持和健康检查。 ```typescript // 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` 服务器中间件文件,负责跨泳道请求转发。 ```typescript // server-middleware.ts import { createServerMiddleware } from './server-utils'; // 导出中间件函数 export default createServerMiddleware(); ``` #### 4. `utils/nacos.ts` Nacos 服务管理文件,负责服务注册、注销、心跳维持和服务发现。 ```typescript // 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 获取指定泳道的服务实例 } ``` ### 类型定义 ```typescript // 服务实例信息 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>; } ``` ### 导出函数 ```typescript // 创建服务器中间件 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````bash 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` 对象作为参数: ```typescript // my-nitro-plugin.ts export default (nitroApp) => { // 插件代码 } ``` #### 插件生命周期 Nitro 插件在服务器启动过程中被加载和执行,具体时机如下: 1. **加载阶段**:模块代码被加载到内存中 2. **初始化阶段**:插件默认导出函数被执行 3. **运行阶段**:服务器运行期间,插件注册的钩子会在相应事件发生时被调用 4. **关闭阶段**:服务器关闭时,插件可以执行清理操作 下图展示了 Nitro 插件的生命周期: ```ascii ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ │ │ │ │ 加载阶段 │────▶│ 初始化阶段 │────▶│ 运行阶段 │────▶│ 关闭阶段 │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 模块代码被加载 │ │ 插件默认函数 │ │ 钩子函数被调用 │ │ 清理资源 │ │ 到内存中 │ │ 被执行 │ │ (request等) │ │ 注销服务 │ └─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` #### 注册 Nitro 插件 在 Nuxt 模块中注册 Nitro 插件的方法: ```typescript // 在 Nuxt 模块的 setup 函数中 nuxt.hook('nitro:config', (nitroConfig) => { // 确保 plugins 数组存在 nitroConfig.plugins = nitroConfig.plugins || []; // 添加插件 nitroConfig.plugins.push(resolve('./runtime/my-nitro-plugin')); }); ``` #### 常用钩子 Nitro 插件可以使用以下常用钩子: - **`request`**:处理每个请求 - **`close`**:服务器关闭时执行清理操作 - **`error`**:处理服务器错误 ```typescript // 注册请求钩子 nitroApp.hooks.hook('request', async (event) => { // 处理请求 }); // 注册关闭钩子 nitroApp.hooks.hook('close', async () => { // 执行清理操作 }); // 注册错误钩子 nitroApp.hooks.hook('error', async (error, event) => { // 处理错误 }); ``` #### 示例:服务注册插件 以下是 Multi-Lane Manager 中服务注册插件的简化版本: ```typescript // 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 插件可以使用以下方法: **日志输出**:在插件中添加详细的日志输出,记录插件的执行流程和状态 ```typescript logger.info('Nitro 插件初始化'); logger.debug('配置:', config); ``` **环境变量**:使用环境变量控制插件的行为,如启用/禁用功能、设置日志级别等 ```typescript if (process.env.DEBUG_NITRO_PLUGIN === 'true') { logger.level = 'debug'; } ``` **健康检查端点**:添加健康检查端点,返回插件的状态信息 ```typescript 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(), // 其他状态信息 })); } }); ``` **错误监控**:捕获并记录插件中的错误,便于排查问题 ```typescript try { // 插件代码 } catch (error) { logger.error('插件错误:', error); // 可以将错误发送到监控系统 } ``` ### 模块配置选项`nuxt.config.ts` 中可以配置以下选项: ```typescript // 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 内置了完善的故障转移机制来处理发版时的实例不可用问题: #### 故障检测 系统会自动检测以下类型的故障: - **连接错误**`ECONNREFUSED``ENOTFOUND``ETIMEDOUT` - **HTTP错误**:502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout - **请求超时**`ECONNABORTED` #### 故障转移流程 1. **实例排序**:按心跳时间对实例进行排序,最近心跳的实例优先 2. **主实例请求**:首先尝试请求选定的目标实例 3. **故障检测**:如果请求失败且符合故障转移条件,记录故障 4. **实例选择**:从同泳道的其他健康实例中选择备用实例 5. **重试请求**:自动转发到备用实例 6. **黑名单管理**:连续故障的实例会被加入黑名单 #### 配置参数 ```typescript 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. **心跳时间戳记录**:每次发送心跳时,在元数据中记录时间戳 ```typescript metadata: { lastHeartbeat: "2024-01-15T10:30:00.000Z", // ISO格式时间戳 heartbeatTimestamp: "1705312200000", // 毫秒时间戳 laneId: "prod-lane" } ``` 2. **实例排序**:查询实例时按心跳时间降序排列(最近心跳的在前) 3. **优先选择**:负载均衡时优先选择最近心跳的实例 4. **故障转移**:故障转移时也优先选择最近心跳的备用实例 #### 优势 - **提高可用性**:优先选择最活跃的实例,减少请求失败率 - **快速故障检测**:心跳时间较老的实例可能即将下线 - **平滑发版**:新实例的心跳时间更新,会被优先选择 - **自动恢复**:实例恢复后心跳时间更新,重新获得优先级 #### 配置选项 ```typescript // 获取实例时启用心跳时间排序(默认启用) const instances = await getNacosLaneInstances(serviceName, laneId, true); // 禁用心跳时间排序 const instances = await getNacosLaneInstances(serviceName, laneId, false); ``` #### 调试信息 启用调试模式时,可以查看实例的心跳时间排序结果: ```log 📊 实例心跳时间排序结果: 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` 控制日志输出级别: ```env # 设置日志级别 LOG_LEVEL=info # 可选值: silent, fatal, error, warn, info, success, debug, trace, verbose ``` 支持的日志级别(从低到高): - `silent`:不输出任何日志 - `fatal`:只输出致命错误 - `error`:输出错误信息 - `warn`:输出警告和错误信息 - `info`:输出重要信息、警告和错误(默认级别) - `success`:输出成功信息和 info 级别以上的日志 - `debug`:输出调试信息和 info 级别以上的日志 - `trace`:输出跟踪信息和 debug 级别以上的日志 - `verbose`:输出所有详细日志 日志格式示例: ```log [泳道管理] [INFO] 服务器中间件初始化: 启用状态=true, 当前泳道ID=dev-lane [泳道管理] [DEBUG] 从环境变量获取服务器端口: 3000 [泳道管理] [ERROR] 服务注册错误: 连接超时 ``` 在开发环境中,建议使用 `debug``trace` 级别以获取更详细的信息;在生产环境中,建议使用 `info``warn` 级别以减少日志量。 ## API 参考 ### 类型定义 ```typescript // 服务实例信息 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; } ``` ### 导出函数 ```typescript // 创建服务器中间件 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; ``` ### 模块结构 ```text 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 插件和服务器中间件。 ```typescript // 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 插件文件,负责服务注册、心跳维持和健康检查。 ```typescript // 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` 服务器中间件文件,负责跨泳道请求转发。 ```typescript // server-middleware.ts import { createServerMiddleware } from './server-utils'; // 导出中间件函数 export default createServerMiddleware(); ``` #### 4. `utils/nacos.ts` Nacos 服务管理文件,负责服务注册、注销、心跳维持和服务发现。 ```typescript // 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