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 传递 TraceIdMDC.put("traceId", UUID.randomUUID().toString());正确的拆分姿势
1. 优先拆分数据
-- 每个微服务有独立的数据库user-service -> user_dborder-service -> order_dbproduct-service -> product_db2. 渐进式拆分
阶段1: 斯特拉斯堡阶段(Strangler)├── 保留单体应用├── 新功能用新服务├── 逐步迁移旧功能
阶段2: 绞杀榕阶段(Strangler Fig)└── 单体应用逐渐缩小,最终移除3. 基础设施先行
├── 统一配置中心 (Nacos/Apollo)├── 服务注册发现 (Nacos/Consul)├── 统一网关 (Spring Cloud Gateway)├── 链路追踪 (SkyWalking)├── 日志聚合 (ELK)└── 熔断限流 (Sentinel/Hystrix)总结
| 阶段 | 建议 | 团队规模 |
|---|---|---|
| 初期 | 单体更香,不要拆 | <5人 |
| 中期 | 优先拆分边缘服务 | 5-10人 |
| 后期 | 按业务能力拆分 | >10人 |
TIP微服务拆分的最佳时机:团队规模 > 10 人,单体应用确实无法满足需求。