dubbo、spring cloud、sofa - rpc、motan和gRPC
支持基于数据库存储的集群模式,可水平扩展。
支持配置中心、注册中心、spi扩展
若干分支事务的整体协调
理解:一个全局事务只有一个TM,但是每一个需要调用别的服务的服务也就是全局事务的发起者都应该持有或者说是具备成为一个TM的能力。
从这里我们可以看到,全局事务的发起者通过rpc调用开启远程的分支事务,并且将全局事务id通过调用链传播下去,很明显,这一条调用链只有起点能够对整条调用链的成功与否进行感知,rpc调用链让起点天然具备了决定全局提交或是回滚的能力。
数据库一定要支持本地事务——几乎是所有事务模式的基础
数据表一定要定义主键——考虑到事务的隔离性,AT里面很有用。
加粗下划的部分是AT模式最大的价值体现,也是它最大的卖点。
两步走 - 2pc在广义上的实现
关于为什么要使用undolog的形式问题 ——如果不使用undolog,那么有什么方式能够让已提交的事务进行回滚呢?
答案是使用数据补偿方法,但是如何保证数据补偿方法的幂等性,这一直以来就是个难题。
为什么注册分支在执行sql之后?
因为本地执行事务出现异常,就不需要提交自动可以回滚,那么这个分支事务就没有必要注册到TC中去了。
所以看表象是注册分支这个行为在执行sql之后,但其实本质上是把注册分支这个事尽量后移,让有可能出现问题的地方都先暴露出来,注册分支之后只关注提交事务的状态,让TC能够更加专注。
当然,这样的流程设计并不止是为了流程简洁 ,代码清晰,更加重要的作用,请见下一章节。
回顾:如果分支事务已经提交,Seata是如何回滚的?
根据前后镜像来进行回滚。
提到回滚,那必定要提到另一个概念,事务隔离。那么Seata是怎么保证事务之间进行隔离的呢?
假设本地事务A注册完分支之后即提交了,对于其他的本地事务B\C\D来说,并不知道A的分布式事务其实尚未完成,那么就存在对已提交的A事务的数据进行更改的可能性。
“行锁”。
在上面的例子中,Seata的RM在为A在TC中注册分支事务时,会将行的唯一标识也一并带上,在TC中注册,而TC会控制非A事务无法对此行进行修改。
在“行锁”解决方案中,行的唯一标识是至关重要的存在,而主键则完美的符合了行锁的唯一标识的需求。
多说一句,这样的“行锁”与行锁本身的设计还是挺像的,就像是在说“数据库的不顶用了,那我们就自己来造一个”。
附带一个undolog的范本:
*:以下步骤都是基于存于本地数据库的undolog记录实现的
一、写隔离:
背景:事务A和B需要对同一行进行更改,并且,AB都交由TC管理。
当事务A拿到全局锁之后,事务B可以在本地执行业务操作,但是当事务B获取全局锁时,需要先等待A释放全局锁才行,
若B等待超时,则事务B会回滚本地事务,释放本地锁;
写的特殊情况:1.事务A 获得了全局锁并在本地提交。2.事务B获取了本地锁并对全局锁进行等待。3.A发生了异常,需要进行全局回滚。
特殊情况发生原因:A需要回滚,而回滚需要获取本地锁,然而本地锁已经被B持有了,形成了互相等待的死锁局面。
这种情况下,根据刚才的流程,事务B在等待全局锁的时候会因为获取锁超时而回滚本地事务,当事务B回滚并释放锁后,事务A 获得本地锁,进行回滚。
二、读隔离:
背景:事务A全局锁住了某一行但是已经本地提交,而事务B需要对同一行进行读取,并且,AB都交由TC管理。
在官方文档中,“读已提交”是在“特定的场景下”才必须要求的,我们在使用的时候一定要对业务进行分析,斟酌地进行读隔离的配置。
TCC模式的想法是好的,但是实际在业务中操作却有一定的难度,在各方面都不如AT模式,故目前了解即可。
try的目的就是为了保证confirm能够成功
业务只需要关注try,即预留资源的工作,保证业务能够成功,事务的部分Seata的TC会自动完成
允许空回滚
try阶段结果信息可能无法顺利到达TC,之后触发回滚。
防悬挂控制
try阶段的结果信息到的太晚了,已经触发了回滚操作。
需要拒绝已经回滚的try。
需要幂等
可能会有超时重试、补偿,try、confirm、cancel都有可能被重复调用
这部分我就不多讲了,因为“需要幂等”这一条就算尝试去实现其实也是有风险的,业务上肯定是不如AT模式的。