zoukankan      html  css  js  c++  java
  • 分布式事务方案整合

    分布式事务方案整合

    通过几天的资料查找,对解决分布式事务的方法有两阶段提交、支付宝分享的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

     

    四、最佳实践

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

  • 相关阅读:
    iOS MJRefresh的使用 (列表上拉加载更多)
    iOS 后台任务
    ios Alamofire网络插件的使用
    springboot shiro ehcache redis 简单使用
    android BottomNavigationView 简单使用
    iOS ksyhttpcache音视频缓存
    springboot 简单使用shiro登录
    springboot 使用 mybatis + mapper
    [LeetCode] Permutations
    【经典算法】回溯算法
  • 原文地址:https://www.cnblogs.com/wz12406/p/5712318.html
Copyright © 2011-2022 走看看