引子:一次失败的微服务拆分

2019年某月,某金融科技公司CTO李明决定将单体应用拆分为微服务。

拆分方案(按技术层拆分):

  • 前端服务:负责所有页面渲染
  • 业务服务:负责所有业务逻辑
  • 数据服务:负责所有数据访问

3个月后的结果

  • 部署次数:从每周1次降到每月1次(更慢了!)
  • 数据库连接:3个服务共享同一个数据库(还是耦合)
  • 代码冲突:业务服务有50个类,15个人修改,冲突率40%(比单体更高)
  • 故障影响:数据服务挂了,整个系统不可用(单点依旧存在)

李明的困惑

  • “我们明明拆分了,为什么比单体还糟糕?”
  • “什么才是正确的拆分方式?”
  • “服务边界应该如何划分?”

这个案例揭示了一个核心问题:拆分微服务的本质不是"拆分技术层",而是"拆分业务领域"

让我们从第一性原理出发,系统化解答这个问题。


一、服务拆分的本质问题

1.1 什么是服务边界?

服务边界的定义

  • 物理边界:独立的进程、独立的数据库、独立的部署单元
  • 逻辑边界:独立的业务职责、独立的团队所有权、独立的变更频率

好的服务边界的三个特征

  1. 高内聚:服务内部的功能紧密相关(订单创建、订单查询、订单取消都在订单服务内)
  2. 低耦合:服务之间的依赖最小化(订单服务不依赖评论服务)
  3. 独立演进:服务可以独立开发、测试、部署(订单服务升级,不影响商品服务)

1.2 拆分的三个核心目标

目标1:独立部署

单体架构的问题

  • 改订单服务的一个Bug,整个系统重启
  • 部署时间:120分钟

微服务的解决方案

  • 改订单服务的Bug,只重启订单服务
  • 部署时间:15分钟

目标2:独立扩展

单体架构的问题

  • 订单服务压力大,整体扩容,浪费80%资源

微服务的解决方案

  • 订单服务压力大,只扩容订单服务

目标3:独立演进

单体架构的问题

  • 整个系统必须用同一技术栈(Java + Spring)

微服务的解决方案

  • 订单服务用Java,推荐服务用Python,搜索服务用Go

1.3 拆分的代价

不要忘记:拆分微服务是有代价的!

代价维度单体架构微服务架构增加幅度
网络调用延迟0ms(本地调用)10-50ms+∞
事务复杂度ACID(本地事务)BASE(分布式事务)+10倍
运维复杂度1个服务100+服务+100倍
问题定位难度单进程堆栈链路追踪+5倍

核心原则:只有当拆分的收益 > 拆分的成本时,才应该拆分!


二、领域驱动设计(DDD):识别服务边界的方法论

2.1 什么是领域驱动设计?

**领域驱动设计(Domain-Driven Design, DDD)**是由Eric Evans在2004年提出的一套软件设计方法论。

核心思想

  • 软件设计应该以业务领域为中心,而不是技术实现
  • 通过建立通用语言(Ubiquitous Language),让技术人员和业务人员对齐
  • 通过识别限界上下文(Bounded Context),划分服务边界

类比

  • 传统方式:像盲人摸象,从技术角度拆分(前端服务、后端服务、数据库服务)
  • DDD方式:像建筑师设计房子,从业务角度拆分(订单领域、商品领域、用户领域)

2.2 核心概念1:限界上下文(Bounded Context)

什么是限界上下文?

限界上下文是DDD中最核心的概念,它定义了一个模型的边界。

例子:在电商系统中,“商品"这个概念在不同上下文中的含义不同:

  • 商品目录上下文:商品 = ID + 名称 + 描述 + 图片 + 价格 + 类目
  • 库存管理上下文:商品 = ID + SKU + 库存数量 + 仓库位置
  • 订单上下文:商品 = ID + 名称 + 价格 + 数量(快照)
  • 搜索上下文:商品 = ID + 标题 + 关键词 + 销量 + 评分

关键发现:同一个"商品”,在不同上下文中,属性和行为完全不同!

限界上下文的作用

  1. 定义模型边界:每个上下文有自己的模型,互不干扰
  2. 划分团队边界:每个上下文由一个团队负责
  3. 确定服务边界:每个上下文对应一个微服务

如何识别限界上下文?

方法1:用例分析法

以电商系统为例:

用例1:用户浏览商品
  - 涉及模块:商品目录、搜索、推荐
  - 限界上下文:商品目录上下文

用例2:用户下单
  - 涉及模块:订单、库存、支付
  - 限界上下文:订单上下文、库存上下文、支付上下文

用例3:用户评价商品
  - 涉及模块:评论、商品
  - 限界上下文:评论上下文

用例4:商家管理库存
  - 涉及模块:库存、仓库
  - 限界上下文:库存上下文

方法2:业务流程分析法

以订单流程为例:

订单流程:
1. 创建订单 → 订单上下文
2. 扣减库存 → 库存上下文
3. 调用支付 → 支付上下文
4. 发送通知 → 通知上下文
5. 生成物流单 → 物流上下文

每个流程节点 = 一个限界上下文 = 一个微服务

方法3:名词分析法

识别业务中的核心名词:

核心名词:
- 用户、会员等级、积分 → 用户上下文
- 商品、SKU、类目、品牌 → 商品目录上下文
- 订单、订单项、订单状态 → 订单上下文
- 库存、仓库、出入库记录 → 库存上下文
- 支付、支付流水、退款 → 支付上下文
- 物流、运单、配送 → 物流上下文
- 评论、评分、晒图 → 评论上下文

2.3 核心概念2:聚合根(Aggregate Root)

什么是聚合根?

聚合是一组相关对象的集合,它们作为一个整体进行数据变更。

聚合根是聚合的入口,所有对聚合内对象的访问都必须通过聚合根。

例子:订单聚合

/**
 * 订单聚合根
 * 聚合包含:订单(Aggregate Root)+ 订单项(Entity)
 */
@Entity
@Table(name = "t_order")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orderNo;           // 订单号
    private Long userId;              // 用户ID
    private OrderStatus status;       // 订单状态
    private BigDecimal totalAmount;   // 总金额

    // 订单项(聚合内的实体)
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "order_id")
    private List<OrderItem> items = new ArrayList<>();

    /**
     * 业务方法:创建订单
     * 保证不变量:总金额 = 所有订单项金额之和
     */
    public void create(List<OrderItem> items) {
        if (items == null || items.isEmpty()) {
            throw new BusinessException("订单项不能为空");
        }

        this.items.addAll(items);
        this.totalAmount = calculateTotalAmount();
        this.status = OrderStatus.UNPAID;
    }

    /**
     * 业务方法:添加订单项
     * 保证不变量:总金额 = 所有订单项金额之和
     */
    public void addItem(OrderItem item) {
        this.items.add(item);
        this.totalAmount = calculateTotalAmount();
    }

    /**
     * 业务方法:取消订单
     * 保证不变量:只有未支付的订单才能取消
     */
    public void cancel() {
        if (this.status != OrderStatus.UNPAID) {
            throw new BusinessException("只有未支付的订单才能取消");
        }
        this.status = OrderStatus.CANCELLED;
    }

    /**
     * 计算总金额(保证不变量)
     */
    private BigDecimal calculateTotalAmount() {
        return items.stream()
                .map(OrderItem::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

/**
 * 订单项(聚合内的实体)
 * 不能单独存在,必须属于某个订单
 */
@Entity
@Table(name = "t_order_item")
public class OrderItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long productId;       // 商品ID
    private String productName;   // 商品名称(快照)
    private BigDecimal price;     // 单价(快照)
    private Integer quantity;     // 数量
    private BigDecimal amount;    // 小计金额

    /**
     * 计算小计金额
     */
    public void calculateAmount() {
        this.amount = this.price.multiply(new BigDecimal(this.quantity));
    }
}

聚合根的核心作用

  1. 保证不变量:订单的总金额永远等于所有订单项的金额之和
  2. 事务边界:订单和订单项要么一起保存,要么一起回滚
  3. 并发控制:通过订单的版本号控制并发修改

2.4 核心概念3:实体(Entity)与值对象(Value Object)

实体(Entity)

特点

  • 有唯一标识(ID)
  • 有生命周期(创建、修改、删除)
  • 可变的

例子:订单、用户、商品

/**
 * 订单实体
 * 特点:有ID,有生命周期,可变
 */
@Entity
public class Order {
    @Id
    private Long id;  // 唯一标识

    private OrderStatus status;  // 状态可变

    // 业务方法
    public void pay() {
        this.status = OrderStatus.PAID;
    }
}

值对象(Value Object)

特点

  • 无唯一标识
  • 无生命周期
  • 不可变的

例子:地址、金额、日期范围

/**
 * 地址值对象
 * 特点:无ID,不可变,通过属性比较相等性
 */
@Embeddable
public class Address {
    private String province;
    private String city;
    private String district;
    private String street;

    // 不可变:只有getter,没有setter
    public String getProvince() {
        return province;
    }

    // 创建新的地址(不是修改原地址)
    public Address withCity(String newCity) {
        return new Address(this.province, newCity, this.district, this.street);
    }

    // 通过属性比较相等性(没有ID)
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Address) {
            Address other = (Address) obj;
            return Objects.equals(this.province, other.province)
                && Objects.equals(this.city, other.city)
                && Objects.equals(this.district, other.district)
                && Objects.equals(this.street, other.street);
        }
        return false;
    }
}

