TCC(Try-Confirm-Cancel)是一种分布式事务的实现方式。TCC和2PC非常类似,Try-Confirm-Cancel对应2PC中的Prepare-Commit-Rollback,区别是2PC是资源层的(数据库),而TCC是应用层的,我们可以把TCC看成是应用层的2PC。
一个TCC事务中有以下3个步骤(对一个业务功能来说相当于三个接口)。
尝试执行业务。完成所有业务检查并预留业务所需的资源,在这一步中不会直接执行业务逻辑。
确认执行业务逻辑。这一步会真正执行业务,并且不做任何业务检查,在业务执行过程中只会使用Try步骤中预留的资源。
取消执行业务逻辑。释放Try步骤中预留的资源。
下面我们来看一个电商系统的场景。在用户支付后我们需要完成以下几个步骤。
上述几个步骤,要么都执行,要么都不执行,这就是一个典型的分布式事务。如果我们只是简单的通过订单系统调用其它三个服务的接口,那么只要其中一个接口挂了,就会导致数据不一致,显然不满足需求。
下面我们来看看TCC事务是如何应用的。
首先在TCC的Try阶段,我们不能直接执行业务逻辑,而是要把业务中所需要的资源预留出来并做记录,比如不能直接把订单状态设为已支付,而是设为正在更新;库存服务预留出订单中商品的数量,比如数据库中有一列称为冻结库存;积分服务也不能直接加到用户账号中,也可以在数据库中增加一列预增加积分;仓储服务也类似,可以把出库单状态设置为未知。
如果Try阶段各个服务执行成功,那么接下来就会进入Confirm阶段。每个服务需要完成自己的业务逻辑,比如订单服务需要把订单状态更改为已支付;库存服务需要把冻结的库存从剩余库存中扣去;积分服务需要预增加的积分真正增加到用户账户中;存储服务需要把出库单状态改为已创建。
如果Try阶段有一个服务执行失败,那么接下来会进入Cancel阶段。各个服务需要对之前的操作进行回滚,比如订单服务把订单状态设置为已取消;库存服务把冻结的库存清零;积分服务把预增加的积分清零;仓储服务把出库单状态设为已取消。
在实际场景中,不可能保证上述的几个步骤一定能执行成功,比如在调用下游服务的Confirm接口时因为网络原因导致请求超时,这个时候我们就无法确定事务的状态,可能执行成功,也可能下游服务根本没有收到请求,这个时候调用方就需要不断进行重试,直到调用成功。因此下游服务的接口必须保证幂等性,保证一次请求和多次请求的结果是一致的。
该方案是利用消息队列(例如:RocketMQ)的特性来实现分布式事务。事务消息和普通消息的区别是:普通消息发送成功后立刻就被消费者消费;事务消息发送成功后,消息处于半提交状态,不会立刻被消费者消费,直到发送方进行二次确认。
通过事务消息实现分布式事务的流程如下:
上述一些步骤存在消息重新投递的情况,因此需要保证业务操作具备幂等性。
该方案需要在事务发起方维护一张消息表,对业务表的操作和对消息表的操作需要在同一个事务内进行以保证原子性,再通过定时任务将消息表中的消息投递到下游消费者。
通过本地消息表实现分布式事务的流程如下: