1 2PC
- 两阶段提交协议。它引入了一个事务协调者角色,来管理各个参与者
- 请求提交阶段
- 协调器向所有参与者发送事务请求,询问是否可以执行事务,然后各个参与者响应Yes或者No
- 提交阶段
- 基于第一阶段的投票结果进行决策
- 当所有参与者同意提交,协调者才会通知各个参与者提交事务。
- 否则协调者通知各个参与者取消事务。
- 参与者接收到协调者发来的消息执行 本地commit或者 Rollback.
- 基于第一阶段的投票结果进行决策
- 请求提交阶段
- 优点
- 利用数据库自身的功能进行本地事务的提交和回滚,也就是提交和回滚实际不需要我们实现。
- 如mysql XA规范实现。
- 利用数据库自身的功能进行本地事务的提交和回滚,也就是提交和回滚实际不需要我们实现。
- 不足
- 提交协议是阻塞协议,如果事务协调器宕机,某些参与者将无法解决他们的事务问题
- 同步阻塞
- 基于数据库实现会锁住资源。
- 不基于数据库实现,也可能要持数据库资源。
- 单点故障
- 事务协调者挂了,整个事务就执行不下去了。
- 如 参与者发生完准备命令之后挂了,每个本地资源都会处于锁定状态。
- 事务协调者挂了,整个事务就执行不下去了。
- 数据不一致问题
- 网络抖动,导致某些参与者无法收到协调者的请求,而某些收到了,导致数据不一致。
- 同步阻塞
- 提交协议是阻塞协议,如果事务协调器宕机,某些参与者将无法解决他们的事务问题
- XA 规范
- 重做日志(redo log)
- 每当有操作执行前,在数据真正更改前,会先把相关操作写入 redo 日志
- 回滚日志(undo log)
- 记录事务开始前数据的状态
- 二进制日志(binlog)
- 记录了所有的 DDL 和 DML 语句,除了数据查询语句 select、show 等,还包含语句所执行的消耗时间
- 重做日志(redo log)
2 3PC
- 过程
- 提交请求阶段
- 协调器向所有参与者发送事务请求,询问是否可以CanCommit,然后各个参与者响应Yes或者No
- 预提交
- 协调者从所有参与者反馈都是Yes响应
- 发送预提交请求
- 向参与者发送预提交请求
- 事务预提交
- 参与者接收请求,会执行事务操作。
- 响应反馈
- 参与者成功执行了事务操作,返回Ack响应
- 发送预提交请求
- 协调者从所有参与者反有一个No反应,或者超时
- 发送中断请求
- 协调者向所有参与者发送中断请求
- 中断事务
- 参与者收到中断请求,执行事务中断。
- 发送中断请求
- 协调者从所有参与者反馈都是Yes响应
- 提交( 只要预提交成功, 则一定要保证 真实提交成功,即使协调器下一阶段不可用,一般是通过重试补偿的策略)两种情况
- 执行提交
- 发送提交请求
- 所有参与者返回Ack响应后,进入提交阶段,协调者向所有参与者发送提交请求。
- 超时提交 如果参与者 超时没有收到提交请求,也进行提交。
- 事务提交
- 参与者接受到提交请求后,执行真正事务提交,完成事务释放资源。
- 响应反馈
- 事务提交后,参与者向协调者发送Ack响应。
- 完成事务
- 协调者接受到所有参与者Ack响应完成事务。
- 发送提交请求
- 中断事务
- 协调者 预提交 没有收到参与者Ack 响应(非ack或者超市),执行中断。
- 执行提交
- 提交请求阶段
- 如何解决2pc同步阻塞问题
- 协调者和参与者都引入了超时机制
- 对2pc 改进
- 引入超时机制
- 添加预阶段,保证最后提交阶段参与者节点状态的一致性。
3 TCC
- TCC 就是一种业务层面或者是应用层的两阶段提交
- TCC 分为指代 Try、Confirm、Cancel 分为两个阶段
- 第一阶段 try 完成业务检查(一致性)、预留业务资源(准隔离性)。
- 第二阶段
- Confirm 不做任何业务检查,仅仅使用预留的资源执行业务操作,如果失败会一直重试
- Cancel 取消执行业务操作,释放预留的资源,如果失败会一直重试
- 一个事务的所有服务都需要提供这三个方法 可以根据表字段去设计
//尝试方法
function try(){
//记录日志
todo save A 转出了 100 元
todo save B 转入了 100 元
//执行转账
update amount set balacne = balacne-100 where id = 1
update amount set balacne = balacne+100 where id = 2
}
//确认方法
function confirm(){
//清理日志
clean save A 转出了 100 元
clean save B 转出了 100 元
}
//取消方法
function cancle(){
//加载日志
load log A
load log B
//退钱
update amount set balacne = balacne+100 where id = 1
update amount set balacne = balacne-100 where id = 2
}
- 场景
- 分布式事务要求高,跟钱有关。
- 每天系统执行时间比较短。
- 特点
- 对代码的嵌入性高,要求每个业务需要写三种步骤的操作
- 对有无本地事务控制都可以支持使用面广
- 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高
- 注意点
- 幂等问题,
- 网络超时,会重复调用参与方的 confirm/cancel 方法
- 异常事务的补偿执行 try 的重复执行。
- 空回滚问题
- try方法由于网络问题超时一直没有返回,事务管理器会发出Cancel命令 执行了cancel方法。
- 解决:事务表 try方法成功执行后,会插入一条记录,标记分支事务 init 状态 表示 try执行成功。
- cancel时 查询状态。
- 悬挂问题
- try方法由于网络问题超时,导致cancel先执行,然后再执行try方法。
- 解决:空回滚 插入一条条记录,标记分支事务 rollbacked 状态。
- try 开始执行的时候 首先尝试插入状态为 init 的分支事务
- 如果失败表示当前分支事务的记录已经存在,try无需执行。
- 幂等问题,
4 lcn 选取
- 支持 TCC,TXC,LCN 三种模式
- 协调控制流程
- image.png
- 三个角色, 发起方,参与方,txManager
- 发起方 调用 txManager 创建事务组
- 发起方 假如执行自己业务, 再调用参与方A,参与方A 执行自己事务,加入到事务组
- 参与方A 出现异常, 不需要加入事务组,发起方捕获异常,通知事务回滚。
- 发起方 知道 是提交事务还是回滚事务。 通知 txManager
- txManager 通知每个事务参与者,并且每个参与者都响应给了txManager
- txManager 响应给 发起方。
4.1 TXC 逆向sql
第一阶段 执行sql的时候, 拦截并解析,查询出要受影响的数据。记录下来
-
第二阶段
- 提交 将记录的数据删除
- 回滚 通过 记录的sql和影响的数据 创建逆向sql 并执行。
-
流程image.png
-
特点
- 代码的嵌入性低
- 限于对支持SQL方式的模块支持
- 不会占用数据库的连接资源,但中间状态可见
4.2 Lcn模式
-
流程 image.png
-
特点
- 对代码的嵌入性为低。
- 仅限于本地存在连接对象且可通过连接对象控制事务的模块
- 事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障
- 缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间, 会锁住资源。
-
负载问题
- 如 A 服务分别掉B服务集群 one tow 方法。 当负载到不同的 实例 导致出现两个事务, 下面代码 行锁 锁住资源导致 资源占用 导致窒息失败而回滚事务。
void one(id){
execute => update demo set state = 1 where id = {id} ;
}
void two(id){
execute => update demo set state = 2 where id = {id} ;
}
4.3 lcn 对一些问题的解决。
1 超时机制
- 参与者 执行完业务,启动定时任务,到了时间 txManager 没有通知参与者
- 主动请求 txManager
- 成功 按响应结果 来回滚或者提交。
- 失败 事务补偿。
- 主动请求 txManager
2 补偿出现
自动补偿
手动补偿
-
场景
- 服务挂了
- 执行完事务挂了
- 没有执行事务挂了
- lcn 记录开始执行业务之前 事务信息。
- 服务挂了
记录发起方信息。
5 基于消息补偿的最终一致性
- 基于消息队列的最终一致性就是一种异步事务机制,在业务中广泛应用
- 基于消息补偿的一致性主要有本地消息表和第三方可靠消息队列等
- 本地消息表
- 将分布式事务拆分成本地事务进行处理,通过消息日志的方式来异步执行
- 本地消息表是一种业务耦合的设计
- 消息生产方需要额外建一个事务消息表,并记录消息发送状态
- 消息消费方需要处理这个消息,并完成自己的业务逻辑
- 有一个异步机制来定期扫描未完成的消息,确保最终一致性
- 两个系统通知 可以用zookeeper, 或者接口。
- 本地消息表
-
image.png
- 如 下单服务与库存服务
- 系统收到下单请求,订单业务存储订单库,并同时存储该订单对应的消息数据,放入同一个事务处理
- 库存服务 通过消息中间件收到库存更新消息,调整库存业务,同时返回业务处理结果。
- 订单服务收到处理结果后,将本地消息表的数据删除或者设置为完成。
- 设置异步任务,定时扫描本地消息表,发现有未完成的任务重试,保证最终一致性。
- 如 下单服务与库存服务
- 缺点
- 依赖 数据库消息表。所以并发不能太大。
6 可靠消息最终一致性。
- image.png
7 最大努力通知方案
- 系统 A 本地事务执行完之后,发送个消息到 MQ;
- 这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
- 是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
8 你们公司是如何处理分布式事务的
如何是特别严格,如资金 那我 tcc 来保证强一致性。
可以不采用分布式事务, 人工去排除。