`
envy2002
  • 浏览: 149357 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

分布式事务

 
阅读更多

                                                        分布式事务

      现在遇到了几个项目,这些项目都有一个共同特点,就是要求分布式事务。能够满足跨系统的保持数据的一致性。举个简单例子,我没钱买房,问老婆借了1万块,到期了,该还给老婆了,那就转账吧。如图:

 

我的银行卡是招商银行的,老婆的银行卡是中国银行的,很显然这就是个分布式事务了,招行要完成的动作是A(我的账号)-10000, 中行的动作时B+10000,两个动作比如同时成功才OK,如图。

上网查了一下,这种分布式事务是有个开放协议的叫x/open,主要采用了两部提交法算法,异构的数据库只要实现了这个协议,就可以完成分布式事务。这个需要底层数据库的支持,因为这个协议和数据库底层实现密切配合,需要各个数据库的厂商自己完成符合xa协议的数据库驱动,即driver.

      很多银行采用Tuxedo中间件来控制分布式事务,其实这个x/open协议就是由 tuxedo的xa协议来的。

       那么这种分布式的事务结构,往往都是借助第三者的,架构图如下:

 

       有意思的是这个x/open协议的实质,是叫做什么两步提交。

第一步叫做预提交。即多个系统事务“几乎”已经全部做好了,各个数据库资源告诉事务管理器--“i am ready",

事务管理器最后一声令下---“提交”,两者真正提交,提交过程中出错,全部回滚。

       如果我们不知道有什么xa协议,两步提交,肯定有人会想,我自己写个中间件来搞个逆向事务不可以吗?这主要涉及到 01,10这两种case, 01表示我A-10000的事务失败,老婆的B+10000成功。那么我先反推导出,老婆的逆事务为B-10000,我的逆事务为A+10000. 那么如果老婆的事务成功,我的失败,很显然,老婆的事务要回退,那么就再执行个逆事务,B-10000.貌似有道理,但是这个逆事务的“推理机”你会做吗?似乎有难度哦。而且有问题的是,如果这个逆事务中途出现失败该如何处理,逆事务的效率如何等等。这都是一堆的问题。

 

        这个分布式事务最大的缺点就是会锁定数据库资源,两步提交消息反反复复,对网络不好的情况,肯定很难有扩展性。于是有人提出了基于消息队列的分布式事务处理。有这篇文章。

http://www.yiihsia.com/2010/11/%E7%94%A8%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E5%92%8C%E6%B6%88%E6%81%AF%E5%BA%94%E7%94%A8%E7%8A%B6%E6%80%81%E8%A1%A8%E6%9D%A5%E6%B6%88%E9%99%A4%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/

   这篇文章的关键代码就是这段,我看的有些稀里糊涂,说说自己的理解吧。

   

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

 

 

 这段代码的场景是 A数据有一个交易的表transaction, B数据有个表User. 要求同时更新 transaction, user表。

    那这段代码的意思是在数据库A上,开始一个事物,插入transaction表,并且把更新数据库B的消息放到了消息队列里。 在另一端的数据库里面,从消息队列里面获取消息,然后开启一个事务,根据消息做动作,然后用另外一个表(message_applied)做了一个log, 表明这个消息已经用过了。然后把消息队列中的消息删除,删除message_appied中的log.

 

      文章最后这样保持了弱一致性。可能看到这里,你和我一样困惑。

      还是上图那4中case:

      00---(A 数据库操作失败,B数据库操作失败)

      01---(A 数据库操作失败,B数据库操作成功)

     10---(A 数据库操作成功,B数据库操作失败)

      11---(A 数据库操作成功,B数据库操作成功)

 

   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;

    这段代码,我觉得应该加一个约束,就是 添加队列也应该在这个事务中,让其保持执行顺序的偏序,就是先插 transaction,然后 put_to_queue。因为有可能如果put_to_queue是个应用程序的话,很可能是先put_to_queue,然后插transaction,这样会导致01这种case出现,那么就会产生不一致性。(放进队列成功,但是插入由于unique的限制失败了)。如果能保证有这个顺序性,那么00,01这种case就不会出现.

那我们大胆假设这个消息队列也是数据库中的一个id自增的表。那么10这种情况会不会出现呢?也不会!根据单个数据库事务一致性。那么最后只剩下11这种case了。

    后面的代码,就是A机器的队列传到B,B根据队列中的数据来执行动作。其实我们分析一下,A和B之间的队列的传输本身就存在不一致风险,断电了怎么办,队列中中数据没了,但是A数据库数据已经提交了,怎么办?所以文章说实现了弱一致性,这里面只不过玩了个风险置换的游戏,如果这种风险发生的概率很高,很显然这是不可取的。

      这段代码的核心思想摈弃(00,01,10这3种case, 只留11z这种case)就是A数据库成功,那么操作数据库B的操作我通过消息队列或者其他备份措施,使其能够正确到达B机器,B机器也能百分之百完成,那么数据就满足一致性! 恰恰,这还是有风险的,我们需要找到一种机制让这种风险不存在,或者无限小。

      谁能告诉我?!其实一段数据从A机器转移到B机器,通过某种协议还是100%做到的!不过这个协议应该有些复杂。所以文章最后说,如果项目时间短,还是用xa协议,分布式事务处理,如果不急,就可以采用消息队列,而且开发这个消息队列还是有些复杂的。

 

      最后,如果不是数据库之间的数据一致,那又改如何处理呢?

      为什么说若一致性呢,因为10这种情况也是能出现的,A数据库提交成功,B提交不成功(由于字段的unique限制等,二步提交法,第一步就会去跑一边事务,如果能跑,就进入第二步,如果不能跑,就通知两机器撤销事务)那么,很显然,我们明显是可以预料B数据库的事务是可以“百分百完成的”。那么这个思想,就是把单机的事务控制,比用利用log, 然后转移log,到B机去执行log,不过这种思想还是有欠缺的。

 

 

 

分享到:
评论
1 楼 lanhaitun1991 2015-09-18  
亲,其实你这里介绍的方法有一个最大的问题:如果放入队列之后,但是commit失败了,怎么办?相当于01的情况。

相关推荐

Global site tag (gtag.js) - Google Analytics