2.5 核心概念4:领域事件(Domain Event)

领域事件表示业务领域中发生的重要事情。

例子:订单已创建、订单已支付、订单已取消

/**
 * 订单已创建事件
 */
@Data
@Builder
public class OrderCreatedEvent {
    private String orderNo;
    private Long userId;
    private BigDecimal amount;
    private LocalDateTime createTime;
}

/**
 * 订单服务:发布领域事件
 */
@Service
public class OrderService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public Order createOrder(CreateOrderRequest request) {
        // 1. 创建订单
        Order order = new Order();
        order.create(request.getItems());
        orderRepository.save(order);

        // 2. 发布领域事件
        OrderCreatedEvent event = OrderCreatedEvent.builder()
                .orderNo(order.getOrderNo())
                .userId(order.getUserId())
                .amount(order.getTotalAmount())
                .createTime(LocalDateTime.now())
                .build();

        rocketMQTemplate.syncSend("ORDER_CREATED_TOPIC", event);

        return order;
    }
}

/**
 * 库存服务:监听领域事件
 */
@Service
public class InventoryEventListener {

    @RocketMQMessageListener(
        topic = "ORDER_CREATED_TOPIC",
        consumerGroup = "inventory-consumer-group"
    )
    public class OrderCreatedListener implements RocketMQListener<OrderCreatedEvent> {

        @Override
        public void onMessage(OrderCreatedEvent event) {
            // 订单创建后,扣减库存
            for (OrderItem item : event.getItems()) {
                inventoryService.deduct(item.getProductId(), item.getQuantity());
            }
        }
    }
}

领域事件的作用

  1. 解耦服务:订单服务不直接调用库存服务,通过事件解耦
  2. 异步处理:订单创建后,库存扣减可以异步执行
  3. 事件溯源:记录所有业务事件,可以重放历史

三、康威定律:组织架构决定系统架构

3.1 什么是康威定律?

康威定律(Conway’s Law)

“Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.”

“设计系统的组织,其产生的系统设计等同于组织内的沟通结构。”

通俗解释

  • 如果你的团队按职能划分(前端团队、后端团队、DBA团队),你的系统就会按技术层拆分(前端服务、后端服务、数据库服务)
  • 如果你的团队按业务划分(订单团队、商品团队、用户团队),你的系统就会按业务领域拆分(订单服务、商品服务、用户服务)

3.2 反康威定律实践:逆向康威策略

逆向康威策略(Reverse Conway Maneuver)

  • 先设计理想的系统架构
  • 再调整组织架构去匹配系统架构

案例:亚马逊的两个披萨团队

2002年,亚马逊CEO Jeff Bezos提出"两个披萨团队"规则

  • 团队规模:5-10人(两个披萨能喂饱的团队)
  • 团队职责:负责一个完整的业务领域(订单、商品、支付等)
  • 团队自治:从前端到后端到数据库,全栈负责

结果

  • 亚马逊的系统架构自然演进为微服务架构
  • 每个两个披萨团队负责一个微服务
  • 服务边界清晰,团队沟通成本低

3.3 组织架构的三种模式

模式1:职能型组织(Function-Oriented)

组织结构

        CTO
         |
    ┌────┴────┬────────┬──────┐
    |         |        |      |
 前端团队  后端团队  测试团队  DBA团队

系统架构

前端服务 → 后端服务 → 数据库

问题

  • 团队按技术层划分,系统也按技术层拆分
  • 任何需求都需要跨团队协作(前端、后端、测试、DBA)
  • 沟通成本高,发布频率低

模式2:项目型组织(Project-Oriented)

组织结构

        CTO
         |
    ┌────┴────┬────────┬──────┐
    |         |        |      |
 项目1团队  项目2团队  项目3团队  公共团队

系统架构

项目1服务 + 项目2服务 + 项目3服务 + 公共服务

问题

  • 项目结束后,团队解散,代码无人维护
  • 公共服务成为瓶颈(所有项目都依赖)

模式3:产品型组织(Product-Oriented)

组织结构

        CTO
         |
    ┌────┴────┬────────┬──────┐
    |         |        |      |
 订单团队  商品团队  用户团队  支付团队
 (全栈)    (全栈)    (全栈)    (全栈)

系统架构

订单服务 + 商品服务 + 用户服务 + 支付服务

优势

  • 团队按业务领域划分,系统也按业务领域拆分
  • 每个团队负责一个完整的业务(从前端到后端到数据库)
  • 团队自治,沟通成本低,发布频率高

3.4 两个披萨团队原则(Two-Pizza Team)

核心思想:团队规模不能太大,应该保持在两个披萨能喂饱的人数(5-10人)。

理由

  • 团队规模5人:沟通路径 = 5 × 4 / 2 = 10条
  • 团队规模10人:沟通路径 = 10 × 9 / 2 = 45条
  • 团队规模20人:沟通路径 = 20 × 19 / 2 = 190条

沟通成本随团队规模指数级增长!

亚马逊的实践

  • 每个两个披萨团队负责一个微服务
  • 团队拥有完整的技能栈(前端、后端、测试、运维)
  • 团队对服务的整个生命周期负责(开发、部署、运维、监控)

四、服务拆分的四个维度

4.1 维度1:业务能力(Business Capability)

核心思想:按照业务能力划分服务边界。

什么是业务能力?

  • 业务能力是企业为了实现业务目标而必须具备的能力
  • 例如:电商企业的业务能力包括商品管理、订单管理、库存管理、支付管理等

如何识别业务能力?

方法1:价值流分析

以电商为例:

用户价值流:
浏览商品 → 加入购物车 → 下单 → 支付 → 配送 → 收货 → 评价

对应的业务能力:
- 商品目录能力(浏览商品)
- 购物车能力(加入购物车)
- 订单能力(下单)
- 支付能力(支付)
- 物流能力(配送、收货)
- 评价能力(评价)

每个业务能力 = 一个微服务

方法2:组织架构映射

