在日常讨论技术方向的时候,经常听伙伴抛出一些概念高可用性,一致性关键词。但是未必每个人都能把这些概念说清楚。今天我们就来聊聊分布式事物那些事,分享一下我们在项目中是如何使用的。

一. CAP理论

1.1什么是CAP理论

      2000年,Eric Brewer教授在PODC的研讨会上提出了一个猜想。一致性、可用性和分区容错性三者无法在分布式系统中被同时满足,并且最多只能满足其中两个!Brewer教授当时想象的分布式场景是webservice,一组websevrice后台运行着众多的server,对service的读写会反应到后台的server集群。

                                                                                 分布式事物理论与实践分布式事物理论与实践

        强一致性 (Consistency):系统在执行过某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读取到最新的值,这样的系统被认为具有强一致性。

        可用性 (Availability):每一个操作总是能够在一定的时间内返回结果,超时和不能返回结果均不符合条件。

        分区容错性(Partition Tolerance):分区容错性可以理解为系统在存在网络分区的情况下仍然可以接受请求。这里网络分区是指由于某种原因网络被分成若干个孤立的区域,而区域之间互不相通。还有一些人将分区容错性理解为系统对节点动态加入和离开的处理能力,因为节点的加入和离开可以认为是集群内部的网络分区。

1.2 系统为什么不能同时满足CAP特性

       如下图所示,在网络中有两个节点分别为G1和G2,这两个节点上存储着同一数据的不同副本,现在数据是一致的,两个副本的值都为V0,A、B分别是运行在G1、G2上与数据交互的应用程序。

                                                       分布式事物理论与实践


                         

在正常情况下,操作过程如下(如下图所示)

     (1) AV0更新,数据值为V1;

     (2) G1发送消息mG2,数据V0更新为V1;

   (3) B读取到G2中的数据V1

分布式事物理论与实践

                                                 分布式事物理论与实践



          如果发生网络分区故障,那么在操作的步骤(2)将发生错误:G1发送的消息不能传送到G2上。这样数据就处于不一致的状态,B读取到的就不是最新的数据,如下图所示。如果我们采用一些技术如阻塞、加锁、集中控制等来保证数据的一致,那么必然会影响到系统的可用性和分区容错性。所以同时满足三点,总是需要放弃一部分。


                                             分布式事物理论与实践    分布式事物理论与实践
                    

       系统满足三个条件中不同的两个条件会具有不同的特点

       放弃P,保证AC:如果想避免分区容错性问题的发生,一种做法是将所有的数据(与事务相关的)都放到一台机器上。

       放弃A,保证CP:一旦遇到分区容错故障,那么受到影响的服务需要等待数据最终一致性

       放弃C,保证AP:这里所说的放弃一致性,而保留数据的最终一致性。该方式也是解决分布式事物的主流方式。


二 分布式事物解决方案

         eBay的架构师DanPritchett 在2008年发给ACM的文档。文章中描述了一个最常见的场景,如果产生了一笔交易,需要在交易表增加记录,同时还要修改用户表的金额。这两个表属于不同的远程服务,涉及到分布式事务一致性的问题。经典的解决方法,引入本地消息表和消息中间件,将写入本地消息表、更新用户表,发送Q放在一个本地事务中完成。同时为了避免重复消费用户表消息带来的问题,保证幂等性增加一个接收端记录表。

举个例子,系统中有以下两个表user,transaction.


                                                                    分布式事物理论与实践


       其中user表记录用户交易汇总信息,transaction表记录每个交易的详细信息。transaction表和user表存储在不同的节点上,那么上述事务是一个分布式事务。要消除这一分布式事务,将它拆分成两个子事务。保证消息的幂等性还需要在接收端创建消息接收表message_applied(msg_id),保证不能重复接收消息。


begin transaction

 insert into transaction(xid, $seller_id, $buyer_id, $amount);

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

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

end transaction

--------------------------------------------------------

for each message in queue

  peekmessage

  begintransaction

       selectcount(*) as cnt from message_applied where msg_id=message.id

              andbalance = message.balance and user_id=message.user_id

       ifcnt == 0

then

             ifmessage.type = “seller” then

                      updateuser 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;

        insert into message_applied values(message.id);

end

end transaction

end fors

第一阶段:transation表和操作队列在同一实例上,可以通过本地的数据库的事务保证。
第二阶段:message_applied表和user表在同一个实上,可以通过本地的数据的事务保证,当有重试或者重复消息过来时,通过message_applied表可以判断是否是重复数据。     

 三   实际项目对理论的实践 

  1.A、B两个系统服务进行异步数据通信

本场景我们采用Q作为消息中间件。可以借鉴ebay模式。

① 应用A的业务逻辑、写本地消息表、写消息队列放在一个队列里。写消息队列业务逻辑放在最后。

② 所有步骤执行成功,事物提交。

③ 有一步骤执行失败,则进行回滚。

此方案需要在记录服务A发送的完整消息日志,以防Q出现问题进行消息补发,在B端创建接收表,去重,保证消息的幂等性。

 2. D调用A、B、C三个服务

      本场景保证事物一致性有两种解决方案,一种是调用中间服务出错后回滚掉其他所有服务的数据,这需要每个应用提供回滚服务。另一种认为服务一定会调用成功,一旦出现问题,会不断重试。下面会根据这两种情况进行详细说明。

第一,    服务调用采用回滚方式处理,如下示意图所示


分布式事物理论与实践

 

 

 以上思路还是借鉴ebay模型来解决最终一致性问题。

第二,不断重试保证消息一致性相对第一种情况要简单一些。一旦调用异常需要记录到错误表中不断的进行重试。

相关文章: