分布式事务
- 分布式与事务
- 横跨不同服务器,由多个本地事务组成
- 如无必要,不要引入分布式事务
- 分布式事务的典型场景
- 跨JVM进程:微服务,通过业务补偿解决
- 自动的补偿机制就是分布式事务。。。
- 跨数据库实例:
- 一个应用调用了多个库,不应该出现这样的情况,应该通过服务调用
- 数据库做了拆分,处于不同的数据库实例中
- 尽量避免这样的拆分,
- 通过绑定表这样的形式,比如订单库,都通过订单ID分片
- 但是客户级的处理就无法避免了
- 尽量避免这样的拆分,
- 跨JVM进程:微服务,通过业务补偿解决
- 解决方案
- 数据库维度和服务维度
- 两阶段提交
- XA 协议: 数据库视角的两阶段提交,性能原因不用,因为阻塞
- Saga:服务视角的两阶段提交,按逻辑依次调用服务,异常就补偿。因为提交了就不阻塞,但是不能保证隔离性
- 可靠消息最终一致性:依靠可靠消息驱动,变种的Sage
- TCC:应用层三阶段提交
- try:业务检查,资源预留
- confirm:业务处理
- cancel:业务回滚
- 业务侵入性,开发成本高,本身的业务需要做资源预留与隔离
- 实际使用: 金融场景中易于做资源预留的扣减模型。
- SEATA 柔性事务
- 支持TCC、SAGA 和 XA
- AT服务层的两阶段
- 优点
- 自动化
- 解析SQL,获取更新前和更新后镜像,插入到undo log表。
- 回滚使用undo log的更新前镜像
- 提交了数据库本地事务,不阻塞
- 自动化
- 缺点:整体效率不高,因为多查询两次,插入一次,一条数据库交互变成了4次
- AT结构
- TM (事务管理器):事务发起方,通过TC完成全局事务的开启,提交和回滚
- RM (资源管理器):事务参与者,上报TC,并配合TC完成本地事务提交和回滚
- TM 和 RM 以 jar 包的方式同业务应用一同部署
- TC (事务协调器):TC 是一个独立部署的服务
- 周期
- TM 要求 TC 开始一个全新的全局事务。
- TC 生成一个代表该全局事务的 XID,XID 贯穿于微服务的整个调用链。
- 作为该 XID 对应到的 TC 下的全局事务的一部分,RM 注册本地事务。
- TM 要求 TC 提交或回滚 XID 对应的全局事务。
- TC 驱动 XID 对应的全局事务下的所有分支事务完成提交或回滚。
- 优点
- 完整流程
- 一阶段:
- 解析SQL:update product set name = ‘GTS’ where name = ‘TXC’;
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
- select id, name, since from product where name = ‘TXC’;
- 执行业务SQL
- 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
- select id, name, since from product where id = 1;
- 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表
中。 - 提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁 。
- 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC
- 二阶段-回滚
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
- update product set name = ‘TXC’ where id = 1;
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
- 二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
- 一阶段: