由于网上讨论的分布式事务方案是有带误导性质的,于是我有想法写这篇博文。从常见的2阶段提交TCC的不足分析,再到博文中提供的解决方案Saga Pattern优雅落地

2阶段提交

2阶段提交,本质上是通过减少系统间事务提交间隔时间来降低出错的可能,但作为严谨的系统,任何出错的可能都需要覆盖规避的,因为根据墨菲定律凡是可能出错的事,有很大几率会出错。

在Commit阶段可能出现的问题

  1. DB1提交成功,DB2 网络断开或故障
  2. DB1提交成功,DB2 事务超时
  3. 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应用如下:

各系统均为本地事务,事务内无外部交互,事件在本地事务提交后才会发送。当用户支付成功,后续系统一定能正确处理。如:订单状态变更、商品库存扣除。(库存超卖问题由订单创建前锁定占用库存或允许业务超卖)。

事件的定义

  1. 事件可以是MQ消息
  2. 事件可以是HTTP或其他RPC请求
  3. 事件能被系统正确识别均可,根据具体情况调整

事件故障的问题

本地事务提交成功后,事件本身可能在传输的过程中出错,如:后续系统Crash、网络错误。此时你应该想到通过事件补发可以解决,术语称之为补发/补偿

事件补发的问题

事件补发又会带来另一问题,如:TCP网络延时超过预期的Timeout时长,导致误判成网络错误,触发事件补发后,导致业务系统收到2次事件(如:商品库存扣2次)。因此业务系统需要保证同一个事件有且仅处理一次,术语称之为幂等

所以只要业务能接受短期不一致(时间长短与事件补发和系统处理耗时有关),Saga Pattern的解决方案就可行。并且由于减少了同步请求链路,该设计为事件驱动异步化的,所以系统整体性能会提升不少。

关键点总结

  1. 同一个事务内,不能有其他I/O操作(硬盘、内存、网络)
  2. 业务接受数据短期内不一致,即接受最终一致性
  3. 补发解决事件故障
  4. 幂等避免同步事件重复处理

如何优雅的落地

目标

  1. Saga Pattern不能给研发人员带来负担
  2. 易使用、易理解、优雅

成品代码样例

//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完成。根据需要控制补发时间间隔,重试次数。
// 这样即可达成优雅落地的目标,对研发人员无负担,易使用。

相关资料

Saga Pattern