第 8 章:微服务拆分
学习目标
- 掌握 4 种主流拆分方法(业务、领域、DDD、组织)
- 学会按业务能力优先级排定拆分顺序
- 理解绞杀者模式和修缮模式
- 避开拆分粒度过细、过早拆分的坑
一、4 种拆分方法
1. 按业务能力拆分
text
电商业务能力
├── 用户管理(注册/登录/资料)
├── 商品管理(上架/分类)
├── 交易管理(购物车/订单)
├── 支付管理(收款/退款)
├── 物流管理(发货/配送)
└── 营销管理(优惠券/促销)java
// 一个业务能力 = 一个服务
public class TradeService {
public Cart createCart(UserId userId) { ... }
public OrderId submitOrder(Cart cart) { ... }
}2. 按子域拆分(DDD 拆分)
text
核心子域
├── 商品(定价/库存)
└── 订单(下单/支付)
支撑子域
├── 物流(发货/配送)
└── 客服(投诉/售后)
通用子域
├── 认证
└── 通知3. 按组织拆分(康威定律)
text
用户团队 → 用户服务
商品团队 → 商品服务
订单团队 → 订单服务
基础设施组 → 通用组件4. 按技术能力拆分
text
├── 高 QPS 模块独立(秒杀)
├── 慢查询模块独立(报表)
└── 计算密集型独立(推荐)⚠️ 坑 1:按"数据表"拆,出现
UserService、OrderService、AddressService、CouponService— 后期 50 个服务互相调用,链路长到失控。按业务能力拆,不是按表。
二、拆分顺序:从易到难
text
阶段 1:基础设施独立
├── 认证
├── 通知
└── 配置中心
阶段 2:通用模块独立
├── 用户
├── 权限
└── 文件
阶段 3:核心业务独立
├── 商品
├── 订单
└── 支付
阶段 4:营销与扩展
├── 优惠券
├── 推荐
└── 搜索java
// 阶段 1:认证服务独立
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/login")
public TokenDto login(@RequestBody LoginRequest req) {
return authService.login(req.username(), req.password());
}
}先拆不会引入分布式事务的模块(用户、认证),最后拆事务重的(订单、支付)。
⚠️ 坑 2:第一个就拆"订单+支付+库存",三大分布式事务叠在一起,直接放弃。先拆没"强一致性"的,后拆"必须强一致"的。
三、绞杀者模式(Strangler Fig)
逐步替换旧单体,而不是一次性重写:
text
阶段 1:100% 单体
[
旧单体
]
阶段 2:新功能走新服务
[
旧单体 ]
用户服务(新)
]
阶段 3:逐步迁移
[
旧单体 ]
用户服务
商品服务 ← API 网关分流
]
阶段 4:旧单体萎缩到 0
[
用户服务
商品服务
订单服务
]java
// API 网关分流
spring:
cloud:
gateway:
routes:
- id: user_route
uri: lb://user-service
predicates:
- Path=/api/user/**
- id: old_route
uri: lb://legacy-monolith
predicates:
- Path=/api/old/**⚠️ 坑 3:"大爆炸(Big Bang)"重写,从 0 写新系统替换旧系统,几乎一定失败。IBM、Microsoft、淘宝都失败过。绞杀者模式渐进迁移。
四、修缮模式
不去替换旧堡垒,在旁边建新房:
java
// 旧系统继续维护
@Service
public class LegacyOrderService {
public void placeOrder(OrderDto dto) { ... }
}
// 新功能建新房
@Service
public class NewOrderService {
public void placeOrder(OrderDto dto) {
// 1. 调旧接口下单
legacyOrderService.placeOrder(dto);
// 2. 调用新的库存服务
inventoryService.deduct(dto.items());
// 3. 发送新事件
publisher.publishEvent(new OrderPlacedV2Event(dto));
}
}五、识别依赖关系
准备拆分前,画依赖图:
text
[订单] ──→ [商品]
↓
[支付] ──→ [用户]
↓
[库存] ──→ [商品]java
// 工具:用代码扫描依赖
public class DependencyAnalyzer {
public Map<String, Set<String>> analyze(String projectPath) {
// 扫描 @Resource / @Autowired / @FeignClient
// 输出每个 class 被哪些引用
}
}⚠️ 坑 4:依赖分析发现"订单"被 30 个类依赖,先抽调用方再抽被调方。先拆依赖少的,后拆依赖多的。
六、拆分后的常见问题
java
// 问题 1:分布式事务
// 单体 @Transactional 就行,跨服务需要 Saga/TCC/Seata
// 问题 2:跨服务查询
// 原 SQL JOIN 失效,改成 API 编排
public OrderDetailDto getOrderDetail(OrderId id) {
Order order = orderClient.findById(id);
User user = userClient.findById(order.buyerId());
List<OrderItem> items = order.items();
return new OrderDetailDto(order, user, items);
}
// 问题 3:数据一致性
// 只能最终一致,需要补偿机制(对账、重试)本章小结
| 方法 | 适用场景 |
|---|---|
| 业务能力 | 业务清晰,标准能力 |
| 子域(DDD) | 复杂业务,需要长期演进 |
| 组织 | 团队规模大,边界清晰 |
| 技术能力 | 性能瓶颈模块 |
| 模式 | 策略 |
|---|---|
| 绞杀者 | 渐进替换,新旧并存 |
| 修缮 | 保留旧系统,边上建新 |
| 大爆炸 | ❌ 几乎必定失败 |
动手练习
- 画依赖图:用工具扫描你手头的项目,画出"模块 → 模块"调用图
- 拆分演示:把"用户管理"独立成一个服务,API 网关分流旧路径
- 绞杀者:把一个老接口
/api/oldOrder切到新服务/api/order,观察过渡期流量 - 优先级:用上述 4 种方法各拆一次,合并去重后排出 5 个阶段
下一章:第 9 章:服务注册发现 →