分布式事务
第一、定义
分布式事务,就是在不同的节点保证事务的ACID。
即是原子性,一致性,隔离性,持久化。
第二、CAP分布式理论
C 一致性,A 高可用,P 分区容忍性。
现在大多是实现AP保证最终一致性。
在出现网络分区的时候,C和A只能保证实现一个。如果没有出现网络分区,则AP都需要实现。
第三、 BASE 理论
BA 基本可用,S 软状态,E 最终一致性
核心就是先保证基本可用,通过软状态过度,最后达到最终一致性。
1、一致性的 3 种级别
- 强一致性 :系统写入了什么,读出来的就是什么。
- 弱一致性 :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
- 最终一致性 :弱一致性的升级版。系统会保证在一定时间内达到数据一致的状态
业界比较推崇是 最终一致性,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。
2、柔性事务
互联网应用最关键的就是要保证高可用, 计算式系统几秒钟之内没办法使用都有可能造成数百万的损失。在此场景下,一些大佬们在 CAP 理论和 BASE 理论的基础上,提出了 柔性事务 的概念。 柔性事务追求的是最终一致性。
实际上,柔性事务就是 BASE 理论 +业务实践。
柔性事务追求的目标是:我们根据自身业务特性,通过适当的方式来保证系统数据的最终一致性。 像 TCC、 Saga、MQ 事务 、本地消息表 就属于柔性事务。
3、刚性事务
与柔性事务相对的就是 刚性事务 了。前面我们说了,柔性事务追求的是最终一致性 。那么,与之对应,刚性事务追求的就是 强一致性。像2PC 、3PC 就属于刚性事务。
ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。
第四、幂等性
- 定义是:多次操作之后,结果是一样的。
比如,数据库多次查询对数据没有任何影响,查询接口天然具有幂等性。 - 实现: 可以实现去重表,使用唯一索引实现。可以使用请求参数实现。
- 在需要进行幂等性的接口上面进行特殊处理,前端在向后台传输数据的时候,首先申请通过接口获取一次性令牌,在请求接口的时候,携带令牌,后台接口进行令牌核销,来保证业务不会重复提交。
- 令牌接口可以通过Redis实现(方法名和时间戳加密字符串),也可以通过数据库的去重表实现。
第五、实现
-
两段式提交 - 2PC(协调者)
-
第一阶段,准备阶段
- 事务协调者/管理者(后文简称 TM) 向所有涉及到的 事务参与者(后文简称 RM) 发送消息询问:“你是否可以执行事务操作呢?”,并等待其答复。
- RM 接收到消息之后,开始执行本地数据库事务预操作比如写 redo log/undo log 日志,此时并不
会提交事务 - RM 如果执行本地数据库事务操作成功,那就回复“Yes”表示我已就绪,否则就回复“No”表示我未就
绪。
-
第二阶段,提交阶段,
- TM 向所有参与者发送消息:“你们可以提交事务啦!”(Commit 消息)
- RM 接收到 Commit 消息 后执行 提交本地数据库事务 操作,执行完成之后 释放整个事务期间所占用的资源。
- RM 回复:“事务已经提交” (ACK 消息)。
- TM 收到所有 事务参与者 的 ACK 消息 之后,整个分布式事务过程正式结束。
-
异常情况
- TM 向所有参与者发送消息:“你们可以执行回滚操作了!”(Rollback 消息)。
- RM 接收到 Rollback 消息 后执行 本地数据库事务回滚 执行完成之后 释放整个事务期间所占用的
资源。 - RM 回复:“事务已经回滚” (ACK 消息)。
- TM 收到所有 RM 的 ACK 消息 之后,中断事务。
-
优点
-
实现简单,基本数据库支持2PC
-
缺点:
- 同步阻塞 :事务参与者会在正式提交事务之前会一直占用相关的资源。
- 数据不一致 :由于网络问题或者TM宕机都有可能会造成数据不一致的情况。比如在第2阶段(提交
阶段),部分网络出现问题导致部分参与者收不到 Commit/Rollback 消息的话,就会导致数据不一致。 - 单点问题 : TM在其中也是一个很重要的角色,如果TM在准备(Prepare)阶段完成之后挂掉的话,事务参与者就会一直卡在提交(Commit)阶段。
-
-
3PC(三阶段提交协议)
-
准备阶段
- 这一步不会执行事务操作,只是向 RM 发送 准备请求 ,顺便询问一些信息比如事务参与者能否执行本地数据库事务操作。RM 回复“Yes”、“No”或者直接超时。
- 如果任一 RM 回复“No”或者直接超时的话,就中断事务(向所有参与者发送“Abort”消息)
-
预提交阶段
- 协调者向所有参与者发送PreCommit消息。
- 参与者收到PreCommit后,执行事务操作但不提交,并回复Ack。
- 如果任一参与者回复No,协调者发送Abort消息,事务中止。
-
提交阶段
- 协调者向所有参与者发送DoCommit消息。
- 参与者收到DoCommit后,提交事务并回复Commit-Ack。
- 如果任一参与者未回复或回复No,协调者发送Abort消息,事务中止。
-
优点
- 3PC 还同时在事务管理者和事务参与者中引入了 超时机制 ,如果在一定时间内没有收到事务参与者的消息就默认失败,进而避免事务参与者一直阻塞占用资源。
-
缺点
- 3PC 并没有完美解决 2PC 的阻塞问题,引入了一些新问题比如性能糟糕
- 依然存在数据不一致性问题。
- 多数应用会选择通过复制状态机解决 2PC 的阻塞问题
-
-
事务补偿(TCC):属于目前比较火的一种柔性事务解决方案
- Try,Confirm,Cancel
- Try(尝试)阶段 : 尝试执行。完成业务检查,并预留好必需的业务资源。
- Confirm(确认)阶段 :确认执行。当所有事务参与者的 Try 阶段执行成功就会执行 Confirm ,Confirm 阶段会处理 Try 阶段预留的业务资源。否则,就会执行 Cancel 。
- Cancel(取消)阶段 :取消执行,释放 Try 阶段预留的业务资源。
- 拿转账场景来说:
- Try(尝试)阶段 : 在转账场景下,Try 要做的事情是就是检查账户余额是否充足,预留的资源就是转账资金。
- Confirm(确认)阶段 : 如果 Try 阶段执行成功的话,Confirm 阶段就会执行真正的扣钱操作。
- Cancel(取消)阶段 :释放 Try 阶段预留的转账资金。
- 正常情况下,只会执行 try , confirm 方法
- 执行 try 出现异常的话,执行 cancel 方法。
- 重试机制
- TCC 会记录事务日志并持久化事务日志到某种存储介质上比如本地文件、关系型数据库
- 事务日志包含了事务的执行状态,通过事务执行状态可以判断出事务是提交成功了还是提交失败了,以及具体失败在哪一步。
- 如果发现是 Confirm 或者 Cancel 阶段失败的话,会进行重试,继续尝试执行Confirm 或者 Cancel 阶段的逻辑。重试的次数通常为 6 次,如果超过重试的次数还未成功执行的话,就需要人工介入处理了
- 总结
- TCC 模式不需要依赖于底层数据资源的事务支持,但是需要我们手动实现更多的代码,属于 侵入业务代码 的一种分布式解决方案
- 针对 TCC 的实现,业界也有一些不错的开源框架。 ByteTCC、Seata 是一款开源的分布式事务解决方案、Hmily : 金融级分布式事务解决方案。
- Try,Confirm,Cancel
-
消息队列实现最终一致性
- QMQ实现MQ事务
- 本地创建一个消息表。
- 在一个本地事务中,执行业务和写入本地消息表,这时候处于同一个本地事务,业务和消息日志是要么都成功要么都失败。
- 然后将消息放入消息队列中,放入消息队列中,更新消息表中消息状态。
- 如果放入失败,子线程会循环消息表,继续尝试放入消息队列。
- 其他节点收到消息,开始执行对应业务(接口要实现幂等性),执行成功,修改消息表中的消息状态。如果失败,消息还会被放入消息队列中,继续执行业务,实现最终一致性。
- RocketMq实现MQ事务
- MQ 发送方(比如物流服务)在消息队列上开启一个事务,然后发送一个“半消息”给 MQ Server/Broker。事务提交之前,半消息对于 MQ 订阅方/消费者(比如第三方通知服务)不可见
- “半消息”发送成功的话,MQ 发送方就开始执行本地事务。
- MQ 发送方的本地事务执行成功的话,“半消息”变成正常消息,可以正常被消费。MQ 发送方的本地事务执行失败的话,会直接回滚。
- 如果 MQ 发送方提交或者回滚事务消息时失败怎么办?RocketMQ 中的 Broker 会定期去 MQ 发送方上反查这个事务的本地事务的执行情况,并根据反查结果决定提交或者回滚这个事务。
- RocketMQ 的事务消息方案中,如果消息队列挂掉,数据库事务就无法执行了,整个应用也就挂掉了。
- QMQ 的事务消息方案中,即使消息队列挂了也不会影响数据库事务的执行。因此,QMQ 实现的方案能更加适应于大多数业务
- QMQ实现MQ事务