[关闭]
@linwbai 2018-08-01T14:28:09.000000Z 字数 3246 阅读 991

这个需求我不接之事务自动补偿

yonyou


分布式事务介绍

什么是分布式事务

分布式事务就是指事务的资源分别位于不同的分布式系统的不同节点之上的事务;

例如:信用卡还款,包含两个操作

事务属性

原子性(Atomicity)

    事务中的所有操作,要么都成功,要么都失败,不可能部分操作成功部分操作失败。事务的操作或者完全应用到数据库或者完全不影响数据库。
    拿信用卡还款来说,原子性就是银行账户扣钱和信用卡额度恢复要么都成功要么都失败。

一致性(Consistency)

    一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿信用卡还款来说,一致性就是还款成功前和还款后银行卡余额加上信用额度是相等的。

隔离性(Isolation)

    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍。

持久性(Durability)

    在事务成功以后,该事务对数据库所做的更改应当持久的保存在数据库中;

总结来说, 一致性是最基本的属性,而原子性、隔离性、持久性是为了保证一致性而存在的

事务的隔离级别

事务的隔离级别有什么用呢?我们先看一下不考虑事务的隔离性会出现那些问题。

脏读

时间 事务A 事务B
1 开始事务
2 开始事务
3 查询额度为100元
4 消费10元
5 查询额度为90元
6 消费异常回滚额度为100元

时间轴为5的地方事务B就出现了脏读

不可重复读

时间 事务A 事务B
1 开始事务
2 开始事务
3 查询额度为100元
4 查询额度为100元
5 消费10元
6 提交事务
7 查询额度为90元

事务B中两次查询结果并不一样。不可重复读和脏读的区别是不可重复读是读取了另一个事务提交的数据。

幻读

时间 事务A 事务B
1 开始事务
2 开始事务
3 把状态为1数据的更新为状态为2
4 插入一条状态为1的数据
5 提交事务
6 查询状态为1的数据发现还有一条

MySQL数据库为我们提供的四种隔离级别:

事务隔离级别 脏读 不可重复读 幻读
Read uncommitted 允许 允许 允许
Read committed 不允许 允许 允许
Repeatable read 不允许 不允许 允许
Serializable 不允许 不允许 不允许

分布式事务解决方案

TCC(Try-Confirm-Cancel)

    TCC是由支付宝架构师提供的一种柔性解决分布式事务解决方案,主要包括三个步骤
  • Try: 完成业务检查,预留必须的业务资源
  • Confirm: 真正的执行业务逻辑,不做任何业务检查,只使用Try阶段预留的业务资源。所以,只要Try执行成功,Confirm必须能成功。Confirm必须满足幂等性,保证一次分布式事务只成功一次。
  • Cancel: 释放Try阶段预留的业务资源。Cancel也需要满足幂等性
    TCC自编码的特性决定TCC资源管理器可以跨DB、跨应用实现资源管理,将对不同的DB访问、不同的业务操作通过编码方式转 换一个原子操作,解决了复杂业务场景下的事务问题;
    在整个流程,我们主要需要关注的是cancel失败和confirm失败引起的数据不一致现象。

服务需要提供Try、Confirm、Cancel三个接口。

事务自动补偿

框架托管FMT(Framework-managed transactions)模型

    FMT是阿里GTS(全局事务服务)中的一个可以实现自动补偿事务的模型。

FMT 框架要求从业务服务只需要正常执行SQL操作就可以了,框架会把业务的本地事务操作作为第一阶段。在第一阶段,框架会拦截用户SQL,解析SQL语义,然后把业务SQL涉及数据执行前后的状态保存下来,即数据快照,这个就相当于在逻辑上完成了数据库内的undo和redo操作。在第二阶段,如果这个事务要提交,那么框架直接把快照数据删除就可以了,因为第一阶段的正常操作已经执行完成。如果该事务要回滚,那么会先校验脏写,根据第一阶段保存的执行后的快照,检查在本事务执行过程中,数据有没有被其他操作修改,如果没有,则把数据执行前的快照拿出来,完成回滚操作。

A主业务B从业务C从业务业务活动管理器启动业务活动rpc登记业务活动保存原快照执行sql保存新快照successrpc登记业务活动保存原快照执行sql保存新快照success提交/回滚提交/回滚删除快照/恢复快照、删除快照提交/回滚删除快照/校验快照、回滚快照、删除快照A主业务B从业务C从业务业务活动管理器A调用B,A调用C

GTX的限制

使用GTX也有很大的限制的,比如下图
image_1cjkve6jd1iepta3cm0rm31ttvg.png-29.8kB

  1. 把一个sql解析为抽象语法树时,sql越复杂解析越耗时。所以复杂的sql使用FMT是非常不值得的。
  2. GTS的隔离级别默认为读未提交。当更新数据时很容易出现脏写。这时候如果出现错误,Cancel校验脏写就会出现错误导致无法恢复快照。
  3. GTS提供的hint模式是针对阿里自研的DRDS。当你使用非DRDS数据库时列如mysql,使用了for update,这样将会大大降低性能
  4. 不能使用聚合函数。读未提交时聚合函数不一定准确(脏读)。读已提交时不支持聚合函数。
  5. 当插入/更新数据数量太多时保存快照的消耗太大。而且快照是否持久化也是一个问题
  6. 没有解决幻读问题

总结

    综上所述,我认为绝大部分场景下事务自动补偿得不偿失,想要性能好必须要做好脏读、幻读的准备,想要读已提交必须要舍弃性能。使用多写几个方法的TCC就可以避免上述的大多数问题,何乐而不为呢!

    我的思考: 我们是不是可以在程序端保证原子性和隔离性。
时间 事务A 事务B 事务C
1 开始事务
2 开始事务
3 开始事务
4 查询余额为100元 查询余额100元(db)
5 保存原快照
6 还信用卡10元
7 保存新快照 查询余额100元(快照)
8 提交A事务
9 查询额度为100元 查询余额100元(快照)
10 保存原快照 查询额度100元(db)
11 被还款10元
12 保存新快照 查询额度100元(快照)
13 提交B事务
14 提交总事务(删除快照) 提交总事务(删除快照)
15 查询额度110元(db)
16 查询余额90元(db)

这样就保证了真正意义上的读已提交

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注