架构设计 | 基于电商交易流程,图解TCC事务分段提交
本文源码:GitHub·点这里 || GitEE·点这里
一、场景案例简介
1、场景描述
分布式事务在业务系统中是十分常见的,最经典的场景就是电商架构中的交易业务,如图:

客户端通过请求订单服务,执行下单操作,实际上从订单服务上又触发了多个服务链请求,基本步骤如下:
- 客户端请求在订单服务上创建订单;
- 订单服务调用账户服务扣款;
- 订单服务调用库存服务执行库存扣减;
- 订单通过物流服务,转化为物流运单;
这套流程在电商系统中是基本业务,在实际的开发中远比这里描述的复杂。
2、服务时序图
上述1中是业务性的流程概念描述,从系统开发层面,在微服务的架构模式下,通常的时序流如下:

这样服务间的通信时序图在程序设计中十分常见,在分布式系统中,清楚的描述各个服务间的通信流程是十分关键的。
上图描述的交易流程是在最理想的状态下,各个服务都执行成功,但是程序是不能100%保证一直正常,经常出现如下情况:
- 服务间通信失败;
- 单个节点服务宕掉;
- 服务接口执行失败;
这些都是实际开发中经常出现的问题,比如订单创建成功,扣款成功,但是库存扣减失败,物流运单生成,那么这笔订单该如何处理?这就是分布式事务要解决的核心问题。
分布式事务机制要保证不同服务之间形成一个整体性的可控的事务,业务流程上的服务除非全部成功,否则任何服务的操作失败,都会导致所有服务上操作回滚,撤销已经完成的动作。
二、TCC基础概念
1、分段提交协议
XA是一个分布式事务协议,大致分为两部分:事务管理器和本地资源管理器,本地资源管理器基本由数据库实现,大多数关系型数据库都实现XA接口,而事务管理器作为全局事务的调度者,负责整个事务中本地资源的提交和回滚,基本原理如下:

阶段1:事务询问
事务管理器向所有的参与事务的资源管理器发送确认请求,询问是否可以执行事务提交操作,并等待各参与者的响应,如果执事务操作成功,就反馈给事务管理器表示事务可以执行,如果没有成功执行事务,就反馈事务不可以执行;
阶段2:事务提交
XA根据第一阶段每个资源管理器是否都准备提交成功,判断是要事务整体提交还是回滚,正式执行事务提交操作,并在完成提交之后释放整个事务占用的资源;事务也会存在失败情况,导致流程取消回滚;
XA事务具有强一致性,在两阶段提交的整个过程中,一直会持有资源的锁,性能不理想的缺点很明显,特别是在交易下单链路中,往往并发量很高,XA无法满足该类高并发场景。
2、TCC概念简介
Try(预处理)-Confirm(确认)-Cancel(取消)模式的简称TCC。
Try阶段
业务检查(一致性)及资源预留(隔离),该阶段是一个初步操作,提交事务前的检查及预留业务资源完成;例如购票系统中的占位成功,需要在15分钟内支付;
Confirm阶段
确认执行业务操作,不在执行任何业务检查,基于Try阶段预留的业务资源,从理想状态下看只要Try成功,Confirm也会成功,因为资源的检查和锁定都已经成功;该阶段出现问题,需要重试机制或者手动处理;购票系统中的占位成功并且15分钟内支付完成,购票成功;
Cancel阶段
Cancel阶段是在业务执行错误需要回滚到状态下执行分支事务的取消,预留资源的释放;购票系统中的占位成功但是15分钟内没有支付,取消占位;
3、TCC对比XA
XA事务的强一致性,导致资源层的锁定;
TCC在业务层面追求最终一致性,不会长久占用资源;
三、分段事务分析
现在回到模块一中的场景案例,在理想状态下流程全部成功是好的,但实际情况是突发情况很多,基于TCC模式分析上述电商的具体业务:
1、资源预留
在TCC模式下,通常表字段的状态设计思路为:订单(支付中.已支付.取消订单),账户(金额.冻结金额),库存(库存.冻结库存),物流(出库中.已出库,已撤回),这种状态管理在开发中非常常见。
所以在TCC模式里通常会如下处理资源预留:

假设订单总额为:200,状态:支付中,则此时资源预留情况如下:
- tc_account账户表:tc_total=1000,tc_ice=200,总金额1000,冻结200;
- tc_inventory库存表:tc_total=100,tc_ice=20,总库存100件,冻结20件;
- tc_waybill运单表:tc_state=1,运单状态,出库中;
这样下单链路上的相关资源已检查并且预留成功;
2、资源提交确认
资源预留成功之后,执行资源提交执行:

- tc_account账户表:tc_total=800,tc_ice=0,即订单扣款成功;
- tc_inventory库存表:tc_total=80,tc_ice=0,库存消减成功;
- tc_waybill运单表:tc_state=2,运单状态,已出库;
这样下单链路上的相关资源已全部提交处理成功,这是最理想的状态;
3、失败回滚
整个过程是可能执行失败的,或者用户直接自己发起回退,则要回滚整个链路上的数据:

- tc_account账户表:tc_total=1000,tc_ice=0,取消账户冻结的200;
- tc_inventory库存表:tc_total=100,tc_ice=0,取消库存冻结的20件;
- tc_waybill运单表:tc_state=3,运单状态,已撤回;
这样下单链路上的相关数据都基于该笔订单做回退操作,恢复;
4、补偿机制
整个电商交易流程,不管是成功,还是完整的回退失败,都是需要在理想状态下,要求整个服务链路和数据是绝对正常的才行。但是在实际分布式架构下是很难保证的,所以在产品的设计上会预留很多操作入口,用来手动做事务补偿或回退操作:

大型复杂的业务系统中,直接修改数据库通常情况下是不允许的,一般核心流程会预留各种操作入口,用来处理突发状况,弥补数据的完整性,例如交易链路上,只要扣款成功,后续的数据无论如何都会补上,是不允许回滚的,当然如果没有扣款成功,订单有效期结束,该笔交易也就算做结束。
5、写在最后
通过电商交易的案例,和TCC模式的概念,描述了分布式事务的流程和处理思路,在开发时通常会选择现有的分布式组件来具体实现事务控制,这个流程后续再聊。
四、源代码地址
GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推荐阅读:架构设计
架构设计 | 基于电商交易流程,图解TCC事务分段提交的更多相关文章
- 架构设计 | 基于Seata中间件,微服务模式下事务管理
源码地址:GitHub·点这里 || GitEE·点这里 一.Seata简介 1.Seata组件 Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata将为用 ...
- Java生鲜电商平台-电商支付流程架构实战
Java生鲜电商平台-电商支付流程架构实战 说明:我一直秉承的就是接地气的业务架构实战.我的文章都有一个这样的核心. 1. 业务场景 2. 解决问题. 3.代码实现. 4.代码重构. 5.总结与复盘. ...
- 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户
阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...
- 如何一步一步用DDD设计一个电商网站(十一)—— 最后的准备
阅读目录 前言 准备 实现 结语 一.前言 最近实在太忙,上周停更了一周.按流程一步一步走到现在,到达了整个下单流程的最后一公里——结算页的处理.从整个流程来看,这里需要用户填写的信息是最多的,那么 ...
- 如何一步一步用DDD设计一个电商网站(十二)—— 提交并生成订单
阅读目录 前言 解决数据一致性的方案 回到DDD 设计 实现 结语 一.前言 之前的十一篇把用户购买商品并提交订单整个流程上的中间环节都过了一遍.现在来到了这最后一个环节,提交订单.单从业务上看,这个 ...
- 如何一步一步用DDD设计一个电商网站(十三)—— 领域事件扩展
阅读目录 前言 回顾 本地的一致性 领域事件发布出现异常 订阅者处理出现异常 结语 一.前言 上篇中我们初步运用了领域事件,其中还有一些问题我们没有解决,所以实现是不健壮的,下面先来回顾一下. 二.回 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式
在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
随机推荐
- P4383 [八省联考2018]林克卡特树 树形dp Wqs二分
LINK:林克卡特树 作为树形dp 这道题已经属于不容易的级别了. 套上了Wqs二分 (反而更简单了 大雾 容易想到还是对树进行联通情况的dp 然后最后结果总和为各个联通块内的直径. \(f_{i,j ...
- luogu P5325 Min_25筛
LINK:Min_25筛 新版感觉有点鬼畜 而且旧版的也够用了至少. 这个并不算很简单也不算很困难的知识点 学起来还是很麻烦的. (误入了很多dalao的blog 说的云里雾里的 甚是懵逼 这里推荐几 ...
- 不用注入方式使用Spring管理的对象中的方法,神奇
在小冷工作中遇到这么一个小问题,当你的业务层对象交给spring管理之后,在普通的类中调用这个类中的方法时候,会有个问题这个类在调用时候会一直返回一个null,而且还会抛出一个空指针异常. 小冷在遇到 ...
- 【NOI2001】方程的解数 题解(dfs+哈希)
题目描述 已知一个方程 k1*x1^p1+k2*x2^p2……+kn*xn^pn=0. 求解的个数.其中1<=x<=150,1<=p<=6; 答案在int范围内 输入格式 第一 ...
- canvas小画板--(1)平滑曲线
功能需求 项目需求:需要实现一个可以自由书写的小画板 简单实现 对于熟悉canvas的同学来说,这个需求很简单,短短几十行代码就能实现: <!doctype html> <html& ...
- “随手记”开发记录day20
练习软件的展示,尽量将软件全方面的展示给大众,希望不要像上次一样有许多遗漏的地方,让其他团队以为我们的软件没有完善的功能.
- Hive操作——删除表(drop、truncate)
Hive删除操作主要分为几大类:删除数据(保留表).删除库表.删除分区. 一.仅删除表中数据,保留表结构 hive> truncate table 表名; truncate操作用于删除指定表中的 ...
- C#LeetCode刷题之#389-找不同(Find the Difference)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4062 访问. 给定两个字符串 s 和 t,它们只包含小写字母. ...
- 自动化特征工程—Featuretools
Featuretools是一个可以自动进行特征工程的python库,主要原理是针对多个数据表以及它们之间的关系,通过转换(Transformation)和聚合(Aggregation)操作自动生成新的 ...
- python 文件读写with open模式r,r+,w,w+,a,a+的区别
模式 可做操作 若文件不存在 是否覆盖 r 只能读 报错 - r+ 可读可写 报错 是 w 只能写 创建 是 w+ 可读可写 创建 是 a 只能写 创建 否,追加写 a+ 可读可写 创建 否,追加写