关于分布式事务的理解

  其实分布式事物也是个老大难的问题了,从提出到现在一直都没有个特别优雅有效的解决方案,上周遇到个分布式的问题,我想了很久,也查了很多资料。找了几种方案,但都没有达到自己的预期,现在先记录下此时对分布式事务的理解。

 

分布式事务概述


1.为什么会需要分布式事务?

  我们可以考虑下以下的场景,假设现在有一个电商平台,包含的功能有订单,支付,库存。当我们去电商平台下一笔订单,下单成功后电商平台会跳转到支付平台,支付成功后会更新库存的数据,然后电商平台就可以给我们发货了。

假设在支付的时候,电商平台向银行发起扣款,可能银行扣款成功了,但是给电商平台返回结果的时候网络出现了问题,没能返回正确的支付结果信息。那电商平台会认为支付失败,即不会给客户发货了。但实际上客户的钱已经被扣了。

针对这种情况,我们希望能把电商平台和银行这一整个支付流程放到一个事物里面。

  这里就产生了两个问题:

    1.作为电商平台,银行的代码是不由我控制的,我怎样才可以把它的代码和我的代码放到一个事物里面呢?

    2.目前的事务都是基于单数据库的本地事务,目前的数据库仅支持单库事务,并不支持跨库事务,如何能做到多数据的事务呢?

  基于上述情况,分布式事务理论就出现了,着微服务架构的普及,一个大型业务系统往往由若干个子系统构成,这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成,而且这些操作可能需要在一个事务中完成。在微服务系统中,这些业务场景是普遍存在的。此时,我们就需要在数据库之上通过某种手段,实现支持跨数据库的事务支持,这也就是大家常说的“分布式事务”。

 

分布式事务的基本理论:


 1.CAP理论

 

CAP理论说的是:在一个分布式系统中,最多只能满足C、A、P中的两个需求。

 

对于一个业务系统来说,可用性和分区容错性是必须要满足的两个条件,并且这两者是相辅相成的。业务系统之所以使用分布式系统,主要原因有两个:

  • 提升整体性能 当业务量猛增,单个服务器已经无法满足我们的业务需求的时候,就需要使用分布式系统,使用多个节点提供相同的功能,从而整体上提升系统的性能,这就是使用分布式系统的第一个原因。

  • 实现分区容错性 单一节点 或 多个节点处于相同的网络环境下,那么会存在一定的风险,万一该机房断电、该地区发生自然灾害,那么业务系统就全面瘫痪了。为了防止这一问题,采用分布式系统,将多个子系统分布在不同的地域、不同的机房中,从而保证系统高可用性。

这说明分区容错性是分布式系统的根本,如果分区容错性不能满足,那使用分布式系统将失去意义。

此外,可用性对业务系统也尤为重要。在大谈用户体验的今天,如果业务系统时常出现“系统异常”、响应时间过长等情况,这使得用户对系统的好感度大打折扣,在互联网行业竞争激烈的今天,相同领域的竞争者不甚枚举,系统的间歇性不可用会立马导致用户流向竞争对手。因此,我们只能通过牺牲一致性来换取系统的可用性分区容错性。这也就是下面要介绍的BASE理论。

2.Base理论

eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论。文章链接:https://queue.acm.org/detail.cfm?id=1394128

    BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。    

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。

    1. 基本可用(Basically Available):指分布式系统在出现不可预知故障的时候,允许损失部分可用性。

    2. 软状态( Soft State):指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。

    3. 最终一致( Eventual Consistency):强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

 

分布式事务的服务模式


 服务模式是柔性事务流程中的特殊操作实现,现在大多数分布式方案都会使用其中的几个服务模式

 1. 可查询操作

服务操作具有全局唯一标识

  可以使用业务单据号(如订单号)
  或者使用系统分配的操作流水号(如支付记录流水号)
  或者使用操作资源的组合组合标识(如商户号+商户订单号)

• 操作有唯一的、确定的时间(约定以谁的时间为准)

 

已支付为例:提交一个支付请求后,可能同步返回和异步返回都会有问题,这个时候就需要我们主动向支付渠道查询支付结果

 

2.幂等操作

重复调用多次产生的业务结果与调用一次产生的业务结果相同 

实现方式:
1.通过业务操作本身实现幂等性 
2.系统缓存所有请求与处理结果,检测到重复请求之后,自动返回之前的处理结果 
 
已支付为例:提交一个支付请求后,支付渠道扣款成功了,但是返回失败信息,这个时候再次提交支付请求,如果支付渠道不做幂等性(对商户号,订单号做判断),等于一笔订单会扣两次款。
 

3.TCC操作

Try: 尝试执行业务
• 完成所有业务检查(一致性)
• 预留必须业务资源(准隔离性) 

