HTML5技术

从银行转账失败到分布式事务:总结与思考 - xybaby(3)

字号+ 作者:H5之家 来源:H5之家 2017-10-23 13:21 我要评论( )

事务消息依赖于支持事务消息的消息队列,其基本思想是 利用消息中间间实施两阶段提交,将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败。流程如下: 主事

  事务消息依赖于支持“事务消息”的消息队列,其基本思想是 利用消息中间间实施两阶段提交,将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败。流程如下:    

主事务向消息队列发送预备消息

主事务收到ACK之后本地执行主事务

根据执行的结果(成功或失败)向消息队列发送提交或者回滚消息

   详细的流程如下图(图片来源见水印)所示:

  

   

  不难看到,相比本地消息表的方式,事务消息由消息中间件保证本地事务与消息的原子性,不依赖于本地数据库存储消息。但实现了“事务消息”的消息队列比较少,还不够通用。

 

  不管是本地消息表还是事务消息,都需要保证从事务执行且仅仅执行一次,exact once。如果失败,需要重试,但也不可能无限次的重试,当从事务最终失败的情况下,需要通知主业务回滚吗?但是此时,主事务已经提交,因此只能通过补偿,实现逻辑上的回滚,而当前时间点距主事务的提交已经有一定时间,回滚也可能失败。因此,最好是保证从事务逻辑上不会失败,万一失败,记录log并报警,人工介入。

 

1PC

   1PC(one phase commit)这个概念,我是在《Distributed systems for fun and profit》一文中看到的,应该是对标2PC,3PC。在wiki中并没有正式的词条,在google上的文章也不是很多。在我的理解中,1PC适用于分布式存储系统的复制集,即复制集中多个节点的数据提交,。一般来说,这些节点存储同样的数据,只要单个节点能提交,其他节点理论上也应该可以提交。 在《Distributed systems for fun and profit》中是这么描述的:

 Having a second phase in place before the commit is considered permanent is useful, because it allows the system to roll back an update when a node fails. In contrast, in primary/backup ("1PC"), there is no step for rolling back an operation that has failed on some nodes and succeeded on others, and hence the replicas could diverge.

   即对于分布式存储中使用非常广泛的中心化复制集协议Primary Secondary,在部分节点失败、部分节点成功的情况下没有回滚操作,可能会导致不一致。不过这些分布式存储系统都竭力保证,这些不一致是暂时的,会通过重试等手段保证最终的一致。

  1PC的优点是性能非常好,而且只有在出现物理故障的时候才会出现不一致。

  比如在MongoDB中,更新操作会写入Primary节点以及oplog collection,Secondary节点从Primary节点的oplog collection拉取操作日志并执行,这是一个异步的过程。及时Secondary节点因为故障执行oplog失败,Promary节点的数据也不会回滚。在《带着问题学习分布式系统之中心化复制集》中也提到过,为了提高数据可靠性(避免极端情况下数据被回滚),设定WriteConcern为w:Majority,(shard有一个Primary 一个Secondary 一个Arbiter组成)。如果这个时候由于其中一个secondary挂掉,写入操作是不可能成功的。因此,在超时时间到达之后,会向客户端返回出错信息。但是在这个时候数据是持久化到了primary节点,不会被回滚。如果此时Secondary重启,那么是会从Primary拉取日志并执行。所以当客户端返回的出错信息包含  时,应该谨慎处理

  对于分布式文件系统GFS、haystack,如果Secondary节点失败,也会采取简单粗暴的重试,并通过一些机制(cheksum,offset)来保证最终能读到正确的数据

思考与总结

  更多的时候,分布式事务只需要保证原子性,这个原子性也保证了应用层面上的一致性,而由本地事务来保证隔离性、持久性。

  原子性这个东西,即使不是分布式,仅仅是单进程单线程也是需要考虑的,这就是C++中的RAII,python中的with statement,以及各种语言的try...finally...。当涉及到跨进程、异步通信的时候,就很难通过语言层面的机制保证原子性了。

  在分布式领域,由于网络或者机器故障,经常需要重试,因此幂等性非常重要

  很多场景,比如电商、网络购票,首先要保证的是高可用,不大可能采用强一致性,因此我们也会看到‘正在处理中...‘这种中间状态,后台很可能是异步处理的,在12306买过票的话都知道,下单成功到最后是否能出票由很长一段时间。

  在笔者的业务领域,并没有涉及到强一致性的场景,只要最终一致性就行了。上面的提到的各种办法,不管是2PC、TCC、本地消息表、事务消息,都需要引入额外的框架或者组件。所以更多的时候是采取业务补偿的方式,比如一个涉及两个进程的操作需要保证原子性,进程间RPC通信,那么一般是A进程先执行,然后RPC调用B进程接口,根据B进程的返回结果,绝对是否回滚(补偿);但如果涉及到异步RPC、或者多线程、或者两个以上进程的串联时,那么就不一定能补偿、甚至很难补偿了,这个时候只记录一个error log,然后通知人工排查。因此,事务补偿只适合业务比较简单的常见,而且很难形成通用的框架,或者说实用性不强。

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • canvas一周一练 -- canvas绘制中国银行标志(4) - 张不丢

    canvas一周一练 -- canvas绘制中国银行标志(4) - 张不丢

    2017-08-01 08:02

  • 样式化加载失败的图片 - royalrover

    样式化加载失败的图片 - royalrover

    2017-02-21 08:03

  • 《七年失败的程序之路》读后感续:积累和包装 - 自由飞

    《七年失败的程序之路》读后感续:积累和包装 - 自由飞

    2016-08-20 10:00

  • 《七年失败的程序之路》读后感 - 自由飞

    《七年失败的程序之路》读后感 - 自由飞

    2016-08-15 13:00

网友点评
r