组织架构:
- 商品部(负责商品上架、商品管理)
- 订单部(负责订单处理、售后)
- 物流部(负责配送、仓储)
- 支付部(负责支付、结算)

系统架构:
- 商品服务
- 订单服务
- 物流服务
- 支付服务

案例:电商系统的业务能力拆分

/**
 * 商品服务:商品目录能力
 */
@Service
public class ProductService {

    // 商品管理
    public Product createProduct(CreateProductRequest request) { }
    public Product updateProduct(UpdateProductRequest request) { }
    public void deleteProduct(Long productId) { }

    // 商品查询
    public Product getProduct(Long productId) { }
    public List<Product> searchProducts(SearchRequest request) { }

    // 类目管理
    public Category createCategory(CreateCategoryRequest request) { }
    public List<Category> getCategories() { }
}

/**
 * 订单服务:订单处理能力
 */
@Service
public class OrderService {

    // 订单管理
    public Order createOrder(CreateOrderRequest request) { }
    public void cancelOrder(String orderNo) { }

    // 订单查询
    public Order getOrder(String orderNo) { }
    public List<Order> getUserOrders(Long userId) { }

    // 订单状态流转
    public void confirmOrder(String orderNo) { }
    public void deliverOrder(String orderNo) { }
    public void completeOrder(String orderNo) { }
}

/**
 * 库存服务:库存管理能力
 */
@Service
public class InventoryService {

    // 库存管理
    public void increaseStock(Long productId, Integer quantity) { }
    public void decreaseStock(Long productId, Integer quantity) { }

    // 库存查询
    public Inventory getStock(Long productId) { }

    // 库存预占(下单时预占库存,支付后扣减)
    public void reserveStock(Long productId, Integer quantity) { }
    public void confirmReservation(Long productId, Integer quantity) { }
    public void cancelReservation(Long productId, Integer quantity) { }
}

4.2 维度2:数据边界(Data Boundary)

核心思想:按照数据依赖关系划分服务边界。

原则

  1. 每个服务拥有独立的数据库(避免数据库共享)
  2. 服务间不能直接访问对方的数据库(通过API访问)
  3. 允许数据冗余(为了性能和独立性)

反模式:数据库共享

订单服务 ----┐
            |
商品服务 ---→ MySQL(共享数据库)
            |
用户服务 ----┘

问题:
1. 数据库成为单点故障
2. 表结构变更影响所有服务
3. 数据库连接池被某个服务耗尽,影响其他服务

正确做法:每个服务独立数据库

订单服务 → Order_DB(订单数据库)
商品服务 → Product_DB(商品数据库)
用户服务 → User_DB(用户数据库)

优势:
1. 服务独立演进
2. 数据库故障隔离
3. 可以选择不同的数据库类型(订单用MySQL,商品用MongoDB)

数据冗余策略

场景:订单服务需要显示商品名称

方案1:同步调用商品服务(不推荐)

public Order getOrder(String orderNo) {
    Order order = orderRepository.findByOrderNo(orderNo);

    // 同步调用商品服务(网络调用,慢)
    Product product = productServiceClient.getProduct(order.getProductId());
    order.setProductName(product.getName());

    return order;
}

问题

  • 每次查询订单都要调用商品服务(性能差)
  • 商品服务挂了,订单查询也挂了(可用性差)

方案2:数据冗余(推荐)

/**
 * 订单实体:冗余商品名称
 */
@Entity
public class Order {
    private Long productId;       // 商品ID
    private String productName;   // 商品名称(冗余,快照)
    private BigDecimal price;     // 商品价格(冗余,快照)
}

/**
 * 创建订单时,冗余商品信息
 */
public Order createOrder(CreateOrderRequest request) {
    // 调用商品服务获取商品信息(只在创建时调用一次)
    Product product = productServiceClient.getProduct(request.getProductId());

    // 冗余商品信息到订单
    Order order = Order.builder()
            .productId(product.getId())
            .productName(product.getName())  // 冗余
            .price(product.getPrice())       // 冗余
            .build();

    return orderRepository.save(order);
}

/**
 * 查询订单时,直接返回冗余的商品名称(不调用商品服务)
 */
public Order getOrder(String orderNo) {
    return orderRepository.findByOrderNo(orderNo);
}

优势

  • 查询订单时不需要调用商品服务(性能好)
  • 商品服务挂了,订单查询不受影响(可用性好)
  • 订单保留下单时的商品信息(业务需求,即使商品改名,订单显示下单时的名称)

4.3 维度3:团队规模(Team Size)

核心思想:团队规模决定服务拆分粒度。

团队规模与服务数量的对应关系

团队规模服务数量每个服务的团队拆分策略
< 15人1-3个5-15人粗粒度拆分(或单体)
15-30人3-6个5-10人中等粒度拆分
30-60人6-12个5-10人细粒度拆分
> 60人> 12个5-10人极细粒度拆分

原则:每个服务由一个两个披萨团队(5-10人)负责。

案例:美团的演进

2012年(团队50人)

  • 服务数量:3个(前台、后台、数据)
  • 每个服务团队:15-20人
  • 问题:团队太大,沟通成本高

2015年(团队200人)

  • 服务数量:20个(订单、商品、用户、支付、物流等)
  • 每个服务团队:10人
  • 改进:团队规模合理,沟通成本低

2020年(团队500人)

  • 服务数量:100+个
  • 每个服务团队:5-8人
  • 改进:服务细粒度拆分,团队高度自治

4.4 维度4:变更频率(Change Frequency)

核心思想:变更频率高的模块应该拆分为独立服务。

变更频率分析

以电商系统为例:

模块变更频率拆分优先级原因
订单服务每周10次业务规则频繁变化
推荐服务每天5次算法频繁调整
搜索服务每周5次搜索策略频繁优化
商品服务每周3次属性偶尔新增
用户服务每月1次用户模型稳定
支付服务每季度1次支付流程稳定

拆分策略

  1. 高频变更模块:优先拆分(订单、推荐、搜索)
  2. 低频变更模块:延后拆分(用户、支付)

收益

  • 订单服务独立部署,每周可以发布10次(不影响其他服务)
  • 用户服务稳定,每月只发布1次(减少运维成本)

五、服务拆分的七个反模式

反模式1:按技术层拆分

错误做法

前端服务(Vue.js)
    ↓
业务服务(Spring Boot)
    ↓
数据服务(MyBatis)
    ↓
数据库(MySQL)

问题

  1. 任何需求都要改三个服务(前端、业务、数据)
  2. 三个服务共享同一个数据库(数据耦合)
  3. 部署时必须三个服务一起部署(部署耦合)

正确做法:按业务领域拆分

订单服务(Vue + Spring Boot + MySQL)
商品服务(Vue + Spring Boot + MySQL)
用户服务(Vue + Spring Boot + MySQL)

反模式2:过度拆分(纳米服务)

错误做法:拆分成100+个服务

订单创建服务
订单查询服务
订单取消服务
订单支付服务
订单发货服务
...(100+个服务)

问题

  1. 运维复杂度爆炸(100个服务 × 3个环境 = 300套部署)
  2. 调用链路过长(一个请求调用10+个服务)
  3. 分布式事务噩梦(100+个服务如何保证一致性?)

正确做法:合理粒度

订单服务(包含创建、查询、取消、支付、发货等功能)

判断标准

  • 一个服务的代码量:5000-50000行
  • 一个服务的团队:5-10人
  • 一个服务的部署频率:每周1-10次

反模式3:数据库共享

错误做法:多个服务共享同一个数据库

订单服务 ----┐
            |
商品服务 ---→ MySQL(共享数据库)
            |
用户服务 ----┘

问题

  1. 表结构变更影响所有服务
  2. 数据库成为单点故障
  3. 服务间通过数据库耦合(订单服务直接查询商品表)

