一、DDD与微服务为什么是天作之合
咱们先打个比方,DDD(领域驱动设计)就像城市规划师,把城市划分成商业区、住宅区、工业区;而微服务就是各个功能完善的社区,每个社区自己发电、自己供水。这俩凑一块儿,简直就是数字世界的黄金搭档。
举个电商系统的例子。用DDD划分出"订单"、"支付"、"库存"三个限界上下文,对应到微服务就是三个独立服务:
// 订单服务 (Java技术栈示例)
public class OrderService {
// 创建订单领域逻辑
public Order createOrder(Long userId, List<Item> items) {
// 验证用户
// 计算总价
// 生成订单号
// 持久化订单
}
// 领域事件发布
@Transactional
public void confirmOrder(Long orderId) {
Order order = repository.findById(orderId);
order.confirm();
eventPublisher.publish(new OrderConfirmedEvent(order));
}
}
// 库存服务
public class StockService {
// 库存扣减逻辑
@Transactional
public void deductStock(String sku, int quantity) {
// 检查库存
// 乐观锁更新
// 记录变更日志
}
}
这种组合的优势很明显:
- 业务边界清晰,每个服务专注自己的"一亩三分地"
- 团队可以按照领域划分,沟通成本直线下降
- 技术栈可以差异化,比如用Java写订单服务,用Golang写高并发的库存服务
二、分布式事务这个"拦路虎"
微服务拆分后最头疼的就是事务问题。想象一下:用户下单后要同时扣库存、生成订单、创建支付单,这三个操作分布在不同的服务里,怎么保证要么全成功要么全失败?
2.1 常见解决方案比武
先看张对比表:
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 差 | 高 | 银行转账等金融场景 |
| TCC | 最终 | 中 | 中 | 电商订单等高并发 |
| 本地消息表 | 最终 | 好 | 低 | 日志处理等 |
| Saga | 最终 | 好 | 中 | 长流程业务 |
2.2 TCC模式实战示例
用Java+Spring Cloud实现一个下单的TCC案例:
// 订单服务TCC接口
public interface OrderTccService {
@Transactional
@PostMapping("/try")
OrderTccResponse tryCreateOrder(@RequestBody OrderRequest request);
@PostMapping("/confirm")
boolean confirmCreateOrder(@RequestParam Long orderId);
@PostMapping("/cancel")
boolean cancelCreateOrder(@RequestParam Long orderId);
}
// 实现类关键代码
@Service
public class OrderTccServiceImpl {
private Map<Long, OrderTccRecord> tryRecords = new ConcurrentHashMap<>();
public OrderTccResponse tryCreateOrder(OrderRequest request) {
// 1. 预检查
if(request.getItems().isEmpty()) {
throw new IllegalArgumentException("商品不能为空");
}
// 2. 预留资源(冻结库存等)
OrderTccRecord record = new OrderTccRecord();
record.setStatus(TccStatus.TRY);
record.setOrderId(generateOrderId());
tryRecords.put(record.getOrderId(), record);
// 3. 返回预操作结果
return new OrderTccResponse(record.getOrderId());
}
public boolean confirmCreateOrder(Long orderId) {
// 获取预操作记录
OrderTccRecord record = tryRecords.get(orderId);
if(record == null || record.getStatus() != TccStatus.TRY) {
return false;
}
// 执行正式业务逻辑
Order order = buildOrder(record);
orderRepository.save(order);
// 更新状态
record.setStatus(TccStatus.CONFIRM);
return true;
}
// cancel方法类似,进行资源释放
}
这个实现有几个关键点:
- try阶段只是预留资源,不实际创建订单
- confirm/cancel阶段要保证幂等性
- 需要定时任务补偿悬挂的操作
三、Saga模式的另类解法
对于长业务流程,比如电商的"下单->支付->发货->确认收货"流程,用TCC就太重了。这时候Saga模式更合适。
3.1 事件驱动的Saga实现
用Spring Cloud Stream + Kafka实现:
// 订单服务事件发布
public class OrderService {
@Autowired
private StreamBridge streamBridge;
public void createOrder(Order order) {
// 保存订单
orderRepository.save(order);
// 发布订单创建事件
streamBridge.send("orderCreated-out-0",
new OrderCreatedEvent(order.getId(), order.getUserId(), order.getAmount()));
}
// 补偿方法
public void cancelOrder(Long orderId) {
// 订单状态更新为已取消
// 释放相关资源
}
}
// 支付服务事件处理
@Slf4j
@Service
public class PaymentListener {
@Autowired
private PaymentService paymentService;
@Bean
public Consumer<OrderCreatedEvent> handleOrderCreated() {
return event -> {
try {
paymentService.createPayment(event.getOrderId(), event.getAmount());
} catch (Exception e) {
// 发布支付失败事件
streamBridge.send("paymentFailed-out-0",
new PaymentFailedEvent(event.getOrderId()));
log.error("支付处理失败", e);
}
};
}
// 补偿事件处理
@Bean
public Consumer<DeliveryFailedEvent> handleDeliveryFailed() {
return event -> {
paymentService.refund(event.getOrderId());
};
}
}
这种模式的要点:
- 每个服务处理完自己的逻辑后发布下一个事件
- 出现异常时发布补偿事件
- 需要实现幂等和防重
四、实战中的避坑指南
4.1 选型决策树
遇到分布式事务问题时,可以这么选:
是否要求强一致?
├── 是 → 考虑2PC或本地事务+MQ事务消息
└── 否 → 业务流程是否长?
├── 是 → 考虑Saga模式
└── 否 → 并发量如何?
├── 高 → TCC模式
└── 低 → 本地消息表
4.2 必须知道的注意事项
- 幂等性设计:所有操作都要支持重复执行。比如支付服务的createPayment方法要判断是否已处理过该订单
- 空回滚问题:在TCC模式中,try阶段可能因为超时失败,但实际执行成功了,cancel时要做检查
- 悬挂控制:confirm/cancel可能比try先到,需要记录try状态
- 日志追踪:分布式事务一定要有完整的链路日志,建议用traceId串联
4.3 监控告警不可少
建议监控这些指标:
- 事务成功率
- 平均处理时长
- 补偿事务比例
- 悬挂事务数量
用Prometheus+Grafana配置个看板,出问题时能快速定位。
五、总结与展望
DDD和微服务配合使用,就像咖啡配奶泡——单独喝也不错,但搭配起来风味更佳。分布式事务虽然麻烦,但现在的解决方案已经比较成熟了。我的建议是:
- 简单场景用本地消息表
- 一般电商用TCC
- 复杂流程上Saga
- 金融级强一致才考虑2PC
未来随着Service Mesh等技术的发展,可能会涌现更优雅的解决方案。但无论技术怎么变,理解业务本质才是根本,这也是DDD给我们最大的启示。
评论