微服务拆分实操:用什么原则划边界
前言
"微服务"这三个字在技术圈已经热了十多年,但"怎么拆"始终是最困扰架构师的问题。拆得太粗——换了个壳的单体;拆得太细——运维成本爆炸,调试像噩梦。
本文不讲微服务的基础概念(假设你已经知道什么是微服务),聚焦于一个核心问题:面对一个单体系统,你该用什么原则来划边界?
第一部分:单体架构的痛点与拆分的时机
1.1 单体不是原罪
在谈拆分之前,必须明确一个观点:单体架构不是原罪。很多成功的系统——包括 GitHub 早期、Stack Overflow——都是单体架构。单体的问题不在于"它是单体",而在于"它何时变得不可维护"。
一个设计良好的模块化单体,比一个拆分混乱的微服务系统好一万倍:
// 设计良好的模块化单体
com.company.app
├── order // 订单模块 - 内部高内聚
│ ├── api // 对外接口
│ └── internal // 内部实现(包级私有)
├── inventory // 库存模块
├── payment // 支付模块
└── user // 用户模块
// 模块间通过接口通信,互不依赖内部实现
1.2 单体的真正痛点
当你开始经历以下问题时,拆分才变得必要:
痛点一:协作冲突
场景:20 个开发者在同一个代码仓库工作
周一:张三改了 OrderService.java 的 calculateTotal() 方法
周二:李四也改了同一个文件,合并冲突
周三:张三的改动导致李四的单元测试失败
周四:王五的 feature 被张三和李四的改动 block 了
周五:发布日,大家都不敢合并代码了
痛点二:部署耦合
场景:订单模块的小改动需要全量部署
14:00 - 订单模块修了一个 bug,改了 3 行代码
14:05 - 提交代码,触发 CI/CD
14:30 - 构建完成(编译了全部 500 个类)
14:45 - 全量回归测试(跑了 3000 个测试用例)
15:00 - 支付模块的测试挂了!有人上午改支付导致了回归
15:30 - 修复支付模块的回归问题
16:30 - 重新构建、重新测试
17:00 - 终于上线了
—— 改 3 行代码,花了 3 小时上线
痛点三:扩展瓶颈
场景:大促期间,订单服务 CPU 飙到 95%,但库存服务只用 10%
单体架构中你只能整体扩容:
┌─────────────────────────────────┐
│ 单体应用 × 5 实例 │
│ ┌─────┐┌─────┐┌─────┐┌─────┐ │
│ │订单 ││库存 ││支付 ││用户 │ │
│ │95% ││10% ││15% ││5% │ │
│ └─────┘└─────┘└─────┘└─────┘ │
└─────────────────────────────────┘
资源大量浪费!你只能把不忙的服务也一起扩
微服务架构中:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│订单 │ │库存 │ │支付 │ │用户 │
│×10 │ │×2 │ │×3 │ │×2 │
└──────┘ └──────┘ └──────┘ └──────┘
按需扩容,资源高效利用
痛点四:技术栈锁定
单体项目一旦选定了技术栈(Java 8 + Spring Boot 2.x + MyBatis),就很难在某一个模块升级到 Java 21 或切换到 JPA。微服务允许每个服务独立选择技术栈。
1.3 拆分的真正时机
| 信号 | 说明 |
|---|---|
| 团队规模 > 10 人,多个团队在同一代码库工作 | 协作冲突频繁 |
| 不同模块有明显的差异化扩展需求 | 资源浪费严重 |
| 不同模块有不同的变更节奏 | 部署耦合导致上线缓慢 |
| 某个模块需要独立的技术栈或语言 | 技术受限 |
| 系统的某个部分需要独立的合规/安全级别 | 安全隔离需求 |
如果你只命中了一条,用模块化单体就够了,不需要拆微服务。如果你命中了三条以上,拆分才有切实的 ROI。
第二部分:拆分原则——业务边界优先
2.1 核心原则:先业务边界,再数据边界
微服务拆分的两颗"北极星":
原则 1:业务边界优先——按照业务能力划分,而不是按技术层次划分。
// ❌ 错误:按技术层次拆分
com.company.app
├── controller-service // 所有 Controller → 一个服务
├── business-service // 所有 Service → 一个服务
└── data-service // 所有 DAO → 一个服务
// 问题:一个业务请求要穿透三层服务,延迟高、耦合重
// ✅ 正确:按业务边界拆分
com.company.app
├── order-service // 订单相关的 Controller + Service + DAO 在一起
├── inventory-service // 库存相关的在一起
└── payment-service // 支付相关的在一起
// 每个服务自包含,独立部署
原则 2:数据边界优先——每个微服务拥有自己的数据库,服务间不共享数据库。
// ❌ 错误:多个微服务共享一个数据库
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 订单服务 │ │ 库存服务 │ │ 支付服务 │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
└─────────────────┼─────────────────┘
▼
┌─────────────────────┐
│ 共享数据库 │
│ (单点故障!) │
└─────────────────────┘
// 问题:库存服务改了表结构,订单服务挂了
// ✅ 正确:每个服务独立数据库
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 订单服务 │ │ 库存服务 │ │ 支付服务 │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 订单DB │ │ 库存DB │ │ 支付DB │
└──────────────┘ └──────────────┘ └──────────────┘
2.2 限界上下文到微服务的映射
DDD 的限界上下文是最自然的微服务边界候选:
担保业务的限界上下文 → 微服务映射:
限界上下文 微服务 核心职责
─────────────────────────────────────────────────
客户管理 → customer-service 客户信息、信用评级
授信管理 → credit-service 额度申请、额度管理
担保管理 → guarantee-service 担保申请、合同管理
风控评估 → risk-service 风控规则、风险评估
签约管理 → contract-service 电子合同、签署流程
财务管理 → finance-service 放款、还款、账务
但是注意:限界上下文 ≠ 微服务!一个限界上下文可以包含多个微服务,多个小型的限界上下文也可以合并为一个微服务。关键是看团队组织和变更频率。
2.3 三种常见拆分策略
策略一:按业务能力拆分(最常用)
这是最常见也是最推荐的拆分方式。每个微服务对应一个核心业务能力。
电商系统的业务能力拆分:
用户能力 → user-service (注册、登录、认证、个人信息)
商品能力 → product-service (商品发布、类目、搜索)
订单能力 → order-service (下单、订单管理)
库存能力 → inventory-service (库存扣减、补货)
支付能力 → payment-service (支付、退款)
物流能力 → logistics-service (发货、物流追踪)
营销能力 → marketing-service (优惠券、促销活动)
策略二:按子域拆分(DDD 视角)
按照 DDD 中的核心域、支撑域、通用域划分:
核心域(公司核心竞争力,重点投入)
├── 风控引擎服务 (risk-engine) — 自研,持续优化
└── 担保交易服务 (guarantee-core) — 自研,核心业务
支撑域(支撑核心业务,但不是差异化所在)
├── 客户管理服务 (customer)
├── 合同管理服务 (contract)
└── 产品配置服务 (product-config)
通用域(通用能力,可采购/复用)
├── 认证授权服务 (auth) — 可采购第三方
├── 消息通知服务 (notification)
└── 文件存储服务 (file-storage) — 用 OSS/COS
核心域投入最强资源,通用域能买就买——这是最有 ROI 的资源分配策略。
策略三:按团队拆分(康威定律)
康威定律说:系统架构反映了组织的沟通结构。如果你有 3 个团队,大概率需要 3 个服务(或 3 的倍数)。
团队 A → order-service + payment-service (交易域)
团队 B → product-service + inventory-service (商品域)
团队 C → user-service + marketing-service (用户域)
一个团队负责的服务不要太多(3-5 个为宜),否则认知负荷太大。
第三部分:数据库拆分——最难的一步
3.1 数据库拆分的三个级别
Level 1: 共享数据库
┌─────┐ ┌─────┐ ┌─────┐
│ Svc1│ │ Svc2│ │ Svc3│
└──┬──┘ └──┬──┘ └──┬──┘
└───────┼───────┘
▼
┌─────────────┐
│ Shared DB │
└─────────────┘
特点:最简单,但有单点故障和服务间隐式耦合
Level 2: 共享数据库独立 Schema
┌─────┐ ┌─────┐ ┌─────┐
│ Svc1│ │ Svc2│ │ Svc3│
└──┬──┘ └──┬──┘ └──┬──┘
│ │ │
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│sch1 │ │sch2 │ │sch3 │
└──┬──┘ └──┬──┘ └──┬──┘
└───────┼───────┘
▼
┌─────────────┐
│ Shared DB │ (同一个数据库实例)
└─────────────┘
特点:逻辑隔离,物理共享;迁移的中间状态
Level 3: 独立数据库
┌─────┐ ┌─────┐ ┌─────┐
│ Svc1│ │ Svc2│ │ Svc3│
└──┬──┘ └──┬──┘ └──┬──┘
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│ DB1 │ │ DB2 │ │ DB3 │
└─────┘ └─────┘ └─────┘
特点:完全隔离,独立扩展,微服务目标状态
推荐路线图:Level 1 → Level 2 → Level 3,不要一步到位。Level 2 是安全的中间状态。
3.2 数据一致性的挑战
拆分数据库最大的挑战是:以前一个事务搞定的操作,现在跨越了多个数据库。
// 单体中:一个事务搞定
@Transactional
public void placeOrder(OrderRequest request) {
// 1. 扣减库存
inventoryMapper.decrease(request.getProductId(), request.getQuantity());
// 2. 创建订单
orderMapper.insert(createOrderDO(request));
// 3. 扣减余额
paymentMapper.deduct(request.getUserId(), request.getAmount());
}
// 三步全在一个数据库事务中,要么全成,要么全败
// 微服务中:三个独立操作
// 问题:库存扣了,订单创建失败了怎么办?
解决方案矩阵:
| 方案 | 一致性 | 复杂度 | 适用场景 |
|---|---|---|---|
| Saga 编排 | 最终一致 | 中 | 长事务,跨多个服务 |
| TCC (Try-Confirm-Cancel) | 强一致 | 高 | 资金类操作 |
| 本地消息表 | 最终一致 | 中 | 异步解耦 |
| 事件驱动 + 补偿 | 最终一致 | 高 | 复杂业务流程 |
| 二阶段提交 (2PC) | 强一致 | 高 | 不推荐——性能差、单点故障 |
推荐:Saga 编排模式
/**
* 订单 Saga 编排器
* 每个步骤有对应的补偿操作
*/
@Service
public class OrderSagaOrchestrator {
@Autowired private InventoryClient inventoryClient;
@Autowired private OrderService orderService;
@Autowired private PaymentClient paymentClient;
public OrderResult execute(OrderRequest request) {
// Step 1: 预留库存
ReserveInventoryResult reserveResult =
inventoryClient.reserve(request.getProductId(), request.getQuantity());
if (!reserveResult.isSuccess()) {
return OrderResult.fail("库存不足");
}
try {
// Step 2: 创建订单
Order order = orderService.create(request);
if (order == null) {
throw new SagaCompensationException("订单创建失败");
}
// Step 3: 扣款
PaymentResult paymentResult =
paymentClient.deduct(request.getUserId(), request.getAmount());
if (!paymentResult.isSuccess()) {
throw new SagaCompensationException("扣款失败");
}
// Step 4: 确认库存
inventoryClient.confirm(reserveResult.getReserveId());
return OrderResult.success(order.getId());
} catch (SagaCompensationException e) {
// 补偿:释放预留的库存
inventoryClient.cancel(reserveResult.getReserveId());
return OrderResult.fail(e.getMessage());
}
}
}
第四部分:实战——电商系统拆分全过程
4.1 现状分析
假设我们有一个电商单体应用,包含以下功能:
Monolith Application
├── 用户管理 (注册、登录、个人信息)
├── 商品管理 (发布、上下架、搜索)
├── 购物车
├── 订单管理 (下单、支付、退款)
├── 库存管理 (扣减、回补、预警)
├── 物流管理 (发货、签收、退回)
├── 营销管理 (优惠券、满减、限时购)
└── 后台管理 (数据报表、权限管理)
团队情况:3 个团队,共 15 人。
4.2 拆分方案设计
第一步:识别业务边界
画出业务能力图谱:
┌────────────────────────────────────────────────────┐
│ 电商系统 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 用户域 │ │ 商品域 │ │ 交易域 │ │
│ │ │ │ │ │ │ │
│ │·用户注册 │ │·商品管理 │ │·购物车 │ │
│ │·认证登录 │ │·类目管理 │ │·下单 │ │
│ │·地址管理 │ │·库存管理 │ │·支付 │ │
│ │·会员等级 │ │·搜索 │ │·退款 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 物流域 │ │ 营销域 │ │ 数据域 │ │
│ │ │ │ │ │ │ │
│ │·发货管理 │ │·优惠券 │ │·运营报表 │ │
│ │·物流追踪 │ │·满减活动 │ │·用户分析 │ │
│ │·签收确认 │ │·限时购 │ │·财务统计 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────────────────────────────────┘
第二步:映射到微服务
团队 A (交易域) 团队 B (商品域) 团队 C (平台域)
├── order-service ├── product-service ├── user-service
├── payment-service ├── inventory-service ├── marketing-service
└── cart-service └── search-service └── data-service
物流域 → logistics-service (由团队 A 兼管)
第三步:确定数据库拆分方案
服务 数据库 关键表
────────────────────────────────────────────────────
user-service user_db t_user, t_address, t_member
product-service product_db t_product, t_category, t_sku
inventory-service inventory_db t_inventory, t_stock_log
order-service order_db t_order, t_order_item
payment-service payment_db t_payment, t_refund
cart-service cart_db(is Redis) cart:{userId}
marketing-service marketing_db t_coupon, t_promotion
logistics-service logistics_db t_shipment, t_tracking
4.3 跨服务数据访问处理
拆分后,订单服务需要"用户的收货地址"——但地址在 user-service 中。如何处理?
// 方案 A:API 调用(推荐用于实时数据)
@Service
public class OrderApplicationService {
@Autowired private UserClient userClient;
@Autowired private OrderRepository orderRepository;
public OrderDetailDTO getOrderDetail(Long orderId) {
Order order = orderRepository.findById(orderId);
// 跨服务调用获取用户地址
UserAddress address = userClient.getAddress(
order.getUserId(), order.getAddressId());
return OrderDetailDTO.from(order, address);
}
}
// 方案 B:数据冗余(推荐用于高频读取)
// 下单时将地址快照保存在订单中
@TableName("t_order")
public class OrderDO {
private Long id;
private Long userId;
// 地址快照 - 冗余存储,不随用户修改地址而变
private String snapshotReceiverName;
private String snapshotPhone;
private String snapshotProvince;
private String snapshotCity;
private String snapshotDetailAddress;
// ...
}
// 原则:订单的收货地址是"下单时的快照",不是"用户当前的地址"
数据访问策略决策树:
需要其他服务的数据?
├── 是实时数据(如库存余量)?
│ └── API 调用
├── 是历史快照(如下单时的地址)?
│ └── 数据冗余存储
├── 是高频读取但低频变更(如商品名称)?
│ └── 本地缓存 + 变更通知
└── 是报表/分析场景?
└── 数据中台/数据仓库
4.4 拆分执行计划
不要一次性拆完!推荐分阶段迁移:
Phase 1 (第 1-2 月): 基础设施准备
├── 搭建服务注册中心 (Nacos/Eureka)
├── 搭建配置中心 (Nacos/Apollo)
├── 搭建 API 网关 (Gateway/Kong)
├── 搭建统一监控 (Prometheus + Grafana)
└── 制定接口规范 + 错误码规范
Phase 2 (第 3-4 月): 拆分非核心服务(先易后难)
├── 拆分 user-service
├── 拆分 marketing-service
└── 拆分 logistics-service
Phase 3 (第 5-6 月): 拆分核心服务
├── 拆分 product-service
├── 拆分 inventory-service
└── 拆分 order-service
Phase 4 (第 7 月): 干掉单体
├── 下线单体中的已迁移功能
└── 清理代码和数据库
先拆"外围"再拆"核心",风险最小。如果非核心服务的拆分出了问题,至少核心业务还在单体中稳定运行。
第五部分:拆分后的挑战
5.1 分布式事务
拆分后,以前一个 @Transactional 搞定的事,现在需要分布式事务处理。
// 场景:下单 = 扣库存 + 创建订单 + 使用优惠券 + 扣款
// 涉及 4 个服务!如何保证一致性?
/**
* 使用 Seata AT 模式处理分布式事务
*/
@GlobalTransactional // Seata 全局事务注解
public OrderResult placeOrder(OrderRequest request) {
// 1. 锁定库存(inventory-service)
inventoryClient.lock(request.getItems());
// 2. 使用优惠券(marketing-service)
couponClient.use(request.getCouponId());
// 3. 创建订单(order-service,本服务)
Order order = orderRepository.save(createOrder(request));
// 4. 发起支付(payment-service)
paymentClient.pay(order.getId(), order.getTotalAmount());
return OrderResult.success(order);
}
// Seata 自动管理各服务的本地事务,实现整体一致性
/**
* 使用 RocketMQ 事务消息(更轻量,推荐)
*/
@Service
public class OrderService {
@Autowired private RocketMQTemplate rocketMQTemplate;
@Transactional
public void createOrder(OrderRequest request) {
// Step 1: 本地事务中创建订单
Order order = orderRepository.save(createOrderEntity(request));
// Step 2: 发送半消息(事务消息)
rocketMQTemplate.sendMessageInTransaction(
"order-created-topic",
MessageBuilder.withPayload(new OrderCreatedEvent(order)).build(),
order.getId() // 事务参数
);
// 如果本地事务提交,消息才真正发送
// 如果本地事务回滚,消息被丢弃
}
}
// 库存服务消费消息
@RocketMQMessageListener(topic = "order-created-topic", consumerGroup = "inventory-group")
@Component
public class InventoryDeductListener
implements RocketMQListener<OrderCreatedEvent> {
@Override
@Transactional
public void onMessage(OrderCreatedEvent event) {
// 扣减库存
inventoryRepository.deduct(
event.getProductId(), event.getQuantity());
}
}
5.2 服务发现与配置管理
# Nacos 配置中心 - order-service 配置示例
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: nacos-cluster:8848
namespace: production
group: ECOMMERCE
config:
server-addr: nacos-cluster:8848
namespace: production
group: ECOMMERCE
file-extension: yaml
shared-configs:
- data-id: common-config.yaml
group: ECOMMERCE
refresh: true
// 服务间调用 - 使用 Feign + Nacos 服务发现
@FeignClient(name = "inventory-service") // Nacos 中注册的服务名
public interface InventoryClient {
@PostMapping("/api/inventory/lock")
Result<LockResult> lock(@RequestBody LockRequest request);
@PostMapping("/api/inventory/confirm/{reserveId}")
Result<Void> confirm(@PathVariable String reserveId);
@PostMapping("/api/inventory/cancel/{reserveId}")
Result<Void> cancel(@PathVariable String reserveId);
}
// 使用时自动负载均衡到 inventory-service 的多个实例
@Autowired private InventoryClient inventoryClient;
5.3 监控与链路追踪
// 使用 Micrometer + OpenTelemetry 做全链路追踪
@RestController
public class OrderController {
@Autowired private MeterRegistry meterRegistry;
@PostMapping("/orders")
public Result<OrderResponse> createOrder(@RequestBody OrderRequest request) {
// 自定义指标
Timer.Sample sample = Timer.start(meterRegistry);
try {
OrderResponse response = orderService.create(request);
// 记录成功指标
meterRegistry.counter("orders.created.total").increment();
meterRegistry.counter("orders.amount.total")
.increment(response.getTotalAmount().doubleValue());
return Result.success(response);
} catch (Exception e) {
meterRegistry.counter("orders.created.error").increment();
throw e;
} finally {
sample.stop(Timer.builder("orders.create.duration")
.description("订单创建耗时")
.register(meterRegistry));
}
}
}
5.4 API 网关
# Spring Cloud Gateway 路由配置
spring:
cloud:
gateway:
routes:
# 订单服务路由
- id: order-service
uri: lb://order-service # lb:// = 负载均衡
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=0
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
# 商品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
- name: CircuitBreaker
args:
name: productCircuitBreaker
fallbackUri: forward:/fallback/products
# 全局过滤器
default-filters:
- AddRequestHeader=X-Request-Id, ${random.uuid}
- name: Retry
args:
retries: 2
statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
第六部分:拆得太碎的代价
6.1 "微服务过小症"的症状
当微服务拆得过细时,会出现以下问题:
症状一:调试地狱
场景:排查一个订单创建失败的问题
调试路径:
API Gateway → order-service → inventory-service → product-service
→ payment-service
→ marketing-service (优惠券)
→ user-service (收货地址)
一个请求跨越 6 个服务,你需要:
- 登录 6 台机器/容器看日志
- 在 6 个代码仓库里找代码
- 追踪 Jaeger 中 6 个 span 之间的关系
- 在 Kafka 里找中间消息
症状二:运维爆炸
N 个微服务的运维成本:
- N 个代码仓库
- N 条 CI/CD 流水线
- N 个 Dockerfile / Helm Chart
- N 组配置(开发/测试/生产)
- N 个数据库实例
- N × 2 个容器实例(至少)
- N 个监控告警规则
当 N = 50 时:
50 个代码仓库 × 3 分支 × 3 环境 = 450 个部署单元
运维团队已经哭晕在机房
症状三:网络延迟累加
单体:查询订单详情 → 1 次数据库查询,耗时 5ms
微服务(过细拆分后):
order-service 查订单 → 5ms
user-service 查用户信息 → 10ms (RPC)
product-service 查商品信息 → 15ms (RPC)
payment-service 查支付状态 → 12ms (RPC)
logistics-service 查物流 → 8ms (RPC)
─────────────────────────────
总耗时:50ms(是单体的 10 倍!)
6.2 合理的服务粒度
一个简单的判断标准:一个微服务 = 一个 2-Pizza 团队(6-8 人)可以维护的范围。
其他参考指标:
| 维度 | 太细 | 刚好 | 太粗 |
|---|---|---|---|
| 代码行数 | < 1000 | 5000-50000 | > 200000 |
| 数据表数量 | < 5 | 10-30 | > 100 |
| API 接口数 | < 5 | 10-50 | > 200 |
| 负责团队人数 | < 2 | 3-8 | > 15 |
| 独立部署频率 | 每天多次 | 每天/每周 | 每月 |
| 启动时间 | < 3 秒 | 5-30 秒 | > 2 分钟 |
6.3 什么时候该合并
如果你发现以下情况,说明拆过了,该合并:
- A 服务和 B 服务总是同时变更、同时发布 → 它们应该是一个服务
- A 服务崩溃了,B 服务完全不能用(强运行时耦合) → 它们应该在一个进程里
- A 服务和 B 服务共享大量代码(复制粘贴) → 应该抽取公共库,或者合并
- 一个简单功能要在 3 个以上仓库里改代码 → 边界划错了
// 合并过度拆分的服务
// 如果 order-processing-service 和 order-query-service
// 共享同样的数据库、同样的实体定义、总是同时发布
// → 它们应该是一个 order-service
第七部分:拆分决策框架
7.1 微服务拆分决策树
┌──────────────────────────────────────┐
│ 当前是否是拆分的好时机? │
├──────────────────────────────────────┤
│ □ 团队人数 > 10 且多团队协作? │
│ □ 不同模块有不同扩展需求? │
│ □ 部署耦合导致发布缓慢? │
│ □ 存在独立技术栈/合规需求? │
│ │
│ 少于 2 项 → 保持模块化单体 │
│ 2-3 项 → 考虑拆分,但谨慎评估 │
│ 4 项以上 → 拆分 ROI 明确 │
└──────────────────────────────────────┘
↓ (确定拆分)
┌──────────────────────────────────────┐
│ 确定拆分边界 │
├──────────────────────────────────────┤
│ 1. 画出业务能力图谱 │
│ 2. 识别限界上下文 │
│ 3. 对齐团队边界(康威定律) │
│ 4. 确定数据所有权 │
│ 5. 画出服务间依赖图 │
│ │
│ 依赖图中有循环依赖?→ 重新审视边界 │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 确定拆分顺序与策略 │
├──────────────────────────────────────┤
│ 1. 先拆外围,后拆核心 │
│ 2. 先准备基础设施,再拆服务 │
│ 3. 每次只拆一个服务,验证后再拆下一个 │
│ 4. 数据库先用 Level 2 (独立 Schema) │
│ 5. 设置回滚方案 │
└──────────────────────────────────────┘
7.2 拆分检查清单
技术检查清单:
组织检查清单:
如果以上超过 3 项未满足,建议先把基础设施建设好再拆。
总结
核心原则回顾
- 业务边界优先:按业务能力拆分,不是按技术层次
- 数据边界优先:每个服务有自己的数据库
- 康威定律:服务边界应匹配团队边界
- 渐进式拆分:先拆外围、先建基础设施、逐步迁移
- 够用就好:3-8 个微服务通常比 30 个更好
一句话总结
好的微服务架构不是拆出来的,是长出来的。 先有模块化单体,当边界清晰后再自然裂变为微服务。不要为了微服务而微服务——业务价值才是最终目标。
行动指南
| 如果你正在... | 建议 |
|---|---|
| 从零开始新项目 | 先用模块化单体,等业务边界稳定后再拆 |
| 单体已经很大 | 识别限界上下文,先拆外围非核心服务 |
| 已经拆了很多服务 | 检查是否有应该合并的"颗粒过小"服务 |
| 不确定要不要拆 | 回答 7.1 节的 4 个问题,客观评估 |
参考资料
- Sam Newman, 《微服务设计》
- Chris Richardson, 《微服务架构设计模式》
- Martin Fowler, "Microservices - a definition"
- Melvin Conway, "How Do Committees Invent?"
- Alibaba, 《阿里微服务拆分实践》