正确做法:每个服务独立数据库

订单服务 → Order_DB
商品服务 → Product_DB
用户服务 → User_DB

反模式4:分布式单体

错误做法:形式上是微服务,实质上是单体

订单服务 ←→ 商品服务 ←→ 用户服务 ←→ 支付服务

特征:
1. 服务间强依赖(必须同时部署)
2. 共享数据库(数据耦合)
3. 同步调用(一个服务挂了,全部挂)

问题

  • 有微服务的复杂度(网络调用、分布式事务)
  • 没有微服务的优势(无法独立部署、独立扩展)

正确做法:服务自治

订单服务 → Order_DB(独立数据库)
  ↓ 异步消息
商品服务 → Product_DB(独立数据库)
  ↓ 异步消息
物流服务 → Logistics_DB(独立数据库)

特征:
1. 服务间解耦(通过异步消息)
2. 独立数据库(数据独立)
3. 可以独立部署(互不影响)

反模式5:忽略团队边界

错误做法:服务边界与团队边界不匹配

系统架构:
- 订单服务
- 商品服务
- 用户服务

团队结构:
- 前端团队(负责所有服务的前端)
- 后端团队(负责所有服务的后端)
- 测试团队(负责所有服务的测试)

问题

  • 任何需求都要跨团队协作
  • 康威定律失效

正确做法:服务边界与团队边界一致

系统架构:
- 订单服务

团队结构:
- 订单团队(全栈:前端 + 后端 + 测试 + 运维)

反模式6:忽略数据一致性

错误做法:拆分后没有考虑分布式事务

创建订单流程:
1. 订单服务:创建订单
2. 库存服务:扣减库存
3. 支付服务:调用支付

问题:如果第3步支付失败,前两步如何回滚?

解决方案

  • 使用Saga模式(补偿事务)
  • 使用TCC模式(两阶段提交)
  • 使用本地消息表(最终一致性)

反模式7:缺乏演进策略

错误做法:大爆炸式重构(一次性全部拆分)

第1周:制定拆分方案
第2-8周:拆分所有服务
第9周:上线

问题:
- 风险巨大(整个系统重写)
- 周期长(8周,业务停滞)
- 回退困难(已经拆分,无法回滚)

正确做法:渐进式演进

第1个月:拆分订单服务(变更频率最高)
第2个月:拆分商品服务
第3个月:拆分支付服务
...

策略:绞杀者模式(Strangler Pattern)

单体应用
    ↓
单体应用 + 订单服务(新功能走订单服务)
    ↓
单体应用 + 订单服务 + 商品服务
    ↓
订单服务 + 商品服务 + 用户服务(单体应用下线)

六、实战案例:电商系统的服务拆分演进

6.1 阶段0:单体应用(0-1年)

系统架构

                单体应用
            ┌─────────────┐
            │  订单模块   │
            │  商品模块   │
            │  用户模块   │
            │  支付模块   │
            │  物流模块   │
            └─────────────┘
                    ↓
               MySQL单库

团队规模:10人

问题

  • 启动时间:60秒
  • 部署时间:30分钟
  • 部署频率:每周1次

6.2 阶段1:拆分核心服务(1-2年)

拆分策略:优先拆分变更频率高的服务

                API网关
                    |
        ┌───────────┼───────────┬───────────┐
        ↓           ↓           ↓           ↓
    订单服务    商品服务    单体应用     Nacos
    (新功能)    (新功能)   (老功能)   (服务注册)
        ↓           ↓           ↓
    Order_DB   Product_DB    MySQL

团队规模:30人

  • 订单团队:8人
  • 商品团队:8人
  • 单体团队:14人

改进

  • 订单服务部署频率:每天3次
  • 商品服务部署频率:每天2次
  • 单体应用部署频率:每周1次

6.3 阶段2:全面微服务化(2-3年)

