分布式事务方案整合

分布式事务方案整合

通过几天的资料查找,对解决分布式事务的方法有两阶段提交、支付宝分享的TCCtry-confirm-cancel)和基于消息的最终一致解决方案,其中第一条和第二条虽然也能解决问题,但普遍对第三种基于消息队列的最终一致解决方案推荐多比较高,所以第一条和第二条可以参考使用。

一、设计原则

业务拆分,架构设计时应“尽量避免”分布式事务,如果实在避免不了(这已经是高并发、用户量比较多的网站了)则使用“最终一致性”处理。

二、设计原理

2.1 CAP原理

  C(一致性)一致性是指数据的原子性,在经典的数据库中通过事务来保障,事务完成时,无论成功或回滚,数据都会处于一致的状态,在分布式环境下,一致性是指多个节点数据是否一致;

  A(可用性)服务一直保持可用的状态,当用户发出一个请求,服务能在一定的时间内返回结果;

  P(分区容忍性)在分布式应用中,可能因为一些分布式的原因导致系统无法运转,好的分区容忍性,使应用虽然是一个分布式系统,但是好像一个可以正常运转的整体

2.2 BASE原理

  1. BA: Basic Availability 基本业务可用性;

  2. S: Soft state 柔性状态,中间状态;

  3. E: Eventual consistency 最终一致性;

三、设计方案

3.1 两阶段提交

两阶段提交分为准备阶段和提交阶段两个阶段,其原理架构图如下:

3-1

概述:在提交事务的过程中需要在多个节点之间进行协调,而各节点对锁资源的释放必须等到事务最终提交时,比较耗时,锁资源发生冲突的概率增加,当事务的并发量达到一定数量的时候,就会出现大量事务积压甚至出现死锁,系统性能就会严重下滑。

3.2 TCCTry-Confirm-Cancel

TCC分三部分,如下所述:

1. Try: 尝试执行业务;

2. Confirm: 确认执行业务;

3. Cancel: 取消执行业务;

此方案开始由支付宝提出来的一种方案,在开源社区github上找到一个TCC型事务java实现(https://github.com/changmingxie/tcc-transaction),部署到本机环境测试了一下demo,可以实现分布式事务补偿机制,由于时间仓促,还没有来得及研究源码。

优点:现成的框架时间,使用不难,有开发使用文档,可以为我们以后设计分布式事务时提供一种设计思路和参考。

缺点:不是很成熟,关注度不是很高,可能会存在一些问题。

3.3 基于事务型消息队列的最终一致性

借助消息队列,在处理业务逻辑的地方,发送消息,业务逻辑处理成功后,提交消息,确保消息是发送成功的,之后消息队列投递来进行处理,如果成功,则结束,如果没有成功,则重试,直到成功,不过仅仅适用业务逻辑中,第一阶段成功,第二阶段必须成功的场景。架构图如下C流程:

图3-3

3.4 基于消息队列+定时补偿机制的最终一致性

前面部分和上面基于事务型消息的队列,不同的是,第二阶段重试的地方,不再是消息中间件自身的重试逻辑了,而是单独的补偿任务机制。其实在大多数的逻辑中,第二阶段失败的概率比较小,所以单独独立补偿任务表出来,可以更加清晰,能够比较明确的直到当前多少任务是失败的。对应上图的E流程。

至于如何实现幂等性操作,可以通过增加一个message_applied(msg_id)记录被成功应用的消息,在事务完成之后删除记录即可。

 

举个例子。假设系统中有以下两个表

  user(id, name, amt_sold, amt_bought)

  transaction(xid, seller_id, buyer_id, amount)

其中user表记录用户交易汇总信息,transaction表记录每个交易的详细信息。

这样,在进行一笔交易时,若使用事务,就需要对数据库进行以下操作:

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;

即在transaction表中记录交易信息,然后更新卖家和买家的状态。假设transaction表和user表存储在不同的节点上,那么上述事务就是一个分布式事务

则使用基于消息队列的最终一致解决方案的伪代码如下所示:

第一步:

begin;

    INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);

   put_to_queue "update user("seller", $seller_id, amount);

   put_to_queue "update user("buyer", $buyer_id, amount);

commit;

第二步:

for each message in queue

  begin;

    SELECT count(*) as cnt FROM message_applied WHERE msg_id = message.id;

    if cnt = 0 then

      if message.type = "seller" then

        UPDATE user SET amt_sold = amt_sold + message.amount WHERE id = message.user_id;

      else

        UPDATE user SET amt_bought = amt_bought + message.amount WHERE id = message.user_id;

      end

      INSERT INTO message_applied VALUES(message.id);

    end

  commit;

第三步:

  if 上述事务成功

    dequeue message

    DELETE FROM message_applied WHERE msg_id = message.id;

  end

end

 

四、最佳实践

根据不同的业务场景对一致性、可用性和分区容忍性的要求不同,在此三者间寻找一个最佳的平衡点来满足不同的业务场景,可以根据以上方案灵活选择,不局限于某一种方案。

原文地址:https://www.cnblogs.com/wz12406/p/5712318.html