664 words
3 minutes
单体应用拆微服务:血泪教训与经验总结

拆分一时爽,运维火葬场。微服务不是银弹,且拆且珍惜

背景#

接手了一个 3 年的 Spring Boot 单体应用,代码量 30 万行,高峰期 QPS 5000+。

面临问题:

  • 代码耦合严重,改一发而动全身
  • 部署一次需要 30 分钟
  • 任何一个 bug 都可能导致全站不可用

拆分策略#

1. 按业务边界拆分#

原单体应用
├── 用户模块 (user-service)
├── 订单模块 (order-service)
├── 支付模块 (payment-service)
├── 商品模块 (product-service)
└── 通知模块 (notification-service)

2. 拆分顺序#

第一步:拆离独立的边缘服务(通知、文件)
第二步:拆离业务复杂度高的核心服务(订单、支付)
第三步:处理共享代码(用户、配置)

血泪教训#

教训1:过早拆分#

// ❌ 拆分太细,创建了大量微服务
user-service // 用户管理
user-auth-service // 用户认证
user-profile-service // 用户资料
// 结果:维护成本爆炸
// ✅ 按业务能力拆分
user-service // 用户+认证+资料 一个服务
order-service // 订单相关
WARNING

团队只有 5 个人,却维护了 20+ 个服务,平均每人负责 4 个服务,运维压力巨大。

教训2:分布式事务#

// ❌ 没有考虑分布式事务
orderService.create(order);
paymentService.pay(orderId, amount);
inventoryService.deduct(productId, quantity);
// 其中任何一个失败都会导致数据不一致
// ✅ 使用 Saga 模式
@Saga(choreography = true)
public void createOrder(Order order) {
orderService.create(order);
// 本地事务成功,发消息
eventBus.publish("order.created", order);
}

教训3:服务发现#

# ❌ 使用 HTTP 直连,IP 硬编码
http://192.168.1.100:8080/api/orders
# ✅ 使用服务发现
http://order-service/api/orders
# Spring Cloud: 使用 Ribbon/Feign 自动负载均衡

教训4:链路追踪#

// 必须引入链路追踪,否则排查问题如同大海捞针
// 推荐:SkyWalking / Jaeger / Zipkin
// MDC 传递 TraceId
MDC.put("traceId", UUID.randomUUID().toString());

正确的拆分姿势#

1. 优先拆分数据#

-- 每个微服务有独立的数据库
user-service -> user_db
order-service -> order_db
product-service -> product_db

2. 渐进式拆分#

阶段1: 斯特拉斯堡阶段(Strangler)
├── 保留单体应用
├── 新功能用新服务
├── 逐步迁移旧功能
阶段2: 绞杀榕阶段(Strangler Fig)
└── 单体应用逐渐缩小,最终移除

3. 基础设施先行#

├── 统一配置中心 (Nacos/Apollo)
├── 服务注册发现 (Nacos/Consul)
├── 统一网关 (Spring Cloud Gateway)
├── 链路追踪 (SkyWalking)
├── 日志聚合 (ELK)
└── 熔断限流 (Sentinel/Hystrix)

总结#

阶段建议团队规模
初期单体更香,不要拆<5人
中期优先拆分边缘服务5-10人
后期按业务能力拆分>10人
TIP

微服务拆分的最佳时机:团队规模 > 10 人,单体应用确实无法满足需求。