前言
在现代软件开发中,微服务架构已成为构建可扩展、可维护应用的重要模式。Node.js凭借其非阻塞I/O特性和丰富的生态系统,成为构建微服务的理想选择。本文将深入探讨如何使用Express框架搭建微服务,并通过Docker实现容器化部署,为开发者提供一套完整的微服务开发与部署解决方案。
什么是微服务架构
微服务的核心概念
微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件设计方法。每个服务:
- 运行在自己的进程中
- 通过轻量级通信机制(通常是HTTP API)进行交互
- 专注于特定的业务功能
- 可以独立部署和扩展
微服务的优势
- 技术多样性:不同服务可以使用不同的技术栈
- 可扩展性:可以根据需求单独扩展某个服务
- 维护性:服务相对独立,便于维护和更新
- 容错性:单个服务故障不会影响整个系统
- 团队协作:不同团队可以独立开发不同服务
Node.js微服务架构设计
架构概览
在本教程中,我们将构建一个典型的电商微服务架构,包含以下核心服务:
- 用户服务(User Service)
- 产品服务(Product Service)
- 订单服务(Order Service)
- 网关服务(API Gateway)
技术选型
{
"express": "^4.18.0",
"cors": "^2.8.5",
"helmet": "^6.0.0",
"dotenv": "^16.0.0",
"axios": "^1.0.0",
"winston": "^3.7.0",
"joi": "^17.7.0"
}
Express微服务开发实践
基础服务结构搭建
首先,我们创建一个基础的Express服务模板:
// app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const dotenv = require('dotenv');
// 加载环境变量
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
// 中间件配置
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 基础路由
app.get('/', (req, res) => {
res.json({
message: 'Welcome to Microservice API',
version: '1.0.0'
});
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});
// 404处理
app.use('*', (req, res) => {
res.status(404).json({
error: 'Route not found'
});
});
module.exports = app;
用户服务实现
// services/user-service.js
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.Console()
]
});
// 模拟数据库存储
let users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 30 }
];
// 获取所有用户
router.get('/', async (req, res) => {
try {
logger.info('Fetching all users');
res.json({
success: true,
data: users,
count: users.length
});
} catch (error) {
logger.error('Error fetching users:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
// 根据ID获取用户
router.get('/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
logger.info(`Fetching user with ID: ${id}`);
res.json({
success: true,
data: user
});
} catch (error) {
logger.error('Error fetching user:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
// 创建用户
router.post('/', [
body('name').notEmpty().withMessage('Name is required'),
body('email').isEmail().withMessage('Valid email is required'),
body('age').isInt({ min: 0 }).withMessage('Age must be a positive number')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
const { name, email, age } = req.body;
const newUser = {
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
name,
email,
age
};
users.push(newUser);
logger.info(`Created new user: ${newUser.name}`);
res.status(201).json({
success: true,
data: newUser
});
} catch (error) {
logger.error('Error creating user:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
module.exports = router;
产品服务实现
// services/product-service.js
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');
// 模拟数据库存储
let products = [
{ id: 1, name: 'iPhone 14', price: 5999, category: 'Electronics' },
{ id: 2, name: 'MacBook Pro', price: 12999, category: 'Electronics' }
];
// 获取所有产品
router.get('/', async (req, res) => {
try {
const { category, minPrice, maxPrice } = req.query;
let filteredProducts = products;
if (category) {
filteredProducts = filteredProducts.filter(p => p.category === category);
}
if (minPrice) {
filteredProducts = filteredProducts.filter(p => p.price >= parseFloat(minPrice));
}
if (maxPrice) {
filteredProducts = filteredProducts.filter(p => p.price <= parseFloat(maxPrice));
}
res.json({
success: true,
data: filteredProducts,
count: filteredProducts.length
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
// 根据ID获取产品
router.get('/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);
const product = products.find(p => p.id === id);
if (!product) {
return res.status(404).json({
success: false,
message: 'Product not found'
});
}
res.json({
success: true,
data: product
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
// 创建产品
router.post('/', [
body('name').notEmpty().withMessage('Name is required'),
body('price').isFloat({ min: 0 }).withMessage('Price must be a positive number'),
body('category').notEmpty().withMessage('Category is required')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
const { name, price, category } = req.body;
const newProduct = {
id: products.length > 0 ? Math.max(...products.map(p => p.id)) + 1 : 1,
name,
price: parseFloat(price),
category
};
products.push(newProduct);
res.status(201).json({
success: true,
data: newProduct
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
module.exports = router;
Docker容器化部署
Dockerfile基础配置
# Dockerfile
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
# 启动应用
CMD ["npm", "start"]
多阶段构建优化
# Dockerfile.multi-stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:18-alpine AS runtime
WORKDIR /app
# 从builder阶段复制node_modules
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
CMD ["npm", "start"]
Docker Compose配置
# docker-compose.yml
version: '3.8'
services:
# 用户服务
user-service:
build: ./user-service
ports:
- "3001:3000"
environment:
- NODE_ENV=production
- PORT=3000
networks:
- microservice-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
# 产品服务
product-service:
build: ./product-service
ports:
- "3002:3000"
environment:
- NODE_ENV=production
- PORT=3000
networks:
- microservice-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
# 订单服务
order-service:
build: ./order-service
ports:
- "3003:3000"
environment:
- NODE_ENV=production
- PORT=3000
networks:
- microservice-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
# API网关
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- USER_SERVICE_URL=http://user-service:3000
- PRODUCT_SERVICE_URL=http://product-service:3000
- ORDER_SERVICE_URL=http://order-service:3000
networks:
- microservice-network
depends_on:
- user-service
- product-service
- order-service
restart: unless-stopped
networks:
microservice-network:
driver: bridge
微服务间通信
RESTful API调用
// utils/http-client.js
const axios = require('axios');
class HttpClient {
constructor() {
this.client = axios.create({
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
this.client.interceptors.request.use(
(config) => {
console.log(`Making request to ${config.url}`);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
this.client.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
console.error('API Error:', error.message);
return Promise.reject(error);
}
);
}
async get(url, options = {}) {
try {
const response = await this.client.get(url, options);
return response;
} catch (error) {
throw new Error(`GET request to ${url} failed: ${error.message}`);
}
}
async post(url, data, options = {}) {
try {
const response = await this.client.post(url, data, options);
return response;
} catch (error) {
throw new Error(`POST request to ${url} failed: ${error.message}`);
}
}
async put(url, data, options = {}) {
try {
const response = await this.client.put(url, data, options);
return response;
} catch (error) {
throw new Error(`PUT request to ${url} failed: ${error.message}`);
}
}
async delete(url, options = {}) {
try {
const response = await this.client.delete(url, options);
return response;
} catch (error) {
throw new Error(`DELETE request to ${url} failed: ${error.message}`);
}
}
}
module.exports = new HttpClient();
负载均衡实现
// services/order-service.js
const express = require('express');
const router = express.Router();
const axios = require('axios');
const { body, validationResult } = require('express-validator');
// 服务发现配置
const serviceRegistry = {
user: 'http://user-service:3000',
product: 'http://product-service:3000'
};
// 创建订单
router.post('/', [
body('userId').isInt({ min: 1 }).withMessage('Valid user ID is required'),
body('productId').isInt({ min: 1 }).withMessage('Valid product ID is required'),
body('quantity').isInt({ min: 1 }).withMessage('Quantity must be a positive number')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: errors.array()
});
}
const { userId, productId, quantity } = req.body;
// 获取用户信息
const userResponse = await axios.get(`${serviceRegistry.user}/users/${userId}`);
if (!userResponse.data.success) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
// 获取产品信息
const productResponse = await axios.get(`${serviceRegistry.product}/products/${productId}`);
if (!productResponse.data.success) {
return res.status(404).json({
success: false,
message: 'Product not found'
});
}
const order = {
id: Date.now(),
userId,
productId,
quantity,
price: productResponse.data.data.price * quantity,
status: 'pending',
createdAt: new Date().toISOString()
};
res.status(201).json({
success: true,
data: order
});
} catch (error) {
console.error('Error creating order:', error);
res.status(500).json({
success: false,
message: 'Internal server error'
});
}
});
module.exports = router;
API网关设计
网关核心实现
// api-gateway/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const axios = require('axios');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
const PORT = process.env.PORT || 8080;
// 中间件配置
app.use(helmet());
app.use(cors());
app.use(express.json());
// 服务端点配置
const SERVICE_ENDPOINTS = {
USER_SERVICE: process.env.USER_SERVICE_URL || 'http://localhost:3001',
PRODUCT_SERVICE: process.env.PRODUCT_SERVICE_URL || 'http://localhost:3002',
ORDER_SERVICE: process.env.ORDER_SERVICE_URL || 'http://localhost:3003'
};
// 路由转发中间件
const serviceProxy = (serviceUrl) => {
return async (req, res) => {
try {
const url = `${serviceUrl}${req.url}`;
const response = await axios({
method: req.method,
url,
data: req.body,
headers: {
'Content-Type': 'application/json',
...req.headers
}
});
res.status(response.status).json(response.data);
} catch (error) {
console.error(`Proxy error for ${req.url}:`, error.message);
if (error.response) {
res.status(error.response.status).json(error.response.data);
} else {
res.status(500).json({
error: 'Service unavailable',
message: error.message
});
}
}
};
};
// 用户服务路由
app.use('/users', serviceProxy(SERVICE_ENDPOINTS.USER_SERVICE));
// 产品服务路由
app.use('/products', serviceProxy(SERVICE_ENDPOINTS.PRODUCT_SERVICE));
// 订单服务路由
app.use('/orders', serviceProxy(SERVICE_ENDPOINTS.ORDER_SERVICE));
// 健康检查端点
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
services: {
user: SERVICE_ENDPOINTS.USER_SERVICE,
product: SERVICE_ENDPOINTS.PRODUCT_SERVICE,
order: SERVICE_ENDPOINTS.ORDER_SERVICE
}
});
});
// 根路径重定向
app.get('/', (req, res) => {
res.redirect('/health');
});
module.exports = app;
配置管理与环境变量
环境配置文件
// config/index.js
require('dotenv').config();
const config = {
// 应用配置
app: {
name: process.env.APP_NAME || 'Microservice App',
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development'
},
// 数据库配置
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME || 'microservice_db',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password'
},
// 服务配置
services: {
userServiceUrl: process.env.USER_SERVICE_URL || 'http://localhost:3001',
productServiceUrl: process.env.PRODUCT_SERVICE_URL || 'http://localhost:3002',
orderServiceUrl: process.env.ORDER_SERVICE_URL || 'http://localhost:3003'
},
// 安全配置
security: {
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['*']
}
};
module.exports = config;
Docker环境变量配置
# docker-compose.override.yml
version: '3.8'
services:
user-service:
environment:
- NODE_ENV=development
- PORT=3000
- LOG_LEVEL=debug
volumes:
- ./user-service:/app
- /app/node_modules
product-service:
environment:
- NODE_ENV=development
- PORT=3000
- LOG_LEVEL=debug
volumes:
- ./product-service:/app
- /app/node_modules
api-gateway:
environment:
- NODE_ENV=development
- PORT=8080
- USER_SERVICE_URL=http://user-service:3000
- PRODUCT_SERVICE_URL=http://product-service:3000
- ORDER_SERVICE_URL=http://order-service:3000
ports:
- "8080:8080"
监控与日志管理
日志配置实现
// utils/logger.js
const winston = require('winston');
const path = require('path');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'microservice' },
transports: [
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error'
}),
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log')
})
]
});
// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
module.exports = logger;
健康检查实现
// middleware/health-check.js
const express = require('express');
const router = express.Router();
// 基础健康检查
router.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 服务依赖健康检查
router.get('/health/dependencies', async (req, res) => {
const healthChecks = {
timestamp: new Date().toISOString(),
services: {}
};
try {
// 检查用户服务
const userResponse = await fetch('http://user-service:3000/health');
healthChecks.services.user = userResponse.ok ? 'healthy' : 'unhealthy';
// 检查产品服务
const productResponse = await fetch('http://product-service:3000/health');
healthChecks.services.product = productResponse.ok ? 'healthy' : 'unhealthy';
res.json(healthChecks);
} catch (error) {
res.status(500).json({
status: 'unhealthy',
error: error.message
});
}
});
module.exports = router;
性能优化与最佳实践
缓存策略实现
// utils/cache.js
const NodeCache = require('node-cache');
class CacheManager {
constructor() {
this.cache = new NodeCache({
stdTTL: 600, // 10分钟默认过期时间
checkperiod: 120, // 每2分钟检查一次过期
deleteOnExpire: true
});
}
get(key) {
return this.cache.get(key);
}
set(key, value, ttl = 600) {
return this.cache.set(key, value, ttl);
}
del(key) {
return this.cache.del(key);
}
flush() {
return this.cache.flushAll();
}
}
module.exports = new CacheManager();
请求限流实现
// middleware/rate-limiter.js
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 限制每个IP 100个请求
message: {
error: 'Too many requests from this IP, please try again later.'
},
standardHeaders: true,
legacyHeaders: false,
});
module.exports = limiter;
部署与运维
CI/CD流水线配置
# .github/workflows/deploy.yml
name: Deploy Microservices
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build Docker images
run: |
docker build -t user-service ./user-service
docker build -t product-service ./product-service
docker build -t order-service ./order-service
docker build -t api-gateway ./api-gateway
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: |
# 部署到服务器的命令
echo "Deploying to production..."
容器编排优化
# docker-compose.prod.yml
version: '3.8'
services:
user-service:
build: ./user-service
restart: unless-stopped
deploy:
replicas: 2
environment:
- NODE_ENV=production
- PORT=3000
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
product-service:
build: ./product-service
restart: unless-stopped
deploy:
replicas: 2
environment:
- NODE_ENV=production
- PORT=3000
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
api-gateway:
build: ./api-g
评论 (0)