最终架构

                API网关
                    |
    ┌───────┬───────┼───────┬───────┬───────┐
    ↓       ↓       ↓       ↓       ↓       ↓
 订单服务 商品服务 用户服务 支付服务 物流服务  Nacos
    ↓       ↓       ↓       ↓       ↓
Order_DB Product_DB User_DB Pay_DB Logistics_DB

团队规模:50人

  • 订单团队:10人
  • 商品团队:10人
  • 用户团队:8人
  • 支付团队:8人
  • 物流团队:8人
  • 基础设施团队:6人

改进

  • 部署频率:每天20次(5个服务并行部署)
  • 故障隔离:某个服务挂了,其他服务不受影响
  • 技术栈自由:订单用Java,推荐用Python

七、总结与行动指南

7.1 核心观点

  1. 服务拆分的本质:按业务领域拆分,而不是按技术层拆分

  2. DDD是方法论:通过限界上下文、聚合根、领域事件识别服务边界

  3. 康威定律是指南:组织架构决定系统架构,要让团队边界与服务边界一致

  4. 四个拆分维度:业务能力、数据边界、团队规模、变更频率

  5. 七个反模式:避免按技术层拆分、过度拆分、数据库共享、分布式单体、忽略团队、忽略一致性、缺乏演进

7.2 决策框架

何时拆分服务?

指标单体架构微服务架构
团队规模< 15人> 30人
代码行数< 50万行> 50万行
启动时间< 5分钟> 10分钟
部署频率< 每周3次> 每天10次
变更冲突< 10%> 30%

如何拆分服务?

  1. 识别限界上下文(DDD)
  2. 分析业务能力(价值流分析)
  3. 确定数据边界(独立数据库)
  4. 匹配团队规模(两个披萨团队)
  5. 评估变更频率(高频模块优先拆分)

7.3 行动建议

如果你是初创公司架构师

  1. ✅ 先用单体架构(快速验证业务)
  2. ✅ 做好模块化(按DDD划分模块)
  3. ✅ 识别限界上下文(为未来拆分做准备)

如果你是成长期公司架构师

  1. ✅ 评估是否需要拆分(用决策框架)
  2. ✅ 渐进式拆分(不要大爆炸式重构)
  3. ✅ 先拆变更频率高的服务(订单、推荐)

如果你是成熟期公司架构师

  1. ✅ 建立服务治理体系(注册中心、网关、监控)
  2. ✅ 优化服务粒度(合并过细的服务)
  3. ✅ 引入领域事件(解耦服务间依赖)

7.4 检查清单

拆分前检查

  • 是否识别了限界上下文?
  • 是否分析了业务能力?
  • 是否确定了数据边界?
  • 是否匹配了团队结构?
  • 是否评估了变更频率?
  • 是否制定了演进策略?

拆分后检查

  • 服务是否可以独立部署?
  • 服务是否可以独立扩展?
  • 服务是否拥有独立数据库?
  • 服务是否有明确的团队负责?
  • 服务间是否通过API通信?
  • 是否有分布式事务方案?
  • 是否有链路追踪和监控?

7.5 下一步

本文回答了"如何拆分服务",但还有三个核心问题:

  1. 如何保证服务间通信?(同步调用 vs 异步消息)
  2. 如何保证数据一致性?(分布式事务)
  3. 如何保证系统稳定性?(服务治理)

这些问题将在后续文章中详细展开:

  • 《服务间通信:同步调用、异步消息与事件驱动》
  • 《分布式事务:从ACID到BASE的演进》
  • 《服务治理:注册发现、负载均衡与熔断降级》

参考资料

  1. 《领域驱动设计》- Eric Evans
  2. 《实现领域驱动设计》- Vaughn Vernon
  3. 《微服务架构设计模式》- Chris Richardson
  4. 《微服务设计》- Sam Newman
  5. 康威定律论文 - Melvin Conway (1967)
  6. 亚马逊技术博客
  7. 美团技术博客

最后更新时间:2025-11-03