一、最终一致性分布式事务概述
强一致性分布式事务解决方案要求参与事务的各个节点的数据时刻保持一致,查询任意节点的数据都能得到最新的数据结果。这就导致在分布式场景,尤其是高并发场景下,系统的性能受到影响。而最终一致性分布式事务解决方案并不要求参与事务的各节点数据时刻保持一致,允许其存在中间状态,只要一段时间后,能够达到数据的最终一致状态即可
1.典型方案
业界对于数据的一致性问题,一直在探索有效的解决方案。为了解决分布式、高并发场景下系统的性能问题,业界基于 Base 理论提出了最终一致性分布式事务解决方案
典型的最终一致性解决方案如下所示:
1、 TCC解决方案;
2、 可靠消息最终一致性解决方案;
3、 最大努力通知型解决方案;
2.适用场景
最终一致性分布式事务解决方案主要用于不要求结果数据时刻保持一致、允许存在中间状态,但经过一段时间后,各个节点的数据能够达到一致状态的场景
在电商支付场景中,会涉及订单服务、支付服务、库存服务、积分服务、仓储服务等环节,每个服务都是单独部署的。订单服务会调用支付服务生成交易流水,订单服务会调用库存服务扣减商品库存,订单服务会调用积分服务为用户的账户增加积分,订单服务会调用仓储服务生成出库单。如果这一系列的服务调用操作使用强一致性分布式事务,很容易造成系统性能低下,导致系统卡顿,并且服务与服务之间的交互是通过网络进行的,由于网络的不稳定性,就会导致服务之间的调用出现各种各样的问题,难以完成强一致性分布式事务的提交操作
上述电商支付场景就是最终一致性分布式事务解决方案的适用场景。在最终一致性分布式事务解决方案中,每个服务都存在中间状态,服务与服务之间不必保持强一致性,允许在某个时刻查询出来的数据存在短暂的不一致性,经过一段时间后,各个服务之间的数据能够达到最终一致性。这样,不仅各个服务的数据达到了最终一致性,还极大地提高了系统的整体性能并降低了分布式事务执行过程中出错的概率
3.优缺点
最终一致性分布式事务解决方案的优点如下:
1、 性能比较高,这是因为最终一致性分布式事务解决方案不要求数据时刻保持一致,不会因为长时间持有事务占用的资源而消耗过多的性能;
2、 具备可用性;
3、 适合高并发场景;
最终一致性分布式事务解决方案的缺点如下:
1、 因为数据存在短暂的不一致,所以在某个时刻查询出的数据状态可能会不一致;
2、 对于事务一致性要求特别高的场景不太适用;
二、服务模式
最终一致性分布式解决方案存在 4 中典型的服务模式,分别为可查询操作、幂等操作、TCC 操作和可补偿操作
1.可查询操作
可查询操作服务模式需要服务的操作具有可标识性,主要体现在服务的操作具有全局唯一的标识,可以是业务的单据编码(如订单号),也可以是系统分配的操作流水号(如支付产生的交易流水号)。另外,在可查询的服务模式中,也要有完整的操作时间信息。可查询操作示意图如下图所示:
如上图可以看出,在可查询操作中,业务服务需要提供业务的接口、查询某业务数据的接口和批量查询业务数据的接口
处理订单操作的方法片段如下,在一个方法中不仅要更新本地数据库中的订单状态,还要通过 RPC 调用的方式来处理远程服务的逻辑。也就是说,其他远程业务服务为订单服务提供了操作业务的接口
public void handleOrder(){
//订单服务本地更新订单状态
orderDao.update();
//调用资金账户服务给资金账户扣款
accountService.update();
//调用积分服务给积分账户增加积分
pointService.update();
//调用会计服务向会计系统写入会计原始凭证
accountingService.insert();
//调用物流服务生成物流信息
logisticsService.save();
}
在上面的代码中,完成支付功能后需要处理订单的状态信息,在处理订单信息的方法中,除了更新订单状态的操作为本地操作外,其他操作都需要调用 RPC 接口来执行。在这种情况下,只使用本地事务就无法保证数据一致性了,需要引入分布式事务。在分布式事务的执行过程中,如果出现了错误,需要明确直到其他操作的处理情况。此时需要其他服务提供可查询的接口,以保证通过可查询的接口获取其他服务的处理情况
2.幂等操作
幂等操作服务模式要求操作具有幂等性。幂等性是数学上的概念,指的是使用相同的参数执行同一个方法时,无论执行多少次,都能输出相同的结果。在编程中,幂等性指的是对于同一个方法来说,只要参数相同,无论执行多少次都与第一次执行时产生的影响相同。幂等操作示意图如下图所示:
由上图可以看出,业务服务对外提供操作业务数据的接口,并且需要在接口的实现中保证对数据处理的幂等性
在分布式环境中,难免会出现数据不一致的情况。很多时候,为了保证数据的最终一致性,系统会提供很多重试操作。如果这些重试操作涉及的方法中,某些方法的实现不具有幂等性,则即使重试操作成功了,也无法保证数据最终一致性
通常有两种实现幂等性的方式:一种是通过业务操作本身实现幂等性;另一种是通过系统缓存所有的请求与处理结果,当再次检测到相同的请求时,直接返回之前缓存的处理结果
3.TCC 操作
TCC操作服务模式主要包括 3 个阶段,分别为 Try 阶段(尝试业务执行)、Confirm 阶段(确定业务执行)和 Cancel 阶段(取消业务执行),如下图所示:
在TCC 操作服务模式中,各阶段的主要功能及特性如下所示:
Try阶段:
1、 完成所有业务的一致性检查;
2、 预留必要的业务资源,并需要与其他操作隔离;
Confirm 阶段:
1、 此阶段会真正执行业务操作;
2、 因为在Try阶段完成了业务的一致性检查,所以此阶段不会做任何业务检查;
3、 只用Try阶段预留的业务资源进行操作;
4、 此阶段的操作需要满足幂等性;
Cancel 阶段:
1、 释放Try阶段预留的业务资源;
2、 此阶段的操作需要满足幂等性;
4.可补偿操作
在分布式系统中,如果某些数据处于不正常的状态,需要通过某种方式进行业务补偿,使数据能够达到最终一致性,这种因数据不正常而进行的补偿操作,就是可补偿操作服务模式。可补偿服务模式示意图如下图所示:
由上图可以看出,业务服务对外提供操作数据的接口时,也需要对外提供补偿业务的接口,当其他服务调用业务服务操作数据的接口出现异常时,能够通过补偿接口进行业务补偿操作:
1、 在执行业务操作时,完成业务操作并返回业务操作结果,这些操作结果对外部都是可见的;
2、 在进行业务补偿时,能够补偿或者抵消正向业务操作的结果,并且业务补偿操作需要满足幂等性;
三、TCC 解决方案
TCC是一种典型的解决分布式事务问题的方案,主要解决跨服务调用场景下的分布式事务问题,广泛应用于分布式事务场景
1.适用场景
TCC解决方案适用于具有强隔离性、严格一致性要求的业务场景,也适用于执行时间比较短的业务
对于电商业务场景中的下单减库存等业务,如果使用 TCC 分布式事务,则会经过 Try、Confirm、Cancel 三个阶段
Try 阶段
提交订单并将订单的状态设置为待提交,调用库存服务预扣减库存,具体操作为在库存数据表中将商品库存字段的数据减去提交订单时传递的商品数量,同时在预扣减库存字段中增加提交订单时传递的商品数量
Confirm 阶段
如果Try 阶段的操作全部执行成功,则执行 Confirm 阶段。在 Confirm 阶段,订单服务将订单数据的状态标记为已提交。库存服务则将库存数据表中预扣减库存字段的数据减去提交订单时传递的商品数量,实现真正扣减库存
Cancel 阶段
如果Try 阶段执行失败或者抛出异常,则执行 Cancel 阶段。在 Cancel 阶段,订单服务将订单数据的状态标记为已取消。库存服务将库存数据表中商品库存字段的数据增加提交订单时传递的商品数量,同时对预扣减库存字段的数据减去提交订单时传递的商品数量,实现事务回滚
2.需要实现的服务模式
在TCC 分布式事务解决方案中,需要实现的服务模式包括 TCC 操作、幂等操作、可补偿操作和可查询操作
例如,实现 TCC 分布式事务方案时,需要实现 Try、Confirm 和 Cancel 三个阶段的业务逻辑,这就是 TCC 操作。在 TCC 操作的每个阶段的方法都需要实现幂等性,这就是幂等操作。如果在执行分布式事务的过程中,业务服务或者网络出现了异常情况,则需要支持重试操作,以达到事务补偿的目的,这就是可补偿操作。另外,业务服务需要提供可以查询自身内部事务状态的接口,以供其他服务调用,这就是可查询操作
3.方案的执行流程
从本质上讲,TCC 是一种应用层实现的二阶段提交协议,TCC 方案的执行流程如下图所示:
Try 阶段
不会执行任何业务逻辑,仅做业务的一致性检查和预留相应的资源,这些资源能够和其他操作保持隔离
Confirm 阶段
当Try 阶段所有分支事务执行成功后开始执行 Confirm 阶段。通常情况下,采用 TCC 方案解决分布式事务时会认为 Confirm 阶段是不会出错的。也就是说,只要 Try 阶段的操作执行成功了,Confirm 阶段就一定会执行成功。如果 Confirm 阶段出错了,就需要引入重试机制或人工处理,对出错的事务进行干预
Cancel 阶段
在业务执行异常或出现错误的情况下,需要回滚事务的操作,执行分支事务的取消操作,并且释放 Try 阶段预留的资源。通常情况下,采用 TCC 方案解决分布式事务时,同样会认为 Cancel 阶段也是一定会执行成功的。如果 Cancel 阶段出错了,也需要引入重试机制或人工处理,对出错的事务进行干预
4.方案的优缺点
TCC分布式事务的优点如下:
1、 在应用层实现具体逻辑,锁定资源的粒度变小,不会锁定所有资源,提升了系统的性能;
2、 Confirm阶段和Cancel阶段的方法具备幂等性,能够保证分布式事务执行完毕后数据的一致性;
3、 TCC分布式事务解决方案由主业务发起整个事务,无论是主业务还是分支事务所在的业务,都能部署为集群模式,从而解决了XA规范的单点故障问题;
TCC方案的缺点是代码需要耦合到具体业务中,每个参与分布式事务的业务方法都要拆分成 Try、Confirm 和 Cancel 三个阶段的方法,提高了开发成本
5.需要注意的问题
使用TCC 方案解决分布式事务问题时,需要注意空回滚、幂等和悬挂的问题
空回滚问题
1)空回滚问题出现的原因
出现空回滚的原因是一个分支事务所在的服务器宕机或者网络发生异常,此分支事务调用失败,此时并未执行此分支事务 Try 阶段的方法。当服务器或者网络恢复后,TCC 分布式事务执行回滚操作,会调用分支事务 Cancel 阶段的方法,如果 Cancel 阶段的方法不能处理这种情况,就会出现空回滚问题
2)空回滚问题的解决方案
识别是否出现了空回滚操作的方法判断是否执行了 Try 阶段的方方法。如果执行了 Try 阶段的方法,就没有空回滚,否则,就出现了空回滚
具体解决方案是在业务发起全局事务时,生成全局事务记录,并为全局事务记录生成一个全局唯一的 ID,叫做全局事务 ID。这个全局事务 ID 会贯穿整个分布式事务的执行流程。再创建一张分支事务记录表,用于记录分支事务,将全局事务 ID 和分支事务 ID 保存到分支事务表中。执行 Try 阶段的方法时,会向分支事务记录表中插入一条记录,其中包含全局事务 ID 和分支事务 ID,表示执行了 Try 阶段。当事务回滚执行 Cancel 阶段的方法时,首先读取分支事务表中的数据,如果存在 Try 阶段插入的数据,则执行正常操作回滚事务,否则为空回滚,不做任何操作
幂等问题
1)幂等问题出现的原因
由于服务器宕机、应用崩溃或者网络异常等原因,可能会出现方法调用超时的情况,为了保证方法的正常执行,往往会在 TCC 方案中加入超时重试机制。因超时重试有可能导致数据不一致的问题,所以需要保证分支事务的执行以及 TCC 方案的 Confirm 阶段和 Cancel 阶段具备幂等性
2)幂等问题的解决方案
解决方案是在分支事务记录表中增加事务的执行状态,每次执行分支事务以及 Confirm 阶段和 Cancel 阶段的方法时,都查询此事务的执行状态,以此判断事务的幂等性
悬挂问题
1)悬挂问题出现的原因
在TCC 分布式事务中,通过 RPC 调用分支事务 Try 阶段的方法时,会先注册分支事务,再执行 RPC 调用。如果此时发生服务器宕机、应用搞崩溃或者网络异常等情况,RPC 调用就会超时。如果 RPC 调用超时,事务管理器会通知对应的资源管理器回滚事务。可能资源管理器回滚完事务后,RPC 请求达到了参与分支事务所在的业务方法,因为此时事务已经回滚,所以在 Try 阶段预留的资源就无法释放了。这种情况,就称为悬挂。总之,悬挂问题就是预留业务资源后,无法继续往下处理
2)解决悬挂问题的方案
解决方案的思路是如果执行了 Confirm 阶段或者 Cancel 阶段的方法,则 Try 阶段的方法就不能再执行。具体方案是在执行 Try 阶段的方法时,判断分支记录表中是否已经存在同一全局事务下 Confirm 阶段或者 Cancel 阶段的事务记录,如果存在,则不再执行 Try 阶段的方法
四、可靠消息最终一致性解决方案
可靠消息最终一致性分布式事务解决方案指的是事务的发起方执行完本地事务之后,发出一条消息,事务的参与方,也就是消息的消费者一定能够接收到这条消息并处理成功。这个方案强调的是只要事务发起方将消息发送给事务参与方,事务参与方就一定能够执行成功,事务最终达到一致的状态
1.适用场景
可靠消息最终一致性方案主要适用于消息数据能够独立存储,能够降低系统之间耦合度,并且业务对数据一致性的时间敏感度高的场景。例如,基于 RocketMQ 实现的可靠消息最终一致性分布式事务解决方案
以电商支付场景,向用户发放优惠卷为例,具体流程为订单服务向 RocketMQ 发送 Half 消息(Half 消息是 RocketMQ 中的概念),发送成功后,RocketMQ 会向订单服务响应 Half 消息发送成功的状态。接下来,订单服务执行本地事务,修改订单数据的状态,并向 RocketMQ 发送提交事务或者回滚事务的消息。如果是提交事务的消息,则 RocketMQ 会向优惠卷服务投递事务消息,优惠卷服务收到消息后,会执行用户发放优惠卷的逻辑。如果是回滚消息,则 RocketMQ 会删除相应的消息,不再向优惠卷服务投递对应的事务消息
2.需要实现的服务模式
可靠消息最终一致性分布式事务解决方案需要实现的服务模式是可查询操作和幂等操作
在具体实现的过程中,需要参与分布式事务的业务服务提供可查询自身事务状态的接口,在发生异常时,能够让其他服务通过查询接口查询具体的事务状态,这就是可查询操作。参与分布式事务的各个业务接口需要保证数据操作的幂等性,只要参数相同,无论调用多少次接口,都应该和第一次调用接口产生的结果相同,这就是幂等操作
3.方案的执行流程
可靠消息最终一致性解决方案中,事务发起方执行完本地事务后,通过可靠消息服务将消息发送给事务参与方,事务参与方接收到消息后,一定能够成功执行。这里的可靠消息服务可以通过本地消息表实现,也可以通过 RocketMQ 消息队列实现
可靠消息最终一致性方案的执行流程如下图所示:
首先,事务发起方将消息发送给可靠消息服务,这里的可靠消息服务可以基于本地数据表实现,也可以基于消息队列中间件实现。然后,事务参与方从可靠消息服务中接收消息。事务发起方和可靠消息服务之间、可靠消息服务和事务参与方之间都是通过网络进行通信的。由于网络本身的不稳定性,可能会造成分布式事务问题,因此在实现上,需要引入消息确认服务和消息恢复服务
消息确认服务会定期检测事务发起方业务的执行状态和消息库中的数据,如果发现事务发起方业务的执行状态与消息库中的数据不一致,消息确认服务就会同步事务发起方的业务数据和消息库中的数据,保证数据一致性,确保事务发起方业务完成本地事务后消息一定会发送成功
消息恢复服务会定期检测事务参与方业务的执行状态和消息库中的数据,如果发现事务参与方业务的执行状态与消息库中的数据不一致(这里的不一致,通常指的是事务参与方消费消息后,执行本地事务操作失败,导致事务参与方本地事务的执行状态与消息库中的数据不一致),消息恢复服务就会恢复消息库中消息的状态,使消息的状态回滚为事务发起方发送消息成功,但未被事务参与方消费的状态
4.方案的优缺点
基于本地消息表实现的最终消息一致性方案
优点:
1、 在业务应用中实现了消息的可靠性,减少了对消息中间件的依赖;
缺点:
1、 绑定了具体的业务场景,耦合性\;
2、 消息数据与业务数据在同一个数据库,占用了业务系统的资源;
3、 消息数据可能会受到数据库并发性的影响;
基于消息队列中间件实现的最终消息一致性方案
优点:
1、 消息数据能够独立存储,与具体的业务数据库解耦;
2、 消息的并发性和吞吐量优于本地消息表方案;
缺点:
1、 发送一次消息需要完成两次网络交互,一次是消息的发送,另一次是消息的提交或回滚;
2、 需要实现消息的会查接口,增加了开发成本;
5.需要注意的问题
适用可靠消息最终一致性方案解决分布式事务问题是,需要注意本地事务与消息发送的原子性问题、事务参与方接收消息的可靠性与幂等性问题
事务发送方本地事务与消息发送的原子性问题
1)原子性问题产生的原因
可靠消息最终一致性要求事务发起方的本地事务与消息发送的操作具有原子性,也就是事务发起方执行本地事务成功后,一定要将消息发送出去,执行本地事务失败后,一定要丢弃消息。执行本地事务和发送消息,要么都成功,要么都失败
2)原子性问题的解决方案
在实际的解决方案中,可以通过消息确认服务解决本地事务与消息发送的原子性问题
事务参与方接收消息的可靠性问题
1)可靠性问题产生的原因
由于服务器宕机、服务崩溃或网络异常等原因,导致事务参与方不能正常接收消息,或者接收消息后处理事务的过程中发生异常,无法将结果正确回传到消息库中。此时,就会产生可靠性问题
2)可靠性问题的解决方案
可以通过消息恢复服务保证事务参与方的可靠性
事务参与方接收消息的幂等性
1)幂等性问题产生的原因
在实际场景中,由于某种原因,可靠消息服务可能会多次向事务参与方发送消息,如果事务参与方的方法不具有幂等性,就会造成消息重复消费的问题,这就是典型的幂等性问题
2)可靠性问题的解决方案
解决方案就是事务参与方的方法实现要具有幂等性,只要参数相同,无论调用多少次接口或方法,得出的结果都与第一次调用接口或方法得出的结果相同
五、最大努力通知型解决方案
当分布式事务跨越多个不同的系统,尤其是不同企业之间的系统时,解决分布式事务问题就需要用到最大努力通知型方案
1.适用场景
最大努力通知型解决方案适用于最终一致性时间敏感度低的场景,并且事务被动方的处理结果不会影响主动方处理结果。最典型的使用场景就是支付成功后,支付平台异步通知商户支付结果
2.需要实现的服务模式
最大努力通知型解决方案需要实现的服务模式是可查询操作和幂等操作
例如,在充值业务场景中,用户调用支付服务充值成功后,支付服务会按照一定的阶梯型通知规则调用账户服务的接口,向账户服务发送支付数据。此时,账户服务的接口需要满足幂等性,这就是幂等操作。如果支付服务调用账户服务的接口超过了设置的最大次数,仍然没有调用成功,则支付服务需要提供查询支付结果的接口,以便账户服务调用并恢复丢失的业务
3.方案的执行流程
最大努力通知型分布式事务解决方案在执行的过程中,允许丢失消息,但需要业务主动方提供事务状态查询接口,以便业务被动方主动调用并恢复丢失的业务。最大努力通知型分布式事务执行流程如下图所示:
实现最大努力通知型方案时,需要实现如下功能:
1、 业务主动方在完成业务处理后,会向业务被动方发送消息通知发送消息通知时,允许消息丢失;
2、 在实现上,业务主动方可以设置时间阶梯型通知规则,在消息通知失败后,可以按照规则再次通知,直到到达最大通知次数为止;
3、 业务主动方需要提供查询接口供业务被动方按照需要查询,用于恢复丢失的消息;
4.方案的优缺点
最大努力通知型方案存在如下优点:
1、 能够实现跨企业的数据一致性;
2、 业务被动方的处理结果不会影响业务主动方的处理结果;
3、 能够快速接入其他业务系统,达到业务数据一致性;
最大努力通知型方案存在如下缺点:
1、 只适用于时间敏感度低的场景;
2、 业务主动方发送的消息可能丢失,造成业务被动方收不到消息;
3、 需要业务主动方提供查询消息的接口,业务被动方需要按照主动方的接口要求查询数据,增加了开发成本;
5.需要注意的问题
业务被动方需要保证接收通知的方法的幂等性,关键是要业务主动方通过一定的机制最大限度地将业务的处理结果通知给业务被动方,因此必须解决如下两个问题
消息重复通知产生的问题
1)消息重复通知产生的原因
由于业务主动方发送消息通知后,业务被动方不一定能够接收到消息,因此需要按照一定的阶梯型通知规则重复向业务被动方发送消息通知。此时,就出现了消息重复通知的情况,因为业务被动方的方法被执行了多次,所以有可能造成数据不一致的问题
2)消息重复通知的解决方案
保证业务被动方接收消息通知的方法具备幂等性,则在业务上就能够解决消息重复通知的问题
消息丢失通知的问题
1)消息通知丢失问题的原因
如果业务主动方尽最大努力都没有将消息通知给业务被动方,或者业务被动方接收到消息并执行完毕后,需要再次获取消息。此时,业务主动方已经删除对应的通知消息,不再向业务被动方发送消息通知,也就是说,消息通知已经丢失
2)消息通知丢失的解决方案
业务主动方需要提供查询消息的接口来满足业务被动方主动查询消息的需求,以恢复丢失的业务。另外,业务主动方在设计消息回查接口时,一定要注意接口的安全性和并发性
6.最大努力通知与可靠消息最终一致性的区别
最大努力通知型方案和可靠消息最终一致性方案有着本质的不同,主要体现在设计不同、业务场景不同和解决的问题不同 3 个方面
设计不同:
1、 可靠消息最终一致性方案需要事务发起方一定要将消息发送成功;
2、 最大努力通知型方案中,业务主动方尽最大努力将消息通知给业务被动方,但消息可能会丢失,业务被动方不一定能接收到消息;
业务场景不同:
1、 可靠消息最终一致性方案适用于时间敏感度高的场景,以异步的方式达到事务的最终一直;
2、 最大努力通知型方案适用于时间敏感度低的场景,业务主动方只需要将处理结果通知出去;
解决的问题不同:
1、 可靠消息最终一致性方案解决的时消息从事务发起方发出,到事务参与方接收的一致性,并且事务参与方接收到消息后,能够正确地执行事务操作,达到事务最终一致;
2、 最大努力通知型方案虽然无法保证消息从业务主动方发出到业务被动方接收的一致性,但是能够提供消息接收的可靠性这里的可靠性包括业务被动方能够接收到业务主动方通知的消息和业务被动方能够主动查询业务主动方提供的消息回查接口,来恢复丢失的业务;