分布式事务

526 阅读9分钟

单机事务

理论

ACID四大特效。

1.原子性

2.数据一致性
几个方面
1)多个应用系统访问同一个单机数据库
看到的数据,应该是一样的
2)数据库本身的约束特性
比如,唯一键对列的约束,值必须唯一等等

3.事务隔离
两个事务,互不影响。就是一个事务,要么看到另外一个事务的修改之前或修改之后的数据,但是不能看到事务执行过程当中的数据。

4.持久化

底层实现原理

mysql innoDB 日志记录成功或失败,如果失败,会继续补偿提交失败的sql。

多机事务

理论

1.CAP
1)Consistency,数据一致性 //集群里的每个节点,数据都要一致
2)Available,可用性 //集群里的每个节点都可用
3)Partition,分区容错性 //集群里的某个节点故障,剩下的节点仍然可以作为集群继续服务,并且还要自动故障转移,自动故障恢复

太拗口了,并不好理解。

特别是最后一项的P-分区容错性partition,名字取得不好,看名字不知道啥意思。其实就是集群的意思,而且这个集群的目的就是要避免单点故障,其次即使出现单点故障,集群仍然可以工作,比如redis集群可以实现1.故障自动转移(晋升从数据节点为新的主数据节点)2.故障自动恢复(旧的主数据节点重新连接之后,降级为新的主数据节点的从数据节点)。

而且,还有一个关键的问题是,CA,单独理解都没有问题,一个是集群里每个节点的数据一致性,一个是集群里的每个节点可用性。

这个理论,有一个前提就是,直选满足三个要求里的两个,为什么?关键点就在于集群二字。

要解决单点故障的问题,就要使用集群架构部署。使用集群,必然带来各个节点的数据不一致的问题,也必然带来某个节点会出现故障挂掉的问题。

总之,重点关注对象是节点集群和节点的分布式


最佳实践
是高可用,和集群避免单点故障。放弃强一致性,但是确保数据最终一致性。

2.BASE
1)基本可用Base Available
分布式系统,非核心业务系统挂了可以,但核心业务系统仍然可用。即允许非核心业务系统不可用。
2)软状态Soft State
数据存在中间状态,但是最终数据一致。
3)最终一致性Eventual Consistency 数据最终一致性,存在一定时间的延迟。

解决方案

1.两阶段提交
1)第一阶段
请求ready
响应ready ok or not ok
2)第二阶段
全部ready ok,才commit;
否则,rollback。

2.TCC 1)Try,就是执行 2)Confirm,就是提交 3)Cancel,就是回滚

3.补偿式提交

两阶段提交/2PC/Paxos/Raft

这些都是两阶段提交协议,就是两个步骤:
1.投票
2.提交


核心角色
1.数据库1
2.数据库2
3.协调者zookeeper/事务管理器(自己实现,比如支付系统的分布式服务解决方案-补偿式提交)


架构图

缺点
事务管理器/协调器存在单点故障。


架构图

步骤
1.投票

2.提交

缺点
事务管理器/协调器存在单点故障。

注:
1.超时机制
2.一段时间内补偿提交即重试多次

总结
每个步骤的每个请求都有响应,只有收到成功响应才说明当前请求成功,然后再做接下来的任务。

三阶段提交/3PC

是什么
就是在2PC的基础之上多了一个preCommit预提交的步骤/阶段。


架构图


总结
1.如果一个事务已经preCommit成功,那么必须能确保最终提交成功。
2.怎么确保最终成功?重试策略。

3.那preCommit有什么作用呢?待补充。

TCC

流程图


优缺点
优点
解决单点故障的问题


发展历史
1.最早,是国外论文
2.支付宝CTO程立的演讲
3.国内的各种开源实现


故障恢复
就是有的机器的事务成功,有的失败。两种解决方法:
1.通过日志
2.通过插入数据到表记录


幂等性
按由谁来处理幂等性问题来划分
1.由分布式事务框架处理
2.业务服务自己处理

最佳实践
推荐使用第一种,业务每个业务服务自己处理太麻烦,最好是基于分布式事务框架统一处理幂等性问题。


参考
www.bytesoft.org/tcc-intro/ //基于TCC解决方案的开源项目实现
www.bytesoft.org/

补偿式提交

应用场景
在我们工作实践-支付系统当中,有使用到。就是一个业务系统有两个写sql,要写到不同的数据库(order库和account库),即一个是订单库,一个是账户金额库。


流程图


具体的流程
一、正常情况
1.开启分布式事务begin()
2.sql1
3.sql2
4.结束分布式事务end()

二、sql1失败
1.begin()
2.sql()
catch异常。在捕获异常里,取消分布式事务cancel(),即结束分布式事务,return。