Confirm:确认执行业务
• 真正执行业务
• 不作任何业务检查
• 只使用Try阶段预留的业务资源 • Confirm操作要满足幂等性 

Cancel: 取消执行业务
• 释放Try阶段预留的业务资源 • Cancel操作要满足幂等性 

举一个老师带着同学们出去玩的例子:
第一步:老师问:大家想不想出去玩呢(这就是try阶段) 
第二步:每个人都回馈:想出去玩 那就都出去了(这就是Confirm),只要有一个人回馈:不想去 ,那大家都不去了(Cancel)
 

4.补偿

1.抵销(或部分抵销)正向业务操作的业务结果 

2.补偿操作满足幂等性 

 

分布式事务的解决方案


 

1.最大努力通知   

  业务活动的主动方在完成业务处理后,向业务活动被动方发送通知消息(允许消息丢失)

  主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知 

  主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息

这种方案在支付场景尤为常见,支付渠道一般会同时提供同步回调,异步回调,主动查询,更精确的还会有对账单的下载。

服务之间的通信很难确保百分之百的成功,如果失败了,在对于一致性要求不高的场合下,多试几次总会成功的,这种思想对于分布式事务的设计尤为重要。

2.可靠消息最终一致

消息中间件能够降低系统间的耦合性,在分布式场景中,我们也可以通过消息帮助我们实现分布式事务。

这里会有几个问题:

  1.业务执行成功后,确保消息必须能够发送

  2.消费端必须要能够反馈消息的消费结果

针对上述两个问题,我们思考下可靠消息一致性的实现。

1.确保消息必须能够发送

  1.1确保消息必须能够发送,我们首先做的是先将将消息落地。所以我们可以将本地的业务操作和消息落地 放到一个本地事务中,如果消息落地失败,业务也得回滚,这一步可以保证一致性

  1.2消息落地成功后,我们可以用一个定时器定时的根据消息的状态,时间将消息投递到mq的中间件------这一步有可能会出现异常(生成方投递失败)

2.消费端必须要能够反馈消息的消费结果

  2.1消息如果投递成功,消费端会消费消息,消费完成后,通知到消息生产方------这一步有可能会出现异常(通知生产者失败)

    这里有两种通知方式:

      1.生产方提供服务接口给消费端调用

      2.通过消息,消费端发送消费结果给生产端。

上述流程如下图所示,我们可以将这种方式称为 本地消息服务

 

   上述方案通过消息实现了分布式事务,但是其中会有好几个流程会导致事务的不一致,这里我们通过消息确认和消息查询,消息生产者会定时根据消息状态去投递消息,所以如果消费者无法正常的进行消息确认,那么消息会不断的进行投递,所以消费端同时需要做幂等性。

 

虽然上述方案解决了一致性的要求,但是上面的系统耦合性太高了,主动方和被动方都存在互相调用的情况,所以有了独立消息服务的方案。

独立消息服务指的是将消息的处理独立出来,单独形成一个服务,如下图所示

具体的流程如下

  1.主动方预发送消息到消息系统,消息系统保存消息,将保存结果反馈给主动方----可能直接发送失败或者反馈的时候失败

  2.主动方收到正确的消息保存结果后,开始执行业务,业务执行完成后,将业务执行结果发送出去------可能存在发送失败问题

  3.消息服务收到业务执行结果后,对消息做更新或者删除(对应业务的成功与失败),然后投递到中间件,消费端取消费------消费端可能获取不到消息  

  4.消费端消费完成后,将消费结果返回-----可能无法返回

 上述各个流程都会出现异常,下面我们来解决异常情况

1.第一步如果出现异常,消息和业务都不会执行下去,数据的一致性得到了保障,业务系统需要用定时任务保障这些未执行的业务

2.第二步如果出现异常了,业务执行了,但是消息未投递出去,数据就不一致了,这时候需要消息服务定时的根据消息的时间和状态查询业务的状态,以便能及时的更新消息状态

3.第三步和第四步是同一个解决方案,对于消息系统来说,消息投递出去后 如果消费端不给我ack,我是没办法知道我是否投递成功的。所以对于长时间没有ack的消息,消息系统统一都会认为消息投递失败,再次进行投递。这时候,消费端必须要做到幂等性。

3.TCC 

相关代码还在研究,待完成。。。

参考:

 

http://www.tianshouzhi.com/api/tutorials/distributed_transaction/383

https://juejin.im/post/5aa3c7736fb9a028bb189bca

http://www.roncoo.com/course/view/7ae3d7eddc4742f78b0548aa8bd9ccdb

posted @ 2018-03-13 17:53  XuMinzhe  阅读(996)  评论(0编辑  收藏  举报