为什么要拆分

三年前,我们的系统还是典型的单体应用:

  • 代码库 50万行,构建一次 20 分钟
  • 任何小改动都需要全量部署
  • 技术栈被锁定,难以引入新技术
  • 新人入职需要3个月才能上手

微服务不是银弹,但在特定场景下确实能解决问题。

拆分策略

1. 按业务能力拆分

遵循康威定律,组织架构决定系统架构:

用户服务 → 用户团队维护
订单服务 → 交易团队维护
商品服务 → 商品团队维护
支付服务 → 财务团队维护

2. 数据拆分挑战

分布式事务: 订单创建涉及订单表、库存表、支付表。

解决方案 - Saga 模式:

// 订单服务
async function createOrder(orderData) {
  // 1. 创建订单
  const order = await orderDB.create(orderData);
  
  // 2. 扣减库存(通过消息队列)
  await messageQueue.send('inventory.deduct', {
    orderId: order.id,
    items: order.items
  });
  
  // 3. 创建支付(通过消息队列)
  await messageQueue.send('payment.create', {
    orderId: order.id,
    amount: order.total
  });
}

// 补偿机制
async function compensateOrder(orderId) {
  await orderDB.cancel(orderId);
  await messageQueue.send('inventory.restore', { orderId });
}

3. 服务间通信

同步调用(REST/gRPC)

  • 实时性要求高
  • 需要立即知道结果
  • 缺点:耦合度高,级联故障风险

异步消息(MQ)

  • 最终一致性场景
  • 削峰填谷
  • 缺点:复杂度增加,需要消息追踪

我们的混合方案:

  • 查询类:同步 REST
  • 命令类:异步消息

服务治理

1. 服务发现

使用 Consul + Envoy:

# 服务注册
services:
  user-service:
    ports:
      - "8080:8080"
    environment:
      - CONSUL_HTTP_ADDR=consul:8500

2. 熔断与降级

// 熔断器配置
const breaker = new CircuitBreaker(userService.getUser, {
  failureThreshold: 5,
  timeout: 3000,
  resetTimeout: 30000
});

// 降级处理
breaker.fallback(() => ({
  id: null,
  name: '默认用户',
  cached: true
}));

3. 配置中心

集中管理配置,支持动态刷新:

# Apollo 配置
app.id: order-service
clusters: default
namespaces: application

监控与可观测性

日志聚合: ELK Stack 收集所有服务日志,统一查询。

链路追踪: Jaeger 追踪请求在各个服务的流转:

const span = tracer.startSpan('create_order');
span.setTag('order_id', orderId);
span.log({ event: 'inventory_deducted' });
span.finish();

指标监控: Prometheus + Grafana 监控 QPS、延迟、错误率。

血泪教训

1. 过早拆分 初期团队只有5人,硬拆出8个服务,维护成本爆炸。

2. 分布式事务滥用 过度追求强一致性,系统复杂度飙升。

3. 接口版本管理混乱 服务间接口频繁 breaking change,联调噩梦。

4. 缺乏自动化 手动部署、手动测试,效率极低。

成功的关键因素

  • DevOps 文化:CI/CD、自动化测试
  • 完善的监控:发现问题先于用户
  • 逐步演进:不要一次性重写
  • 团队准备:培训、工具、流程

总结

微服务不是目标,而是手段。关键问题:

  • 团队规模是否足够支撑?
  • 是否有自动化运维能力?
  • 业务复杂度是否值得拆分?

从小规模开始,逐步演进,比大爆炸式重构更可靠。