三、sql1成功,sql2失败
1.beigin()
2.sql1
3.sql2
catch异常。在捕获异常里,retry重试(我们叫重新确认reconfirm),也就是所谓的补偿提交一次,return。这是第一次重试。

后面的定时重试,由定时任务扫库实现。


事务管理器的方法
begin()的作用是什么?
做一些初始化的工作。比如,让分布式事务这个独立的服务,开始做一些准备工作,例如创建一些初始化类啊什么的。

重试的作用是什么?
就是重新执行一次sql2。第一次在捕获异常里重试和定时任务重试有什么区别吗?没有区别,都是直接调用分布式事务管理器.重试()方法,即iTransactionManager.reconfirmTransaction(transactionId);

回滚的作用是什么?
按相反的方向重做一遍。比如,账户库-金额变动,按相反方向重做一遍。

end()的作用是什么?
设置分布式事务(特别是主流程-代表当前这个分布式事务,还有两个子流程sql)的状态为成功。

注:cancel的作用是什么?
支付系统没有这个方法。


代码 //分布式事务管理器

public interface ITransactionManager {
	
	public String beginTransaction(MainBusinessActivity businessActivity); //开启分布式事务

public boolean reconfirmTransaction(String transactionId); //入金必须成功,如何确保一定成功?重试。

public boolean rollBackTransaction(String transactionId); //出金sql2如果失败/异常,必须回滚。
	
	
	
	public boolean endTransaction(String transactionId); //结束分布式事务
	
	}

总结
1.事务管理器
定义接口方法。

2.如何调用?
事务管理器.方法()。

3.根据业务情况,是使用重试还是回滚
1)入金
重试
2)出金
回滚

4.角色
1)服务
同一个服务,包含了多个不同数据库的sql
2)数据库有两个
订单库
账户金额库
3)分布式事务服务
独立的专门用于解决分布式事务的服务


sql数量
1.实际业务中,所有的系统,所有的业务,都是只有两个sql
2.如果有多个sql怎么办?
目前是,第二个sql,catch{1.重试:事务管理器.重试() 2.消息通知 3.return }。
现在,如果有第三个sql,那么第二个sql里的catch就不能return,而是只应该保留1,去掉2 3等所有其他的步骤,总之,把所有其他的步骤挪到最后一个sql的catch里。

一些概念和角色

1.数据库
资源管理器RM(Resource Manager)
2.事务管理器
独立的分布式事务服务,专门用来处理和协调分布式事务的问题


1.单机事务
数据库ACID开源确保单机事务
2.分布式事务/全局事务
基于单机事务来实现多机事务,不然单机事务多个sql里的每个sql都要去判断成功还是失败,这样实现起来就太复杂了。


1.try
就是指向sql,但是没有真正commit
2.confirm/commit
真正的commit,永久写到数据库里去了
3.cancel/rollback
就是回滚,对还没有被提交commit的执行try进行回滚操作

以上,和单机里的概念,其实是一样的。很好理解。

注:还有一种回滚办法,其实不是数据库概念里的回滚,所以也不是利用数据库的rollback功能进行回滚。那怎么实现回滚操作的?其实就是应用程序层面,程序员来进行回滚,具体来说就是反方向执行一次sql。比如,账户金额减了100块钱,回滚的时候就加100块钱,就是再执行一遍sql,即执行同样的service/method/sql(只是金额字段的值刚好相反)。


1.传统的分布式事务解决方案叫两阶段提交

2.TCC是对两阶段提交的改进,解决单点-事务管理器的问题

3.补偿式提交,是比较简化的解决方案
因为它没有TCC中的业务方的confirm和cancel操作(即单机事务),业务方不能控制即没有接口方法来提交或是回滚。

所有的confirm/commit和cancel/rollback都是全局性的——即由事务管理器来实现。

单机事务

按层级划分
1.数据库
本身支持事务功能

2.jdbc 数据库厂商为每种编程语言,都提供了API接口,来操作数据库,包括建立连接、执行sql语句、commit/rollback。比如,提交事务就是,connection.commit()。

3.持久层
hibernate/mybatis

4.spring SpringTransactionManager类


总结
不管是哪一层,都只是对上一层的封装,当然,最终执行的仍然还是数据库厂商本身为不同编程语言提供的接口API。
java,就是通过jdbc。
更上层的代码和应用,都只是对jdbc做的各种封装,使得更加方便和容易操作访问数据库。


spring
所以,如果想要基于spring框架,或者其他的servcie业务层框架去实现分布式事务,那么分布式事务服务就要封装SpringTransactionManager。因为分布式事务是基于单机事务去实现的,所以必须得这么解决。

参考

www.infoq.cn/article/sol… //好文章,有实践经验

juejin.cn/post/684490… //前面一段写的很好,到了解决方案就完全不知道说什么了