在现代 Web 开发中,构建高并发、高可用的 API 网关是一个常见的需求。本文将介绍如何结合 Docker 和 PM2 构建一个高性能的 Node.js API 网关,并深入探讨分布式限流器的原理与实现。
1. 背景与需求
1.1 高并发 API 网关的挑战
在高并发场景下,API 网关需要具备以下能力:
- 高可用性:服务崩溃时能自动恢复。
- 高性能:充分利用多核 CPU 处理请求。
- 限流与防护:防止恶意请求压垮服务。
- 环境一致性:开发、测试、生产环境行为一致。
1.2 技术选型
- Node.js:轻量、高效,适合 I/O 密集型任务。
- PM2:Node.js 进程管理工具,支持集群模式和自动重启。
- Docker:容器化部署,确保环境一致性。
- Redis:分布式限流器的共享存储。
2. 实现步骤
2.1 项目结构
api-gateway/
├── src/
│ ├── server.js # 主入口
│ ├── auth.js # 鉴权逻辑
│ ├── rate-limiter.js # 限流逻辑
│ └── routes/ # 路由配置
├── ecosystem.config.js # PM2 配置文件
├── Dockerfile # Docker 构建文件
└── package.json
2.2 核心代码实现
2.2.1 高性能服务器 (server.js)
const express = require('express');
const auth = require('./auth');
const rateLimiter = require('./rate-limiter');const app = express();
app.use(express.json());// 全局中间件
app.use(rateLimiter); // 限流
app.use(auth); // 鉴权// 路由配置
app.use('/user', require('./routes/user'));
app.use('/order', require('./routes/order'));// 健康检查端点
app.get('/health', (req, res) => {res.status(200).json({ status: 'ok', pid: process.pid });
});const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {console.log(`API Gateway running on port ${PORT} (PID: ${process.pid})`);
});// 优雅关闭
process.on('SIGTERM', () => {server.close(() => {console.log('Process terminated');});
});
2.2.2 分布式限流器 (rate-limiter.js)
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');// 创建 Redis 客户端
const redisClient = redis.createClient({host: 'localhost', // Redis 地址port: 6379 // Redis 端口
});// 限流配置
const limiter = rateLimit({windowMs: 1000, // 时间窗口:1秒max: 100, // 每个客户端在窗口内最多允许100次请求message: 'Too many requests, please try again later.', // 超限时的响应消息standardHeaders: true, // 返回标准 HTTP 限流头(X-RateLimit-*)store: new RedisStore({client: redisClient, // 使用 Redis 存储计数expiry: 60, // 计数器的过期时间(秒)prefix: 'rate-limit:' // Redis 键前缀})
});module.exports = limiter;
2.3 PM2 集群配置 (ecosystem.config.js)
module.exports = {apps: [{name: 'api-gateway',script: './src/server.js',instances: 'max', // 根据 CPU 核心数启动进程exec_mode: 'cluster', // 集群模式max_memory_restart: '1G',// 内存超限自动重启env: {NODE_ENV: 'production',REDIS_HOST: 'redis-host' // Redis 地址},log_date_format: 'YYYY-MM-DD HH:mm Z',error_file: '/dev/stdout', // 日志输出到标准输出out_file: '/dev/stdout'}]
};
2.4 Docker 容器化配置 (Dockerfile)
FROM node:18-alpine# 安装构建依赖
RUN apk add --no-cache python3 make g++WORKDIR /app
COPY package*.json ./# 生产依赖安装(包含PM2)
RUN npm install --production && \npm install pm2 -gCOPY . .# 使用 PM2 专属容器运行时
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
3. 部署与扩展
3.1 构建并启动容器
# 构建镜像
docker build -t api-gateway .# 运行容器(限制资源)
docker run -d \--name gateway \-p 3000:3000 \--cpus 4 \ # 限制使用4核 CPU--memory 4g \ # 限制4GB内存--network my-network \api-gateway
3.2 水平扩展(Kubernetes 示例)
apiVersion: apps/v1
kind: Deployment
metadata:name: api-gateway
spec:replicas: 8 # 启动8个Podselector:matchLabels:app: gatewaytemplate:metadata:labels:app: gatewayspec:containers:- name: gatewayimage: api-gateway:latestresources:limits:cpu: "2" # 每个Pod限制2核memory: 2Giports:- containerPort: 3000
4. 分布式限流器的原理
4.1 核心原理
分布式限流器通过共享存储(如 Redis)在多个服务实例之间同步请求计数,从而实现对请求的全局限流。其核心思想是:
- 共享存储:使用 Redis 记录请求计数。
- 全局计数:所有服务实例共享同一个计数器。
- 时间窗口:基于时间窗口(如每秒、每分钟)统计请求数。
4.2 工作流程
- 客户端发起请求。
- 服务端检查 Redis 中该客户端的请求计数:
- 如果计数未超限,允许请求并通过 Redis 增加计数。
- 如果计数超限,拒绝请求并返回
429 Too Many Requests
。
- Redis 自动清理过期计数(基于 TTL)。
5. 总结
通过结合 Docker 和 PM2,我们可以构建一个高性能、高可用的 Node.js API 网关,并利用分布式限流器保护服务不被过多请求压垮。这种架构不仅适用于高并发场景,还能显著提升服务的可维护性和可扩展性。
关键优势
- 高可用性:PM2 自动重启崩溃的进程,Docker 提供容器级恢复。
- 高性能:PM2 集群模式充分利用多核 CPU。
- 全局一致性:分布式限流器确保限流策略全局一致。
- 环境一致性:Docker 镜像保证开发、测试、生产环境行为一致。