第2章:服务拆分与边界
服务拆分原则
1. 单一职责原则(Single Responsibility Principle)
定义:一个服务应该只有一个引起它变化的原因
错误示例:
UserService包含:
- 用户注册/登录
- 用户资料管理
- 用户积分管理
- 用户消息通知
问题:职责过多,变化原因太多
正确示例:
UserService: 用户基本信息(注册、登录、资料)
PointService: 积分管理
NotificationService: 消息通知
每个服务职责单一明确
2. 高内聚低耦合
高内聚:相关功能聚集在一起
订单服务包含:
- 创建订单
- 修改订单
- 取消订单
- 查询订单
这些都是订单相关的操作,应该在一起
低耦合:服务间依赖最小化
订单服务 → 直接访问用户数据库
订单服务 → 调用用户服务API
通过API通信,而非直接访问数据库
3. 业务能力对齐
按业务能力拆分,而非技术层次
错误拆分(按技术层):
ControllerService: 所有Controller
ServiceLayerService: 所有业务逻辑
DAOService: 所有数据访问
问题:
- 跨服务调用频繁
- 业务变更涉及多个服务
- 违反高内聚原则
正确拆分(按业务能力):
UserService: 用户管理
- UserController
- UserService
- UserRepository
OrderService: 订单管理
- OrderController
- OrderService
- OrderRepository
优势:
- 业务内聚
- 团队自治
- 变更影响范围小
4. 领域驱动设计(DDD)
按限界上下文拆分
电商系统的限界上下文:
┌──────────────────┐
│ 用户上下文 │
│ User: 认证信息 │
└──────────────────┘
┌──────────────────┐
│ 订单上下文 │
│ User: 收货信息 │
│ Order: 订单详情 │
└──────────────────┘
┌──────────────────┐
│ 营销上下文 │
│ User: 标签、画像 │
└──────────────────┘
同一个"User"在不同上下文中含义不同!
边界识别方法
方法1:业务流程分析
步骤:
- 绘制业务流程图
用户注册流程:
1. 填写注册信息
2. 验证手机号
3. 创建账户
4. 发送欢迎邮件
可拆分为:
- 用户服务:创建账户
- 短信服务:验证手机号
- 邮件服务:发送邮件
- 识别业务边界
下单流程:
1. 选择商品 → 商品服务
2. 确认订单 → 订单服务
3. 扣减库存 → 库存服务
4. 创建支付 → 支付服务
自然边界:商品、订单、库存、支付
方法2:数据边界分析
原则:独立数据库 = 服务边界
-- 用户数据
users表:
id, name, email, phone
-- 订单数据
orders表:
id, user_id, total, status
order_items表:
id, order_id, product_id, quantity
规则:
- users → User Service
- orders + order_items → Order Service
- 通过user_id关联,而非JOIN
方法3:团队组织结构
康威定律:系统架构反映组织沟通结构
组织结构:
- 用户团队(5人)
- 订单团队(8人)
- 商品团队(6人)
- 支付团队(4人)
服务拆分应对齐团队:
- User Service → 用户团队
- Order Service → 订单团队
- Product Service → 商品团队
- Payment Service → 支付团队
方法4:变化频率分析
高变化vs低变化
高变化频率:
- 营销活动(每周变化)→ MarketingService
- 推荐算法(频繁调整)→ RecommendationService
低变化频率:
- 用户认证(稳定)→ AuthService
- 基础商品信息(稳定)→ ProductService
策略:
高变化的功能独立成服务,便于快速迭代
拆分粒度控制
粒度权衡
过大的服务(单体趋势):
ECommerceService包含:
- 用户管理
- 商品管理
- 订单管理
- 支付管理
- 物流管理
问题:
- 失去微服务优势
- 团队耦合
- 难以独立扩展
过小的服务(纳米服务):
CreateOrderService: 只负责创建订单
UpdateOrderService: 只负责更新订单
CancelOrderService: 只负责取消订单
问题:
- 服务数量爆炸
- 网络开销大
- 分布式事务复杂
- 运维成本高
合理粒度:
OrderService包含:
- 创建订单
- 更新订单
- 取消订单
- 查询订单
黄金法则:
- 2-pizza团队可维护(5-10人)
- 代码量:几千到几万行
- 部署时间:< 15分钟
拆分粒度指标
量化指标:
| 维度 | 推荐值 | 说明 |
|---|---|---|
| 团队规模 | 2-8人 | 2-pizza原则 |
| 代码行数 | 5k-50k | 过大则拆分 |
| API数量 | 5-20个 | 过多则职责不清 |
| 数据表数量 | 3-15个 | 独立数据库 |
| 部署时间 | < 15分钟 | 快速部署 |
| 依赖服务数 | < 5个 | 避免过度耦合 |
常见拆分陷阱
陷阱1:按技术分层拆分
错误示例:
ControllerService
BusinessLogicService
DatabaseService
问题:
- 违反业务内聚
- 跨服务调用频繁
- 网络开销大
正确做法:
UserService (包含Controller + Service + Repository)
OrderService (完整业务能力)
陷阱2:过度拆分
案例:
某公司将用户服务拆分为:
- UserRegistrationService
- UserLoginService
- UserProfileService
- UserPasswordService
- UserAvatarService
... 共20个服务
结果:
服务数量失控
分布式事务复杂
运维成本暴增
解决:
合并为UserService
包含所有用户相关功能
陷阱3:循环依赖
错误设计:
OrderService → UserService (获取用户信息)
UserService → OrderService (获取订单数量)
问题:
- 循环依赖
- 部署困难
- 故障传播
解决方案:
方案1:引入中间服务
OrderService → UserProfileService
UserService → UserProfileService
UserProfileService聚合用户+订单信息
方案2:事件驱动
OrderService: 创建订单 → 发布OrderCreatedEvent
UserService: 监听OrderCreatedEvent → 更新订单数量
异步解耦,消除循环依赖
陷阱4:共享数据库
错误做法:
OrderService ─┐
ProductService├→ Shared Database
UserService ─┘
问题:
- 紧耦合
- schema变更影响多个服务
- 无法独立扩展数据库
正确做法:
OrderService → Order DB
ProductService → Product DB
UserService → User DB
每个服务独立数据库
实战案例
电商系统服务拆分
业务分析:
核心业务流程:
1. 用户浏览商品
2. 加入购物车
3. 创建订单
4. 支付
5. 发货
6. 确认收货
初步拆分:
核心域(Core Domain):
Product Service: 商品管理
Order Service: 订单管理
Payment Service: 支付管理
支撑域(Supporting Domain):
User Service: 用户管理
Inventory Service: 库存管理
Shipping Service: 物流管理
通用域(Generic Domain):
Notification Service: 通知服务
Search Service: 搜索服务
服务详细设计:
1. Product Service(商品服务)
// API接口
type ProductService interface {
// 商品管理
CreateProduct(product *Product) error
UpdateProduct(id string, product *Product) error
DeleteProduct(id string) error
// 商品查询
GetProduct(id string) (*Product, error)
ListProducts(page, pageSize int) ([]*Product, error)
SearchProducts(keyword string) ([]*Product, error)
}
// 数据模型
type Product struct {
ID string
Name string
Description string
Price decimal.Decimal
CategoryID string
Stock int
Images []string
Status ProductStatus
CreatedAt time.Time
UpdatedAt time.Time
}
// 数据库表
products:
id, name, description, price, category_id, stock, status, created_at, updated_at
product_images:
id, product_id, image_url, sort_order
categories:
id, name, parent_id, level
2. Order Service(订单服务)
// API接口
type OrderService interface {
// 订单操作
CreateOrder(req CreateOrderRequest) (*Order, error)
CancelOrder(orderID string) error
// 订单查询
GetOrder(orderID string) (*Order, error)
ListUserOrders(userID string, page, pageSize int) ([]*Order, error)
}
// 数据模型
type Order struct {
ID string
UserID string // 引用User Service
Items []OrderItem
TotalAmount decimal.Decimal
Status OrderStatus
ShippingAddress Address
CreatedAt time.Time
}
type OrderItem struct {
ProductID string // 引用Product Service
ProductName string // 冗余,避免查询Product
Quantity int
Price decimal.Decimal
Subtotal decimal.Decimal
}
// 数据库表
orders:
id, user_id, total_amount, status, shipping_address, created_at
order_items:
id, order_id, product_id, product_name, quantity, price, subtotal
3. Payment Service(支付服务)
// API接口
type PaymentService interface {
CreatePayment(req CreatePaymentRequest) (*Payment, error)
QueryPayment(paymentID string) (*Payment, error)
RefundPayment(paymentID string, amount decimal.Decimal) error
}
// 数据模型
type Payment struct {
ID string
OrderID string // 引用Order Service
Amount decimal.Decimal
Method PaymentMethod
Status PaymentStatus
TransactionID string // 第三方支付流水号
CreatedAt time.Time
}
// 数据库表
payments:
id, order_id, amount, method, status, transaction_id, created_at
payment_logs:
id, payment_id, action, result, message, created_at
服务间交互:
下单流程(Saga模式):
1. OrderService.CreateOrder()
↓
2. ProductService.CheckStock() [同步调用]
↓
3. OrderService保存订单(状态:待支付)
↓
4. 发布OrderCreatedEvent
↓
5. InventoryService监听 → 预扣库存
↓
6. PaymentService.CreatePayment()
↓
7. 发布PaymentCompletedEvent
↓
8. OrderService监听 → 更新订单状态
↓
9. InventoryService监听 → 确认扣库存
代码实现:
// 订单服务创建订单
func (svc *OrderService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*Order, error) {
// 1. 验证商品库存(同步调用Product Service)
for _, item := range req.Items {
available, err := svc.productClient.CheckStock(ctx, item.ProductID, item.Quantity)
if err != nil || !available {
return nil, errors.New("insufficient stock")
}
}
// 2. 创建订单(状态:待支付)
order := &Order{
ID: generateID(),
UserID: req.UserID,
Items: req.Items,
Status: OrderStatusPending,
ShippingAddress: req.ShippingAddress,
CreatedAt: time.Now(),
}
// 计算总金额
order.calculateTotal()
// 3. 保存订单
if err := svc.orderRepo.Save(order); err != nil {
return nil, err
}
// 4. 发布事件(异步扣库存)
event := OrderCreatedEvent{
OrderID: order.ID,
Items: order.Items,
}
svc.eventBus.Publish(event)
return order, nil
}
// 库存服务监听订单创建事件
func (svc *InventoryService) OnOrderCreated(event OrderCreatedEvent) error {
// 预扣库存
for _, item := range event.Items {
if err := svc.ReserveStock(item.ProductID, item.Quantity); err != nil {
// 发布库存不足事件,触发订单取消
svc.eventBus.Publish(StockInsufficientEvent{
OrderID: event.OrderID,
ProductID: item.ProductID,
})
return err
}
}
return nil
}
面试问答
如何确定微服务的拆分粒度?
答案:
判断标准:
1. 团队维度:
2-pizza团队(5-10人)可维护
需要跨团队才能完成一个功能
2. 代码维度:
5k-50k行代码
超过10万行(考虑拆分)
3. 业务维度:
单一业务能力
多个业务能力混杂
4. 数据维度:
3-15个数据表
超过20个表(职责过多)
5. 变更维度:
变更影响范围小
改一个功能涉及多个服务
6. 部署维度:
部署时间 < 15分钟
部署复杂、耗时长
经验法则:
宁愿先大后小,不要先小后大
原因:
- 合并服务容易
- 拆分服务困难(数据迁移、事务处理)
建议:
从相对粗粒度开始,根据实际需要逐步拆分
服务间如何避免循环依赖?
答案:
识别循环依赖:
OrderService → UserService (获取用户信息)
UserService → OrderService (获取订单统计)
这就是循环依赖!
解决方案:
方案1:合并服务
如果两个服务相互依赖严重,考虑合并
OrderService + UserService → UserOrderService
适用:边界划分错误的情况
方案2:引入聚合服务
创建UserProfileService:
- 聚合用户信息
- 聚合订单统计
- 对外提供统一查询
OrderService → UserProfileService
UserService → UserProfileService
方案3:事件驱动(推荐)
// OrderService: 创建订单后发布事件
func (svc *OrderService) CreateOrder(...) {
// ... 创建订单
svc.eventBus.Publish(OrderCreatedEvent{
UserID: order.UserID,
})
}
// UserService: 监听事件,更新订单数
func (svc *UserService) OnOrderCreated(event OrderCreatedEvent) {
svc.userRepo.IncrementOrderCount(event.UserID)
}
优势:
异步解耦
无循环依赖
性能好
方案4:数据冗余
OrderService存储:
- user_id
- user_name (冗余)
- user_phone (冗余)
避免每次查询都调用UserService
适用:读多写少的场景
如何处理跨服务的数据一致性?
答案:
场景:
创建订单需要:
1. 创建订单(Order Service)
2. 扣减库存(Inventory Service)
3. 创建支付(Payment Service)
如何保证一致性?
方案对比:
| 方案 | 一致性 | 性能 | 复杂度 | 推荐 |
|---|---|---|---|---|
| 2PC | 强一致 | 低 | 高 | |
| Saga | 最终一致 | 高 | 中 | |
| TCC | 最终一致 | 中 | 高 | 金融场景 |
| 本地消息表 | 最终一致 | 高 | 低 |
推荐:Saga编排模式
// Saga协调器
type OrderSaga struct {
orderSvc *OrderService
inventorySvc *InventoryService
paymentSvc *PaymentService
}
func (saga *OrderSaga) Execute(req CreateOrderRequest) error {
var compensations []func() error
// 步骤1: 创建订单
order, err := saga.orderSvc.CreateOrder(req)
if err != nil {
return err
}
compensations = append(compensations, func() error {
return saga.orderSvc.CancelOrder(order.ID)
})
// 步骤2: 扣减库存
err = saga.inventorySvc.ReserveStock(req.Items)
if err != nil {
saga.compensate(compensations)
return err
}
compensations = append(compensations, func() error {
return saga.inventorySvc.ReleaseStock(req.Items)
})
// 步骤3: 创建支付
payment, err := saga.paymentSvc.CreatePayment(order.ID, order.TotalAmount)
if err != nil {
saga.compensate(compensations)
return err
}
return nil
}
func (saga *OrderSaga) compensate(compensations []func() error) {
// 倒序执行补偿
for i := len(compensations) - 1; i >= 0; i-- {
compensations[i]()
}
}
DDD中的限界上下文如何映射到微服务?
答案:
原则:一个限界上下文 = 一个微服务(或一组微服务)
示例:电商系统
限界上下文识别:
1. 用户上下文(User Context):
- 核心概念:User(认证、授权)
- 服务:User Service
2. 订单上下文(Order Context):
- 核心概念:Order、OrderItem
- 服务:Order Service
3. 商品上下文(Product Context):
- 核心概念:Product、Category
- 服务:Product Service
4. 库存上下文(Inventory Context):
- 核心概念:Stock、Warehouse
- 服务:Inventory Service
关键点:
1. 同一概念在不同上下文中含义不同:
User在用户上下文:
- ID、name、email、password
User在订单上下文:
- ID、name、phone、address
(只关心收货信息)
User在营销上下文:
- ID、tags、preferences、behavior
(只关心用户画像)
2. 上下文间通过ID引用:
Order.userID → 引用User Context
而非直接包含User对象
3. 上下文映射:
- Shared Kernel: 共享模型
- Customer-Supplier: 上下游关系
- Anti-Corruption Layer: 防腐层
什么时候应该拆分服务,什么时候应该合并服务?
答案:
拆分信号:
应该拆分的情况:
1. 服务过大:
代码超过5万行
团队超过10人
部署时间超过30分钟
2. 职责混乱:
一个服务包含多个业务能力
变更频繁互相影响
3. 扩展需求:
某个功能需要独立扩展
不同功能的资源需求差异大
4. 团队组织:
团队拆分,服务也应拆分
合并信号:
应该合并的情况:
1. 服务过小:
代码少于1000行
只有1-2个API
部署比开发还复杂
2. 频繁交互:
两个服务相互调用频繁
分布式事务复杂
网络开销大
3. 业务内聚:
两个服务总是一起变更
边界不清晰
4. 运维成本:
服务数量过多(>50个)
运维困难
决策流程:
问自己:
1. 拆分后是否更容易维护?
2. 拆分后团队是否更自治?
3. 拆分后扩展是否更灵活?
如果3个都是"是" → 拆分
否则 → 保持现状或合并