eBay在2008年公布了一个关于BASE准则提到一个分布式事务解决方案。eBay的方案其实是一个最终一致性方案,它主要采用消息队列来辅助实现事务控制流程,方案的核心是将需要分布式处理的任务通过消息队列的方式来异步执行,如果事务失败,则可以发起人工重试的纠正流程。人工重试被更多的应用于支付场景,通过对账系统对事后问题进行处理

    比如一个很常见的场景:某个用户产生了一笔交易,那么需要在交易表中增加记录,同时需要修改用户表的金额(余额),由于这两个表属于不同的远程服务,所以就会涉及到分布式事务与数据一致性的问题

    user(id, name, amt_sold, amt_bought)
    transaction(xid, seller_id, buyer_id, amount)
    begin;
    INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);
    UPDATE user SET amt_sold = amt_sold + $amount WHERE id = $seller_id;
    UPDATE user SET amt_bought = amt_bought + $amount WHERE id = $buyer_id;
    commit;

    那么在这里可以使用消息队列(MQ)来做

    先启动一个事务,更新交易表(transaction)后,并不直接更新user表,而是将要对user表进行的更新插入到消息队列中。
    目标系统收到该消息以后,启动本地事务去对用户表的余额做调整
    伪代码
    bool result=dao.updateTransaction();
    if(result){
    mq.send();
    }
    根据上面的伪代码的实现方案,可能出现几种情况
    1.数据库操作成功,向MQ中投递消息也成功
    2.操作数据库失败,不会向MQ中投递消息
    3.操作数据库成功,但是向MQ中投递消息时失败,向外抛出异常。数据库操作回滚
    对于上面几种情况,问题都不大。那么我们分析小消费端的问题
    1.消息出队列以后,消费者对应的业务操作要执行成功。如果执行失败,消息不能失效或者丢失。需要保证消息和业务操作一致
    2.尽量避免消息重复消费,如果重复消费,也不能影响业务的执行结果

    对于第一个问题,如何保证消息不丢失
    现在用的比较普遍的MQ都具有持久化消息的功能,如果消费者宕机或者消费失败,都可以执行重试机制

    对于如何避免消息的重复消费
    1.保证消费者的幂等性;也就是说如果队列中的消息因为网络异常导致发送多次的情况下,仍然需要保证消息被应用多次与应用一次产生的效果是一样的
    2.通过消费日志表来记录消费状态;增加一个message_applied(msg_id)表,用来记录已经被成功应用的消息。在目标系统执行更新操作之前,先检测该消息是否已经被消费过,消费完成后通过本地事务控制来更新这个“消费表状态”,用来避免消息重复消费问题

    上面这种方式是非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。
    各大知名的电商平台和互联网公司,几乎都是采用类似的设计思路来实现“最终一致性”的。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大