分布式事务解决方案及TCC执行流程分析

分布式事务

问题产生

在分布式环境下,数据库是部署在多台数据库服务器中。在数据有多份副本的情况下,如果网络、服务器或者软件出现故障,会导致部分副本写入成功,部分副本写入失败。这就造成各个副本之间的数据不一致,数据内容冲突。

CAP定理

  • CAP理论认为在分布式的环境下设计和部署系统时,有3个核心的需求,对于共享数据系统,最多只能同时拥有CAP其中的两个,没法三者兼顾:
  1. Consistency:一致性
    这个和数据库ACID的一致性类似,但这里关注的所有数据节点上的数据一致性和正确性,而数据库的ACID关注的是在在一个事务内,对数据的一些约束。系统在执行过某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读取到最新值。就是说最终所有用户读取的数据应该是一致的
  2. Availability:可用性
    每一个操作总是能够在一定时间内返回结果。需要注意“一定时间”和“返回结果”。“一定时间”是指,系统结果必须在给定时间内返回。“返回结果”是指系统返回操作成功或失败的结果
  3. Partition Tolerance:分区容忍性
    是否可以对数据进行分区。这是考虑到性能和可伸缩性
  • 数据一致性模型
  1. 强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性
  2. 弱一致性:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。用户读到某一操作对系统特定数据的更新需要一段时间,我们称这段时间为“不一致性窗口”。
  3. 最终一致性:是弱一致性的一种特例。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响

解决方案一-两段提交协议

  • 名词解释
  1. 事务管理器(TM),管理全局事务状态和参与的资源,系统资源一致提交/回滚
  2. TX协议
    应用或者应用服务器与事务管理器的接口
  3. XA协议
    全局事务管理器与资源管理器的接口
  4. 资源管理器(RM)
    资源管理器(可以是DBMS或者消息服务器),应用程序通过资源管理器对资源进行控制,资源必须实现XA定义的接口
  • 具体实现
    TM和RM间采取两阶段提交(Two Phase Commit)的方案来解决一致性问题,两阶段提交需要一个协调者(TM)来掌控所有参与者节点(RM)的操作结果并且指引这些节点是否需要最终提交
  1. 请求阶段
    在请求阶段,协调者将通知事务参与者准备提交或取消事务,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。
  2. 提交阶段
    在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行响应的操作。
  • 两阶段提交的问题
  1. 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。
  2. 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。
  3. 协议成本较高

解决方案二-基于可靠消息的最终一致性方案

  • 在分布式应用中,我们使用到了消息中间件比如mq,要想保证最终一致性,那么就需要保证消费者对于消息的业务处理要实现幂等.同时,在消息生产者和mq中间引入一层消息服务器(比如tcc服务器),包含消息服务子系统,消息状态确认子系统,消息恢复子系统,消息服务库(消息系统中用来存取消息状态的数据库)

  • 幂等性
    f(f(x)) = f(x),重复调用多次产生的业务结果与调用一次产生的业务结果相同
    实现方式一
    通过业务操作本身实现幂等性,比如某些业务逻辑update操作,无论执行多次业务方法所得到的结果都是一样的,这是业务本身就具有幂等性

实现方式二
系统缓存所有请求与处理结果。检测到重复请求后,自动返回之前的处理结果。比如进行业务逻辑一开始就像数据库查询,如果数据状态已经发生改变,就不继续进行接下来的操作

TCC型分布式事务方案介绍

分布式事务解决方案及TCC执行流程分析

  • 使用场景
    首先模拟一个场景,对一个订单支付之后,我们需要做下面的步骤:
    更改订单的状态为“已支付”
    扣减商品库存
    给会员增加积分
    创建销售出库单通知仓库发货
    在这个场景中,订单服务就是TCC主业务服务,库存服务,会员服务等就是TCC从业务服务

  • 使用tcc服务器可以保证分布式环境下的事务最终一致性,包含以下三步:

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

    解释:
    如果你要实现一个 TCC 分布式事务,首先你的业务的主流程以及各个接口提供的业务含义,不是说直接完成那个业务操作,而是完成一个 Try 的操作。比如上述场景中不要直接把所有服务方法执行完成,可以预处理:订单服务先把自己的状态修改为:OrderStatus.UPDATING;库存服务也别直接扣减库存啊,你可以是冻结掉库存;可以保持积分为 1190 不变,在一个预增加字段里,比如说 prepare_add_credit 字段,设置一个 10,表示有 10 个积分准备增加。可以先创建一个销售出库单,但是这个销售出库单的状态是“UNKNOWN”。

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

    解释:
    只要所有服务的所有try阶段都没问题,tcc框架会执行所有服务的confirm逻辑:

    1. 理想情况,那就是各个服务执行自己的那个 Try 操作,都执行成功了。这个时候,就需要依靠 TCC 分布式事务框架来推动后续的执行了
    2. 我们使用的tcc框架比如TCC-transaction感知到各个服务的try操作都执行成功了,开始进入tcc下一阶段confirm阶段
    3. 无论是主业务服务还是从业务服务,除了提供核心业务之外(上面贴@Compensable(confirmMethod = “confirmMakePayment”, cancelMethod = “cancelMakePayment”)用于指定确认方法和取消方法),需要额外提供确认方法和取消方法,在确认方法中就需要把真正的业务逻辑做完;
    4. 订单服务内的 TCC 事务框架会负责跟其他各个服务内的 TCC 事务框架进行通信,依次调用各个服务的 Confirm 逻辑。然后,正式完成各个服务的所有业务逻辑的执行。
  3. Cancel: 取消执行业务
    • 释放Try阶段预留的业务资源
    • Cancel操作要满足幂等性

    解释:
    订单服务的 TCC 分布式事务框架只要感知到了任何一个服务的 Try 逻辑失败了,就会跟各个服务内的 TCC 分布式事务框架进行通信,然后调用各个服务的 Cancel 逻辑,大家一起回滚,保证事务的最终一致性

  • 源码分析
  1. TCC-Transaction 将每个业务操作抽象成事务参与者,每个事务可以包含多个参与者。比如查看tcc源码demo时,会在同一个transaction创建出订单参与者,账户参与者,红包参与者。并且因为服务会贴有@Compensable标签,所以框架底层会在每个参与者participant中创建出相应的执行器terminator,terminator中就拥有confirmInvocation和CancelInvocation分别用来调用确认方法和取消方法。
  2. TCC-Transaction 有两个拦截器,第一个拦截器,可补偿事务拦截器CompensableTransactionInterceptor,作用是:a. 在 Try 阶段,对事务的发起、传播 b.在 Confirm / Cancel 阶段,对事务提交或回滚.事务的传播是指会通过rpc远程调用执行从服务业务,这样事务就传递给了远程参与者;第二个拦截器ResourceCoordinatorInterceptor,资源协调者拦截器:在 Try 阶段,添加参与者到事务中。当事务上下文不存在时,进行创建
  3. xid,事务编号( TransactionXid ),用于唯一标识一个事务。使用 UUID 算法生成,保证唯一性,就是说主从服务的xid应当是一致的。事务状态分为尝试,确认,取消。事务类型分为根事务(ROOT)和分支事务(BRANCH)。
  • 总结
    tcc框架执行流程:
  1. 先是服务调用链路依次执行 Try 逻辑。
  2. 如果都正常的话,TCC 分布式事务框架推进执行 Confirm 逻辑,完成整个事务。
  3. 如果某个服务的 Try 逻辑有问题,TCC 分布式事务框架感知到之后就会推进执行各个服务的 Cancel 逻辑,撤销之前执行的各种操作。