编程式事务
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
int resultJames = jdbcTemplate.update(sql_james,-money,"james");
int resultPeter = jdbcTemplate.update(sql_peter,money,"peter");
if (money > 20){
throw new RuntimeException("money too large");
}
} catch (DataAccessException ex) {
transactionManager.rollback(status);
throw ex;
}
transactionManager.commit(status);
return "success";
}
---------------------------------------------
事务 Transaction
数据库支撑业务的一个逻辑单位,由 n 个数据库操作构成
本地事务 Local Transaction
单个 JVM 进程访问单个数据库的事务
分布式事务 Distributed Transaction
跨库跨 JVM 进程的事务
---------------------------------------------
事务的四大特性 ACID
---------------------------------------------
原子性 Atomicity
一个事务被视为不可分割的最小单位,所有操作要么全部成功,要么全部失败
一致性 Consistency
事务执行前后,数据的状态都是正确一致的
隔离性 Isolation
一个事务所做的修改在提交以前对其他事务不可见
持久性 Durability
一旦事务提交,所做的修改就会永久保存
---------------------------------------------
Open Group DTP 模型
AP(Application Program) 应用程序
RM(Resource Manager) 资源管理器,一般为数据库,控制分支事务
TM(Transaction Manager) 事务管理器,负责协调和管理事务,如 Spring 提供的 Transaction Manager
Open Group XA 规范
Open Group DTP 定义的 TM 与 RM 通信的接口规范,由数据库厂商提供
TM 向 AP 提供 应用程序编程接口,AP 通过 TM 提交或回滚事务
TM 通过 XA 接口通知 RM 事务的开始结束以及提交回滚
---------------------------------------------
2PC Two-Phase Commit 两阶段提交协议
准备阶段
RM 执行实际的业务操作,锁定资源,但不提交事务
提交阶段
如果有任一个 RM 执行失败,TM 通知所有 RM 回滚,否则,TM 通知所有 RM 提交,提交阶段结束资源锁释放
2PC 的缺陷
同步阻塞
所有分支事务均处于阻塞状态, 资源锁需要等到两个阶段结束才释放,性能较差
单点
如果协调者出现故障,参与者一直阻塞并一直占用事务资源
数据不一致
提交阶段,如果只有部分参与者执行了 Commit 请求,会导致节点数据不一致
---------------------------------------------
两阶段提交
1.jar 包导入
spring-boot-starter-jta-atomikos
JTA - Java Transaction API
XA 分布式事务规范在 java 的实现
2.配置 JtaTransactionManager
3.定义几组 datasource, JdbcTemplate
4.spring 里的申明式事务支持 JTA
@Transactional
public String transfer(int money) {
int resultJames = jamesJdbcTemplate.update("INSERT INTO bank_a(money,user_name)VALUES (?,?)",-money,"james");
int resultPeter = peterJdbcTemplate.update("INSERT INTO bank_b(money,user_name)VALUES (?,?)",money,"peter");
if (money > 20){
throw new RuntimeException("money too large");
}
return resultJames+"-"+resultPeter;
}
---------------------------------------------
3PC Three-Phase Commit 三阶段提交协议
CanCommit 事务询问
协调者向参与者发送事务内容,询问是否可以执行
参与者返回 Yes 或 No
PreCommit
事务预提交 -- 所有参与者均反馈 YES
协调者向所有参与者发出 PreCommit 请求,参与者执行但不提交事务
各参与者向协调者反馈 Yes 或 No
中断事务 -- 任何一个参与者反馈 NO,或者等待超时
协调者向所有参与者发出 abort 请求,参与者收到 abort 请求,或者等待协调者超时均会中断事务
do Commit
提交事务 -- 所有参与者反馈 Yes 响应
协调者向所有参与者发出提交指令
中断事务 -- 任何一个参与者反馈 NO,或者协调者等待参与者反馈超时
协调者向所有参与者发出回滚指令
参与者等待超时,执行事务提交
---------------------------------------------
3PC 的优点
增加了一个 CanCommit 事务询问阶段,尽早发现无法执行的操作及时中止后续动作,降低了阻塞
增加了超时机制,降低了阻塞
参与者等待 PreCommit 指令时,若等待超时就 abort,降低了阻塞
参与者等待 Commit 指令时,若等待超时就 commit 事务,降低了阻塞
3PC 的缺陷
数据不一致问题仍然存在,
第三阶段协调者发出了 abort 请求,如果某些参与者没有收到 abort,那么就会自动 commit,造成数据不一致
---------------------------------------------
基于 Base 理论的柔性事务
通过适当放宽 强一致性要求 为 最终一致性要求,来换取高可用
---------------------------------------------
---------------------------------------------
TCC 补偿型 柔性事务
TCC 把业务拆分成 Try, Confirm, Cancel 三个步骤
主业务服务
负责调用所有分支事务的 try 服务
从业务服务
负责提供预处理(Try), 确认(Confirm), 撤销(Cancel)三个接口
事务协调器
控制整个业务活动,维护全局事务的事务状态和每个分支事务状态,调用所有从业务服务的 Confirm 或 Cancel 操作
Try 操作
做业务检查及资源预留
Confirm 操作
确认执行,使用 Try 预留的资源
Cancel 操作
取消执行,释放预留资源
主业务服务首先调用所有分支事务的 try 服务,任何一个分支事务的 try 失败,TM 将会发起所有分支事务的 Cancel 操作,若 try 操作全部成功,TM 将会发起所有分支事务的 Confirm 操作
TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,如果重试失败,人工介入
TCC 优点
性能提升
控制资源锁的粒度变小,不会锁定整个资源
数据最终一致性
基于 Confirm 和 Cancel 的幂等性,保证事务最终一致性
可靠性
解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群
TCC 缺点
对应用的侵入性非常强,业务逻辑的每个分支都需要实现 try confirm cancel 三个操作,定制化程度高,开发成本高
---------------------------------------------
2PC
强一致性的刚性事务,可用性差,服务有延迟,数据不一致问题
3PC 改良版 2PC
询问阶段,超时机制 降低了阻塞,可用性差,服务有延迟,数据不一致问题
2PC / 3PC 刚性事务 适合短事务, 低并发的场景
TCC 柔性事务 适合长事务,高并发的场景
---------------------------------------------
TX-LCN 框架
一款事务协调性框架
TxClient 分布式事务的发起方或参与方
TxManager 分布式事务的控制方
1.创建事务组
事务发起方开始执行业务代码之前先调用 TxManager 创建事务组对象,拿到事务标示 GroupId
2.加入事务组
参与方在执行完业务方法以后,将该模块的事务信息通知 TxManager
3.通知事务组
发起方执行完业务代码以后,将发起方执行结果状态通知给 TxManager,
TxManager 通知相应的参与模块提交或回滚事务,并返回结果给事务发起方
---------------------------------------------
Seata
阿里开源的分布式事务解决方案, 性能较好, 对业务零侵入
XA 模式
XA 强一致性 刚性事务, 通过 XA 协议来管理分支事务
TCC 模式
TCC 把业务拆分成 Try, Confirm, Cancel 三个步骤,通过事务管理器在业务逻辑层面根据每个分支事务的执行情况分别调用该业务的 Confirm 或者 Cancel 方法
AT 模式
在 XA 的基础上做了很多优化
TM Transaction Manager
作为 Seata 的客户端,与业务系统集成
负责开启一个全局事务,发起全局提交或全局回滚
RM Resource Manager
作为 Seata 的客户端,与业务系统集成
负责分支事务注册, 提交和回滚
TC Transaction Coordinator
作为 Seata 的服务器,独立部署
事务协调器,负责协调分支事务的提交或回滚
Saga 模式
Saga 模式是 SEATA 提供的长事务解决方案,把一个长事务拆分成多个本地短事务,每个短事务都直接提交,如果每个短事务都提交成功,全局事务自动 success, 如果某个短事务提交失败,有两种补偿恢复方式,一种重新提交失败的短事务,一种是回滚已提交的短事务,回滚的话,每个短事务在提交数据的同时提交回滚逻辑到 undo_log 表,已 commit 的数据进行反向回滚,回滚不是利用 db 回滚,而是通过 undo_log 表的记录把之前的数据改回去
---------------------------------------------
---------------------------------------------
DTS
为了解决 4 个问题,我们开发了这套分布式事务框架
1.需要支撑时间跨度和空间跨度都比较大的长事务
2.在高并发的场景下保持高性能
3.各种异常情况下,不影响业务的正常运作
4.涉及到资金操作,对用到的框架需要有一定的把控性,最好做到一切尽在掌握之中
思路
1.一个跨多个服务节点的跨库事务,由多个同一服务节点的同库事务组成,同库事务通过 spring 申明式事务管理,跨库事务通过框架管理,一个分布式事务内的分支服务正常调用,如果有异常就再调一次,再异常就不用管了,后台的定时任务会去处理非正常结束的事务,当然框架有事务注册和追踪功能
事务注册 - 主库
主业务活动记录表
主业务活动 ID,状态(开始,结束),分支服务个数,异常处理方法(回滚,重复提交)
分支业务活动记录表
分支活动 ID, 主业务活动 ID,服务名,方法名,参数序列化值,创建时间
跟踪 - 分库
分支业务状态记录表
主业务活动 ID,分支服务名,方法名,状态(已提交 已回滚),创建时间,状态更新时间
由于系统保证的是数据的最终一致性
需要业务系统自身设计业务的调用顺序,从而保证数据一致性的延后不会对系统产生影响,比如,提现要先扣钱再加钱,因为扣钱成功加钱失败,后台定时任务最终会加钱成功,但如果先加钱再扣钱,那平台账号的钱转走就扣不了了,针对支付系统,涉及到账务系统扣减金额的业务必须先调用账务系统,再处理业务系统(失败处理方式为回滚 CANCEL ),涉及到账务系统增加金额的业务必须先处理业务系统,再调用账务系统(失败处理方式为重新提交 RECONFIRM )
1.开启事务
// 保存事务记录到 主业务活动记录表 / 分支业务活动记录表
// 异常处理机制为回滚的事务,分支业务活动需注册用于回滚的服务名,方法及参数
// 异常处理机制为重新提交的事务,分支业务活动需注册用于重新提交的服务名,方法及参数
// 把 transactionId 存在 ThreadLocal
String transactionId = TransactionManager.beginTransaction(business);
2.分支服务调用
通过 aop 记录分支服务状态,通过 spring 申明式事务,确保分支服务执行情况和状态记录一致性原子性
分支服务实现方法上通过自定义注解标明分支服务名,方法类型为 resubmit / rollback,
aop 记录分支服务状态的切面从 ThreadLocal 取出 transactionId,从自定义注解中取出服务名及方法类型
根据方法类型来记录分支服务的状态 resubmit / rollback
3.结束事务
// 正常结束
// 更新主业务活动记录状态为 end
TransactionManager.endTransaction(transactionId);
// 异常结束 - 回滚
// 回滚已提交的分支服务,通过 aop 记录分支服务状态
// 回滚成功,更新主业务活动记录状态为 end
// 回滚异常,不做处理,交由定时任务下一次处理
// 每个服务需提供查询分支服务状态的方法,从框架类继承即可
TransactionManager.rollBackTransaction(transactionId);
// 异常结束 - 重新提交
// 如果所有的分支服务都没执行,就无需重复提交,直接更新主业务活动记录状态为 end
// 如果有执行成功的分支服务,就重新提交未执行的分支服务,通过 aop 记录分支服务状态
// 重新提交成功,更新主业务活动记录状态为 end
// 重新提交异常,不做处理,交由定时任务下一次处理
// 每个服务需提供查询分支服务状态的方法,从框架类继承即可
TransactionManager.reconfirmTransaction(transactionId);
---------------------------------------------