分布式事务解决方案与优雅落地
由于网上讨论的分布式事务方案是有带误导性质的,于是我有想法写这篇博文。从常见的2阶段提交和TCC的不足分析,再到博文中提供的解决方案Saga Pattern与优雅落地。
2阶段提交
2阶段提交,本质上是通过减少系统间事务提交间隔时间来降低出错的可能,但作为严谨的系统,任何出错的可能都需要覆盖规避的,因为根据墨菲定律凡是可能出错的事,有很大几率会出错。
在Commit阶段可能出现的问题
- DB1提交成功,DB2 网络断开或故障
- DB1提交成功,DB2 事务超时
- DB1提交成功,DB2 T1事务提交时,由于T2事务先提交,T1事务违反约束(如唯一约束)
N阶段提交出错证明:
因为最后一阶段为Commit,设DB1、DB2、DB3操作序列分别为(X + Commit)、(Y + Commit)、(Z + Commit)。X、Y、Z可为任意事务控制方案。因为最后的操作为Commit,那么部分DB提交就可能失败,原因如上在Commit阶段可能出现的问题。
Try-Confirm/Cancel(TCC)
TCC也有类似问题,当调用方Crash,无法触发Cancel出错后,或许还能通过“业务超时”回退(补偿)。但调用方成功触发Confirm后,因网络或其他原因再Crash,数据就不一致了。也许能通过数据核实的方案去核对回滚,但是不够优雅。
Saga Pattern
Saga Pattern是分布式事务的一个解决方案,并不是一种技术框架。 他的理念很简单,让各业务系统同一个事务内,不涉及其他I/O操作。以周知电商业务为例,用户支付订单,商品扣库存,Saga Pattern应用如下:
各系统均为本地事务,事务内无外部交互,事件在本地事务提交后才会发送。当用户支付成功,后续系统一定能正确处理。如:订单状态变更、商品库存扣除。(库存超卖问题由订单创建前锁定占用库存或允许业务超卖)。
事件的定义
- 事件可以是MQ消息
- 事件可以是HTTP或其他RPC请求
- 事件能被系统正确识别均可,根据具体情况调整
事件故障的问题
本地事务提交成功后,事件本身可能在传输的过程中出错,如:后续系统Crash、网络错误。此时你应该想到通过事件补发可以解决,术语称之为补发/补偿。
事件补发的问题
事件补发又会带来另一问题,如:TCP网络延时超过预期的Timeout时长,导致误判成网络错误,触发事件补发后,导致业务系统收到2次事件(如:商品库存扣2次)。因此业务系统需要保证同一个事件有且仅处理一次,术语称之为幂等。
所以只要业务能接受短期不一致(时间长短与事件补发和系统处理耗时有关),Saga Pattern的解决方案就可行。并且由于减少了同步请求链路,该设计为事件驱动、异步化的,所以系统整体性能会提升不少。
关键点总结
- 同一个事务内,不能有其他I/O操作(硬盘、内存、网络)
- 业务接受数据短期内不一致,即接受最终一致性
- 补发解决事件故障
- 幂等避免同步事件重复处理
如何优雅的落地
目标
- Saga Pattern不能给研发人员带来负担
- 易使用、易理解、优雅
成品代码样例
//1.支付事件简要示例
@Amqp(type = AmqpType.QUEUE)
public interface PaymentEvent{
//支付完成事件
void paid(PaidPayload payload);
}
//2. 支付服务示例 (代码所属支付系统)
@Serivce
public class PaymentServiceImpl implement PaymentService {
//自定义注解,Spring容器加载时,通过代理实现。class名、method名组合成队列名(框架完成)
//参考 org.springframework.beans.factory.config.BeanPostProcessor
@Producer
private PaymentEvent paymentEvent;
@Override
@Transactional(rollbackFor = Exception.class)
public Payment pay(...) {
...
//付款逻辑
...
//注:当前仅注册一个事务同步器,并不是立即发送消息。(框架完成)
//beformCommit: 本地事务提交前: 消息写入消息记录表,与当前事务关联,保证原子性。
//afterCommit: 本地事务提交后: 发送消息,保证及时到达。
//相关class
//org.springframework.transaction.support.TransactionSynchronizationManager
//org.springframework.transaction.support.TransactionSynchronization
paymentEvent.paid(PaidPayload);
return Payment;
}
}
//3. 订单服务接收消费支付成功事件 (代码所属订单系统)
//自定义注解,Spring容器加载时,根据class名、method名组合成队列名订阅消息(框架完成)
//参考 org.springframework.beans.factory.config.BeanPostProcessor
@Consumer
public class PaymentEventListener implement PaymentEvent {
@Inject
private OrderService orderSerivce;
// 接收消息并处理, 消息添加UUID, 保证幂等有且消费一次(框架完成)
@Override
@Transactional(rollbackFor = Exception.class)
void paid(PaidPayload payload) {
...
orderSerivce.methodA();
...
}
}
// 事件的补发机制,由定时任务,JOB完成。根据需要控制补发时间间隔,重试次数。
// 这样即可达成优雅落地的目标,对研发人员无负担,易使用。相关资料