KOA技术分享

专注 Koa.js 框架的编程知识分享

Koa.js微服务架构与容器化部署实战

引言

随着业务规模的增长,单体应用往往会面临扩展性、可维护性和部署效率等问题。将 Koa.js 应用拆分为微服务架构,并结合容器化部署,可以有效解决这些问题,实现更灵活的系统扩展。

微服务架构设计

Koajs 微服务的典型拆分策略:

服务名称 职责 技术栈
api-gateway 请求路由、认证、限流 Koa + Kong
user-service 用户管理、认证授权 Koa + JWT
order-service 订单处理、状态管理 Koa + Redis
product-service 商品管理、库存 Koa + MySQL
notification-service 消息推送、通知 Koa + RabbitMQ

服务通信实现

微服务间的通信机制:

// 服务注册与发现
class ServiceRegistry {
  constructor(etcdHost) {
    this.client = new Etcd3({ hosts: etcdHost });
    this.servicePrefix = '/services/';
  }

  // 服务注册
  async register(serviceName, instanceId, host, port, metadata = {}) {
    const key = `${this.servicePrefix}${serviceName}/${instanceId}`;
    const value = JSON.stringify({
      host,
      port,
      metadata,
      registeredAt: Date.now()
    });

    // 服务注册 TTL 30秒
    await this.client.put(key)
      .value(value)
      .ttl(30)
      .end();

    // 保持心跳
    this.keepAlive(serviceName, instanceId, host, port, metadata);
  }

  // 服务发现
  async discover(serviceName) {
    const key = `${this.servicePrefix}${serviceName}`;
    const instances = await this.client.getAll()
      .prefix(key)
      .strings();

    return Object.entries(instances).map(([k, v]) => ({
      ...JSON.parse(v),
      key: k
    })).filter(i => Date.now() - i.registeredAt < 60000);
  }

  // 负载均衡选择
  async selectInstance(serviceName) {
    const instances = await this.discover(serviceName);
    if (instances.length === 0) {
      throw new Error(`No available instances for ${serviceName}`);
    }

    // 简单轮询
    const index = this.roundRobin[serviceName] = (this.roundRobin[serviceName] || 0) + 1;
    return instances[index % instances.length];
  }
}

// HTTP 客户端封装
class ServiceClient {
  constructor(serviceRegistry) {
    this.registry = serviceRegistry;
    this.httpClient = axios.create({
      timeout: 5000,
      httpAgent: new http.Agent({ keepAlive: true })
    });
  }

  // 调用远程服务
  async call(serviceName, path, options = {}) {
    const instance = await this.registry.selectInstance(serviceName);
    const url = `http://${instance.host}:${instance.port}${path}`;

    try {
      const response = await this.httpClient.request({
        url,
        method: options.method || 'GET',
        data: options.body,
        headers: {
          'X-Request-ID': options.requestId || uuid(),
          'X-Correlation-ID': options.correlationId,
          ...options.headers
        }
      });
      return response.data;
    } catch (error) {
      console.error(`Service call failed: ${serviceName}${path}`, error.message);
      throw error;
    }
  }

  // 带重试的调用
  async callWithRetry(serviceName, path, options = {}) {
    const maxRetries = options.retries || 3;
    let lastError;

    for (let i = 0; i < maxRetries; i++) {
      try {
        return await this.call(serviceName, path, options);
      } catch (error) {
        lastError = error;
        if (error.response?.status === 500 && i < maxRetries - 1) {
          await this.delay(Math.pow(2, i) * 100); // 指数退避
        }
      }
    }

    throw lastError;
  }

  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Docker容器化配置

为每个微服务编写 Dockerfile:

# Dockerfile 基础镜像
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装生产依赖
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
USER nodejs

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"

# 启动命令
CMD ["node", "src/index.js"]

---
# docker-compose.yml 用于本地开发
version: '3.8'

services:
  user-service:
    build: ./user-service
    ports:
      - "3001:3000"
    environment:
      - NODE_ENV=development
      - PORT=3000
      - DB_HOST=localhost
      - REDIS_HOST=redis
    depends_on:
      - redis
      - mysql
    volumes:
      - ./user-service:/app
      - /app/node_modules
    networks:
      - microservice-network

  order-service:
    build: ./order-service
    ports:
      - "3002:3000"
    environment:
      - NODE_ENV=development
      - PORT=3000
      - DB_HOST=mysql
      - REDIS_HOST=redis
      - RABBITMQ_HOST=rabbitmq
    depends_on:
      - redis
      - mysql
      - rabbitmq
    volumes:
      - ./order-service:/app
      - /app/node_modules
    networks:
      - microservice-network

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - microservice-network

  mysql:
    image: mysql:8
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=microservices
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - microservice-network

networks:
  microservice-network:
    driver: bridge

volumes:
  mysql-data:

Kubernetes 部署配置

Kubernetes 部署和服务配置:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
        version: v1
    spec:
      containers:
      - name: user-service
        image: myregistry/user-service:v1.0.0
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: microservice-config
              key: db-host
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: production
spec:
  selector:
    app: user-service
  ports:
  - name: http
    port: 80
    targetPort: 3000
  type: ClusterIP

---
# hpa.yaml (水平 Pod 自动扩缩容)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: user-service-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

服务网格集成

使用 Istio 实现服务治理:

# Istio VirtualService - 流量管理
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: order-service
  namespace: production
spec:
  hosts:
  - order-service
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: order-service
        subset: v2
      weight: 100
  - route:
    - destination:
        host: order-service
        subset: v1
      weight: 90
    - destination:
        host: order-service
        subset: v2
      weight: 10

---
# Istio DestinationRule - 熔断配置
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service
  namespace: production
spec:
  host: order-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: UPGRADE
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    circuitBreaker:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

---
# Istio AuthorizationPolicy - 授权
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: order-service-auth
  namespace: production
spec:
  selector:
    matchLabels:
      app: order-service
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/production/sa/user-service"]
    - source:
        principals: ["cluster.local/ns/production/sa/product-service"]

总结

Koa.js 微服务架构与容器化部署的核心价值:

通过微服务架构和容器化部署,可以构建更加灵活、可扩展的 Koa.js 应用系统。

← 下一篇:Koa.js云原生可观测性实践与监控告警