Node.js微服务架构实战:基于Express与Docker的容器化部署方案

ThickSam
ThickSam 2026-02-03T13:10:04+08:00
0 0 1

前言

在现代软件开发中,微服务架构已成为构建可扩展、可维护应用的重要模式。Node.js凭借其非阻塞I/O特性和丰富的生态系统,成为构建微服务的理想选择。本文将深入探讨如何使用Express框架搭建微服务,并通过Docker实现容器化部署,为开发者提供一套完整的微服务开发与部署解决方案。

什么是微服务架构

微服务的核心概念

微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件设计方法。每个服务:

  • 运行在自己的进程中
  • 通过轻量级通信机制(通常是HTTP API)进行交互
  • 专注于特定的业务功能
  • 可以独立部署和扩展

微服务的优势

  1. 技术多样性:不同服务可以使用不同的技术栈
  2. 可扩展性:可以根据需求单独扩展某个服务
  3. 维护性:服务相对独立,便于维护和更新
  4. 容错性:单个服务故障不会影响整个系统
  5. 团队协作:不同团队可以独立开发不同服务

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)

    0/2000