微服务设计模式实战

参与过三个微服务项目,有成功的,也有失败的。总结一些设计模式。

单体 vs 微服务

维度单体微服务
部署简单复杂
扩展整体扩展按需扩展
开发简单需要协调
故障影响可能全局局部隔离
运维成本

不要为了微服务而微服务。小团队、早期项目,单体更合适。

服务拆分

按业务能力拆分

用户服务:注册、登录、权限
订单服务:下单、支付、退款
商品服务:商品管理、库存
通知服务:消息推送

拆分原则

原则说明
高内聚相关功能放一起
松耦合服务间依赖少
独立部署改一个不影响其他
数据隔离每个服务有自己的数据库

微服务架构图

服务发现

客户端发现

服务消费者直接查询注册中心:

class ServiceClient {
  constructor(registryUrl) {
    this.registry = registryUrl;
    this.cache = new Map();
  }

  async getServiceUrl(serviceName) {
    if (this.cache.has(serviceName)) {
      return this.cache.get(serviceName);
    }

    const response = await fetch(`${this.registry}/services/${serviceName}`);
    const instances = await response.json();

    // 简单轮询
    const instance = instances[Math.floor(Math.random() * instances.length)];
    this.cache.set(serviceName, instance.url);

    return instance.url;
  }
}

服务端发现

通过网关代理:

客户端 → API 网关 → 服务实例

Nginx、Kong 都可以做。

通信模式

同步通信

REST 或 gRPC:

// 调用用户服务
const user = await fetch(`${userServiceUrl}/users/${userId}`).then(r => r.json());

简单直接,但有耦合。

异步通信

消息队列:

// 发布事件
await messageQueue.publish('user.created', {
  userId: user.id,
  email: user.email,
});

// 订阅事件
messageQueue.subscribe('user.created', async (event) => {
  await sendWelcomeEmail(event.email);
});

解耦,但调试复杂。

模式优点缺点
同步简单、实时耦合、级联故障
异步解耦、削峰复杂、调试难

API 网关

网关是微服务的入口:

# Nginx 配置
location /api/users {
  proxy_pass http://user-service;
}

location /api/orders {
  proxy_pass http://order-service;
}

location /api/products {
  proxy_pass http://product-service;
}

网关职责:

职责说明
路由请求转发
认证统一鉴权
限流保护后端
熔断故障隔离
日志请求追踪

数据一致性

分布式事务

不要用两阶段提交,太慢。用 Saga 模式:

// 订单创建 Saga
async function createOrderSaga(order) {
  const steps = [
    { execute: () => createOrder(order), compensate: () => cancelOrder(order.id) },
    { execute: () => reserveInventory(order.items), compensate: () => releaseInventory(order.items) },
    { execute: () => processPayment(order.payment), compensate: () => refundPayment(order.payment.id) },
  ];

  const executed = [];

  for (const step of steps) {
    try {
      await step.execute();
      executed.push(step);
    } catch (error) {
      // 补偿已执行的步骤
      for (const s of executed.reverse()) {
        await s.compensate();
      }
      throw error;
    }
  }
}

最终一致性

接受短暂不一致,通过事件同步:

// 发布库存变更事件
await inventoryService.decrement(productId, quantity);
await eventBus.publish('inventory.decremented', { productId, quantity });

// 订单服务监听
eventBus.subscribe('inventory.decremented', async (event) => {
  await orderService.updateStatus(event.productId, 'inventory_checked');
});

数据流

容错设计

熔断器

class CircuitBreaker {
  constructor(fn, options = {}) {
    this.fn = fn;
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 30000;
    this.state = 'CLOSED';
    this.failures = 0;
  }

  async call(...args) {
    if (this.state === 'OPEN') {
      throw new Error('Circuit is open');
    }

    try {
      const result = await this.fn(...args);
      this.failures = 0;
      return result;
    } catch (error) {
      this.failures++;
      if (this.failures >= this.failureThreshold) {
        this.state = 'OPEN';
        setTimeout(() => {
          this.state = 'HALF_OPEN';
          this.failures = 0;
        }, this.resetTimeout);
      }
      throw error;
    }
  }
}

重试

async function withRetry(fn, options = {}) {
  const maxRetries = options.maxRetries || 3;
  const delay = options.delay || 1000;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await sleep(delay * Math.pow(2, i));
    }
  }
}

降级

async function getProduct(productId) {
  try {
    return await productService.getProduct(productId);
  } catch (error) {
    // 降级:返回缓存或默认值
    return cache.get(`product:${productId}`) || defaultProduct;
  }
}

监控追踪

分布式追踪

每个请求有唯一 ID:

// 中间件生成 trace ID
app.use((req, res, next) => {
  req.traceId = req.headers['x-trace-id'] || uuid();
  res.setHeader('x-trace-id', req.traceId);
  next();
});

// 调用下游服务时传递
const response = await fetch(url, {
  headers: {
    'x-trace-id': req.traceId,
  },
});

集中式日志

traceId: abc-123 | service: user | action: getUser | userId: 456
traceId: abc-123 | service: order | action: getOrders | userId: 456

用 ELK 或 Loki 汇总日志。

总结

微服务不是银弹,增加了系统复杂度。

关键点:

  1. 服务拆分要合理
  2. 数据一致性是难点
  3. 容错设计不可少
  4. 监控追踪要跟上

小团队不建议上微服务,单体够用。