multi-lane-manager
Version:
Nacos 泳道管理与请求路由组件
1,574 lines (1,205 loc) • 51.9 kB
Markdown
# 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