服务拆分原则
一、何时拆分微服务?
单体 vs 微服务
┌─────────────────────────────────────────────────────────────────┐
│ 单体架构 vs 微服务架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 单体架构 微服务架构 │
│ ┌─────────────────────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ │ │用户 │ │订单 │ │支付 │ │
│ │ 用户模块 │ │服务 │ │服务 │ │服务 │ │
│ │ ───────── │ └──┬──┘ └──┬──┘ └──┬──┘ │
│ │ 订单模块 │ │ │ │ │
│ │ ───────── │ ▼ ▼ ▼ │
│ │ 支付模块 │ ┌─────────────────────┐ │
│ │ ───────── │ │ 消息队列/网关 │ │
│ │ │ └─────────────────────┘ │
│ └─────────────────────┘ │
│ │
│ 优势: 优势: │
│ • 简单、开发快 • 独立部署、独立扩展 │
│ • 本地调用、无网络开销 • 技术栈灵活 │
│ • 事务简单 • 故障隔离 │
│ • 运维简单 • 团队自治 │
│ │
│ 劣势: 劣势: │
│ • 扩展受限 • 分布式复杂性 │
│ • 技术栈固定 • 运维成本高 │
│ • 故障影响全局 • 数据一致性难 │
│ • 团队协作困难 • 网络开销 │
│ │
└─────────────────────────────────────────────────────────────────┘拆分时机判断
┌─────────────────────────────────────────────────────────────────┐
│ 是否需要拆分微服务? │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ✅ 适合拆分的场景: │
│ ──────────────────────────────────────── │
│ □ 团队规模 > 10 人,协作困难 │
│ □ 不同模块需要独立扩展 (如搜索需要 20 实例,用户只需 2 个) │
│ □ 不同模块技术栈需求不同 (如 AI 模块用 Python) │
│ □ 需要独立发布,减少发布风险 │
│ □ 故障需要隔离,核心业务不受非核心影响 │
│ □ 有明确的业务边界 (如电商: 用户、商品、订单、支付) │
│ │
│ ❌ 不适合拆分的场景: │
│ ──────────────────────────────────────── │
│ □ 团队规模 < 5 人 │
│ □ 业务还在快速变化,边界不清晰 │
│ □ 单体性能足够,没有扩展需求 │
│ □ 没有分布式基础设施 (服务发现、网关、监控) │
│ □ 团队没有微服务经验 │
│ │
│ ⚠️ 提醒: 微服务不是银弹 │
│ ──────────────────────────────────────── │
│ "如果你的单体架构做得不好,微服务只会让它更糟糕" │
│ - 先优化单体,再考虑拆分 │
│ │
└─────────────────────────────────────────────────────────────────┘二、拆分原则
1. DDD 领域驱动设计
┌─────────────────────────────────────────────────────────────────┐
│ 基于领域驱动的服务拆分 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 识别限界上下文 (Bounded Context) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 电商系统领域 │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 用户上下文 │ │ 商品上下文 │ │ 订单上下文 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • 用户 │ │ • 商品 │ │ • 订单 │ │ │
│ │ │ • 地址 │ │ • 分类 │ │ • 订单项 │ │ │
│ │ │ • 会员 │ │ • 库存 │ │ • 物流 │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 支付上下文 │ │ 营销上下文 │ │ 搜索上下文 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • 支付单 │ │ • 优惠券 │ │ • 索引 │ │ │
│ │ │ • 退款 │ │ • 活动 │ │ • 搜索 │ │ │
│ │ │ • 对账 │ │ • 积分 │ │ • 推荐 │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Step 2: 一个限界上下文 = 一个微服务 │
│ ──────────────────────────────────────── │
│ 用户上下文 → user-service │
│ 商品上下文 → product-service │
│ 订单上下文 → order-service │
│ 支付上下文 → payment-service │
│ ... │
│ │
└─────────────────────────────────────────────────────────────────┘2. 单一职责原则
┌─────────────────────────────────────────────────────────────────┐
│ 服务的单一职责 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ❌ 错误: 服务职责过多 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ order-service │ │
│ │ ├─ 订单管理 │ │
│ │ ├─ 库存扣减 ← 应该属于 inventory-service │ │
│ │ ├─ 支付处理 ← 应该属于 payment-service │ │
│ │ ├─ 物流查询 ← 应该属于 logistics-service │ │
│ │ └─ 发送通知 ← 应该属于 notification-service │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ✅ 正确: 职责单一 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ order-service │ │
│ │ ├─ 创建订单 │ │
│ │ ├─ 查询订单 │ │
│ │ ├─ 取消订单 │ │
│ │ └─ 订单状态管理 │ │
│ │ │ │
│ │ 调用其他服务: │ │
│ │ ├─ inventory-service.deduct() 扣减库存 │ │
│ │ ├─ payment-service.create() 创建支付 │ │
│ │ └─ notification-service.send() 发送通知 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 判断标准: 修改一个功能时,是否只需要改一个服务? │
│ │
└─────────────────────────────────────────────────────────────────┘3. 服务自治原则
┌─────────────────────────────────────────────────────────────────┐
│ 服务自治原则 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 数据自治 - 每个服务有自己的数据库 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ❌ 共享数据库 ✅ 数据库隔离 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ │
│ │ │ Service │ │ Service │ │ Service │ │ Service ││ │
│ │ │ A │ │ B │ │ A │ │ B ││ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘│ │
│ │ │ │ │ │ │ │
│ │ └─────┬──────┘ ▼ ▼ │ │
│ │ ▼ ┌─────────┐ ┌─────────┐│ │
│ │ ┌─────────┐ │ DB │ │ DB ││ │
│ │ │ DB │ │ A │ │ B ││ │
│ │ └─────────┘ └─────────┘ └─────────┘│ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 2. 部署自治 - 独立部署,不影响其他服务 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 发布 order-service v2.0 │ │
│ │ ├─ user-service 不受影响 ✓ │ │
│ │ ├─ product-service 不受影响 ✓ │ │
│ │ └─ payment-service 不受影响 ✓ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 3. 技术自治 - 可以选择不同的技术栈 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ user-service → Go + MySQL │ │
│ │ search-service → Java + Elasticsearch │ │
│ │ ml-service → Python + TensorFlow │ │
│ │ realtime-service → Node.js + Redis │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘三、拆分策略
1. 绞杀者模式 (Strangler Fig Pattern)
┌─────────────────────────────────────────────────────────────────┐
│ 绞杀者模式演进 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1: 在单体旁边部署新服务 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 用户 ──▶ 负载均衡 │ │
│ │ │ │ │
│ │ ├──▶ 单体应用 (所有功能) │ │
│ │ │ ├─ 用户模块 │ │
│ │ │ ├─ 订单模块 │ │
│ │ │ └─ 支付模块 │ │
│ │ │ │ │
│ │ └──▶ 新用户服务 (独立出来) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 阶段 2: 逐步迁移流量 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ /api/users/* ──▶ 新用户服务 (100%) │ │
│ │ /api/orders/* ──▶ 单体应用 │ │
│ │ /api/payments/* ──▶ 单体应用 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 阶段 3: 持续拆分,直到单体消失 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ /api/users/* ──▶ user-service │ │
│ │ /api/orders/* ──▶ order-service │ │
│ │ /api/payments/* ──▶ payment-service │ │
│ │ │ │
│ │ 单体应用 (已废弃) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘2. 分支抽象模式 (Branch by Abstraction)
go
// Step 1: 定义抽象接口
type UserRepository interface {
GetByID(id string) (*User, error)
Create(user *User) error
}
// Step 2: 实现旧版本 (访问本地数据库)
type LocalUserRepository struct {
db *sql.DB
}
func (r *LocalUserRepository) GetByID(id string) (*User, error) {
return r.db.Query("SELECT * FROM users WHERE id = ?", id)
}
// Step 3: 实现新版本 (调用新服务)
type RemoteUserRepository struct {
client UserServiceClient
}
func (r *RemoteUserRepository) GetByID(id string) (*User, error) {
return r.client.GetUser(context.Background(), id)
}
// Step 4: 使用特性开关切换
type SwitchableUserRepository struct {
local UserRepository
remote UserRepository
toggle FeatureToggle
}
func (r *SwitchableUserRepository) GetByID(id string) (*User, error) {
if r.toggle.IsEnabled("use_remote_user_service") {
return r.remote.GetByID(id)
}
return r.local.GetByID(id)
}
// Step 5: 全量切换后,删除旧实现四、数据拆分
1. 数据迁移策略
┌─────────────────────────────────────────────────────────────────┐
│ 数据拆分策略 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 方式 1: 共享数据库 → 逻辑隔离 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 同一数据库,不同 Schema │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ MySQL │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ user_db │ │ order_db │ │ payment_db│ │ │ │
│ │ │ │ (Schema) │ │ (Schema) │ │ (Schema) │ │ │ │
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 优点: 简单、事务可跨 Schema │ │
│ │ 缺点: 仍然耦合、资源竞争 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 方式 2: 物理隔离 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ MySQL │ │ MySQL │ │ MySQL │ │ │
│ │ │ user_db │ │ order_db │ │ payment_db│ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ │ │ │
│ │ 优点: 完全隔离、独立扩展 │ │
│ │ 缺点: 跨服务查询困难、事务复杂 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘2. 跨服务查询
go
// 问题: 如何实现跨服务的联表查询?
// 原来: SELECT * FROM orders o JOIN users u ON o.user_id = u.id
// 方案 1: API 组合
func GetOrderWithUser(orderID string) (*OrderWithUser, error) {
// 1. 查询订单
order, err := orderService.GetOrder(orderID)
if err != nil {
return nil, err
}
// 2. 查询用户
user, err := userService.GetUser(order.UserID)
if err != nil {
return nil, err
}
return &OrderWithUser{
Order: order,
User: user,
}, nil
}
// 方案 2: 数据冗余
// 订单表中冗余用户信息
type Order struct {
ID string
UserID string
UserName string // 冗余字段
// ...
}
// 方案 3: CQRS - 读写分离
// 写: 分散在各个服务
// 读: 构建统一的查询模型
type OrderQueryService struct {
esClient *elasticsearch.Client
}
func (s *OrderQueryService) SearchOrders(query SearchQuery) ([]OrderView, error) {
// 从 ES 查询,ES 中已包含订单+用户+商品信息
return s.esClient.Search(query)
}
// 通过事件同步数据到 ES
func (s *OrderQueryService) OnOrderCreated(event OrderCreatedEvent) {
orderView := buildOrderView(event)
s.esClient.Index(orderView)
}五、服务通信
1. 同步 vs 异步
┌─────────────────────────────────────────────────────────────────┐
│ 服务通信方式选择 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 同步通信 (HTTP/gRPC) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Service A ──────请求──────▶ Service B │ │
│ │ ◀─────响应─────── │ │
│ │ │ │
│ │ 适用场景: │ │
│ │ • 需要立即返回结果 (如: 查询用户信息) │ │
│ │ • 调用链短 (2-3 层) │ │
│ │ • 强一致性要求 │ │
│ │ │ │
│ │ 缺点: │ │
│ │ • 服务耦合 │ │
│ │ • 被调用方故障影响调用方 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 异步通信 (消息队列) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Service A ──────发送消息──────▶ MQ ──▶ Service B │ │
│ │ (不等待响应) │ │
│ │ │ │
│ │ 适用场景: │ │
│ │ • 不需要立即返回结果 (如: 发送通知) │ │
│ │ • 解耦服务 │ │
│ │ • 削峰填谷 │ │
│ │ • 最终一致性可接受 │ │
│ │ │ │
│ │ 缺点: │ │
│ │ • 复杂度增加 │ │
│ │ • 消息顺序、重复处理 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘2. 事件驱动架构
go
// 事件定义
type OrderCreatedEvent struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Items []Item `json:"items"`
Amount float64 `json:"amount"`
CreatedAt time.Time `json:"created_at"`
}
// 订单服务 - 发布事件
type OrderService struct {
repo OrderRepository
publisher EventPublisher
}
func (s *OrderService) CreateOrder(order *Order) error {
// 保存订单
if err := s.repo.Save(order); err != nil {
return err
}
// 发布事件
event := OrderCreatedEvent{
OrderID: order.ID,
UserID: order.UserID,
Items: order.Items,
Amount: order.Amount,
CreatedAt: time.Now(),
}
return s.publisher.Publish("order.created", event)
}
// 库存服务 - 订阅事件
type InventoryService struct {
repo InventoryRepository
subscriber EventSubscriber
}
func (s *InventoryService) Start() {
s.subscriber.Subscribe("order.created", s.handleOrderCreated)
}
func (s *InventoryService) handleOrderCreated(event OrderCreatedEvent) error {
for _, item := range event.Items {
if err := s.repo.Deduct(item.ProductID, item.Quantity); err != nil {
return err
}
}
return nil
}
// 通知服务 - 也订阅同一事件
type NotificationService struct {
sender EmailSender
subscriber EventSubscriber
}
func (s *NotificationService) Start() {
s.subscriber.Subscribe("order.created", s.handleOrderCreated)
}
func (s *NotificationService) handleOrderCreated(event OrderCreatedEvent) error {
return s.sender.SendOrderConfirmation(event.UserID, event.OrderID)
}六、拆分检查清单
拆分前评估
- [ ] 团队规模是否支持微服务?
- [ ] 业务边界是否清晰?
- [ ] 基础设施是否准备好?
- [ ] 团队是否有微服务经验?
拆分过程检查
- [ ] 是否基于业务领域拆分?
- [ ] 每个服务是否职责单一?
- [ ] 服务之间是否松耦合?
- [ ] 数据是否独立?
拆分后验证
- [ ] 服务可以独立部署吗?
- [ ] 单个服务故障是否影响其他服务?
- [ ] 服务可以独立扩展吗?
- [ ] 团队可以自主开发部署吗?
💬 讨论
使用 GitHub 账号登录后即可参与讨论