事务的acid,隔离级别这里不说了可以查看
数据库事务的隔离级别及常见事务异常 - 简书 (jianshu.com)
或者其他资料
RM:资源管理器,泛本地事务对应的数据库资源
TM:事务管理器
XA 两阶段提交协议
db2,oracle等厂商使用xa两阶段提交协议实现分布式事务
- 一阶段
- TM 通知参与该事务的所有RM开始准备事务
- RM 在接收到消息后开始准备(写好事务日志,并执行事务但不提交)然后将就绪的状态返回TM,此时大部分事情已经做完了,二阶段耗时极短。
- 二阶段
- TM在接收到各个 RM的回复后,基于投票结果进行决策,提交或者取消。如果有任意一个回复失败,则发送回滚命令,否则发送提交。
- RM接收到命令后执行提交/回滚 后将结果返回给TM
上述两阶段协议是阻塞的,每个事务分支都不会提交,代表这些本地事务的锁资源不会释放,性能较差。如果3个事务分支,那么耗时会将比时间最长的某一个事务分支时间还要长一点,那么所有分支都需要阻塞锁住自己的资源。
两阶段提交的缺陷
- 同步阻塞
- 单点故障,一旦TM故障则所有事务阻塞无法进行
- 数据不一致,在二阶段处理中如果在事务协调器向参与者发送commit请求后局部网络故障,则会导致只有一部分参与者接收到commit请求,而其他的不会commit,这个事务数据不一致
- 状态不确定,如果TM发送commit指令后RM和TM宕机了,即使通过选举选处了新得TM没人知道之前的状态是什么样的,不知道如何恢复
分布式理论
CAP 理论 这个太多了可以网上搜索其他资料
CP,AP是我们选择的范围,一定要保证分区容错性。一致性和可用性的选择。
- CP:强一致性降低可用性,例如XA两阶段提交,seata的AT模式
- AP:最终一致性,强可用性,TCC,Saga以及基于消息的最终一致性
base理论可网上搜索其他资料
TCC 柔性事务
TCC的核心思想是通过对资源的预留,尽早释放对资源的锁,如果可提交,则确认预留的资源,如果要回滚,则释放预留的资源
TCC方案在电商,金融领域落地较多。TCC方案其实是两阶段提交的一种改进,对业务侵入较大,资源锁定交由业务方完成。
TCC显示的分成try,confirm,cancel这三个阶段的操作,try操作阶段完成业务的准备工作,在confirm操作阶段完成业务提交,cancel操作阶段完成事务的回滚
操作流程
- 业务应用向事务协调器发起开始事务的请求。
- 业务应用调用所有服务的try接口
- 业务应用根据所有服务的try接口成功与否决定提交或者回滚请求发送到事务协调器
- 事务协调器接收到提交/回滚请求开始调用服务confirm/cancel,如果调用失败会重试
1.优点通过自定义资源锁的维度,可降低锁粒度提高吞吐
缺点
1.业务侵入太强。业务逻辑的每个分支都需要实现try,confirm,cancel这三个操作,改造成本很高。
- 实现难度较大,要根据网络,系统故障等不同失败原因来实现不同的回滚策略。并且为了满足一致性需要实现幂等。
基于消息的最终一致性
- 业务操作时记录一条消息数据到数据库,状态为待发送。并且和业务操作在同一事务内。
- 在消息数据记录完成后通过定时任务轮询,并发送mq。发送mq失败则重试,知道确认投递成功。下游消费者如果失败则重试消费。
sage 基于冲正模式的分布式事务
通过对正向流程的逆向流程进行冲正
seata主推模式AT模式
AT模式的诱人之处在于,只需要部署一个seata-server,创建好对应的表。
然后通过 @GlobalTransactional 注解就可以做到自动执行AT模式的分布式事务逻辑。
如图1根据执行顺序可知整个AT模式的执行流程
- TC 即 事务协调器单独部署的seata-server端
- TM transaction-manager 即事务管理器通过引入jar包集成在业务服务也是seata的client端用于开启提交回滚全局事务
- RM resource-manager 即资源管理器通过引入jar包集成在业务服务也是seata的client端,用于与本地事务交互然后向TC上报注册分支事务
seata AT模式的回滚/故障恢复 是通过undolog存储的前后镜像来实现。
上述流程的这些逻辑如何自动的,通过对dataSource,connection,statement,preparedStatementProxy对原有的数据库操作逻辑进行代理增强,然后进行上述AT的流程,目前先不去详细说明源码逻辑,后续如果有工作中实际的业务需求需要仿照源码实现,会贴出新的文章详细了解一下源码逻辑。
1.分布式 cap 理论,一致性,分区容错性,
分布式系统必定会面对网络问题,脑裂等问题故要保证分区容错性
一般选择cp 或者 ap 即选择放弃 一致性(最终一致性)和 可用性
典型的实现,zookeeper放弃可用性保证强一致,需要选主,在无主期间强制阻塞所有请求。
适用于分布式锁,需要保证一致性的元数据存取(kafka),HA主备切换 (cannal)
eureka / nacos等注册中心,放弃一致性,保证可用性,多个节点平等,无master,每个节点的信息可能不一致,每个节点所拥有的
被注册信息可能不一致,应用节点上线和下线并不是所有注册中心都有一致性的信息
最终一致性也可以通过消息来保证,有一定延迟性
base理论:基本可用,软状态,最终一致性
2.两阶段提交协议:
一阶段:
1)事务协调器通知参与当前事务的所有资源管理器(resource即本地事务的db)开始准备事务
2)资源管理器在接收到消息后开始准备(写好事务的日志并执行事务,但不提交)之后将就绪的状态提交给事务协调器,此时已经将事务的大部分
事情已经做完了。
二阶段:
1)事务协调器在接收到资源管理器的消息后,基于投票结果,其实就是是否所有分支都执行成功了,如果都成功则
进行提交,如果有失败的则进行取消提交(包括回复消息失败)
2)所有资源管理器接收到 事务协调器的发送的命令后 进行回滚/提交后,再返回一个结果给事务协调器
缺点:1)同步阻塞如果占用公共资源有消耗 。2)事务协调器有单点故障,2阶段故障则1阶段都锁住了对应的资源
3) 数据不一致,如果二阶段有网络问题,例如协调器发送 commit的指令给资源管理器则有的接收到了 就commit了,有的没接收到
超时回滚,数据不一致。
4)状态不确定,在事务协调器发送了commit的指令后宕机,且接收commit的也宕机了,待协调器选出新主后这个事务的状态不确定了
TCC柔性事务: 金融领域较多。
分为 try,confirm,cancel ,完全由开发者操作,可以选择有锁无锁,锁定的范围等等。
1)业务应用向事务协调器发起开始事务的请求
2)业务应用调用所有对应服务的try接口
3)业务应用根据try接口是否全部成功,决定提交或者回滚事务,并发送这个决定到事务协调器
4)事务协调器根据提交或者回滚调用所有服务的 confirm/cancel接口,如果调用接口失败会重试
缺点 业务侵入太大,实现难度大,不同故障还有不同的回滚策略等等,还需要实现幂等性
消息最终一致性的方案,一般采用消息表轮询的方式
RM 资源管理器,本地事务的db TM事务管理器, TC 事务协调器维护全局事务和分支事务的状态,推进事务的两阶段处理,对于AT模式
的分支事务,TM负责事务并发控制
Saga模式: 冲正补偿服务,是一种长事务解决方案。有一个步骤失败,则补偿事务 逆向冲正之前的提交
优势:无全局锁,性能好。 参与者可以使用事件驱动的异步执行,高吞吐。补偿服务易于理解。
劣势:一阶段已经提交了的数据不能保证隔离性,可能会在最终冲正回滚的间隙被其他线程/服务看到然后读取或者使用了脏数据
AT:模式,seata主推,只需要注解,undo表,引入简单,无侵入
seata 通过DataSourceProxy代理对数据库操作进行拦截,除了执行原始的请求,会进行产生前镜像,后镜像,加锁数据,保存事务日志等
1)TM (客户端)向TC(服务端)发起全局事务的请求,TC返回全局事务的id
2)开启本地事务,生成undolog数据,执行业务sql,生成redolog数据,保存到undolog表,生成全局锁数据
3)RM发送注册分支事务请求,如果全局事务对当前本地待提交事务数据加全局锁成功,则TC返回分支事务id
4)提交本地事务
5)RM上报分支事务状态
6)远程调用其他服务,并带上全局事务id
7)开启本地事务,同样做 2)的事
8)同样的 3)
9)同样4)
10)同样 5)
11) 返回远程调用成功
12)发起全局事务的 提交/回滚 (如果回滚则通过 undolog表进行回滚)
RM向TC注册分支事务会为当前本地事务的数据 加全局事务的锁,如果存在分布式事务的全局锁冲突,默认失败回滚。也可以
配置为sleep重试。
seata对于 java.sql包下的 DataSource Connection Statement PreparedStatement 4个接口进行了代理增强
,在sql执行前后,commit rollback前后,进行一些分布式事务的操作。例如分支事务注册,分支状态回报,全局锁查询,事务日志插入等。
ResourceManager 通过SPI获取实现类,可以有 tcc,at,saga等
为什么要查询 seata的全局事务锁,为了支持本地事务的 读未提交以上级别的事务,因为前一个事务分支实际已经提交了,但是在下一个事务分支
还未结束,对于当前这个分布式事务,前一个事务理论上还是未提交,所以如果有其他分布式事务在读上一个事务的数据,理论上不应该读到,但是对于其他本地事务就不做控制了,这相当于是将分布式事务自己保证得了与本地事务的逻辑一致性吧。。。
但是实际 分布式事务的中的查询,默认是读未提交,那么这个查询锁默认不会存在。有一定性能损耗。
PreparedStatement 执行预编译sql,也就是 带有 ? 替换符的sql缓存到缓冲区。Statement执行静态sql
代理 dataSource 和 connection为了和 TC服务建立连接,Statement 和preparedStatement为了拦截 sql的执行时机,如果在分布式事务下,for update 默认有分布式事务锁
Druid库生成语法树 AST
如果处于全局事务中会绑定全局事务id,要将本地事务的 autoCommit设置为false,保证分布式事务的查询前后镜像,插入事务日志也包含在本地事务内、多主键只有mysql支持 AT模式
TCC:
1)资源预留模式,例如 交易需要冻结对应余额,如果cancel则不转账,confirm才转账
2)补偿模式:可以是使用类似 AT模式的补偿,也可以类似 sage模式的 冲正补偿等等
seata使用了 消息合并发送逻辑,多个分布式事务事务,一些不同类型的请求也会合并发送,小包合并提高RPC性能
消息合并并对 出现大量合并情况打印日志,出现消息积压可提前扩容
二阶段 提交由TC 主动推进,AT由本地事务保证幂等,而 TCC需要用户自己处理好幂等性
全局锁 专门为AT模式设计,微服务 本身大部分是垂直拆分的逻辑,对分布式事务的隔离性要求较低,只是提供了这样的场景、
先本地事务锁,再全局,释放也是,顺序固定,获取不到全局锁 fastFail,seata提供了 LockManager,可进行扩展子类
全局锁数据存入 map 中,并且通过数据库id, 表名, 桶id , 锁map 映射,为防止数据过多,利用桶id 来分片