一。首先复习下mysql事务
事务四特性:
原子性(A):要么全运行,要么全不运行
一致性(C):事务之前A有100块,B有100块,A给B转账,事务结束后A和B的余额总和必须也得是200块。这里除了代码逻辑上没有问题之外,其实还需要其他三个特性的支持。
隔离性(I):事务之间互不影响。
持久性(D):事务一旦结束,数据永久性保存。
C需要AID一起来保证,那么AID分别是怎么保证的呢,众所周知隔离性由隔离级别保证,而持久性保证的方式是将数据写入磁盘保证断电不丢,重点是原子性原子性是通过redo log 和undo log来保证的,如下:
过去只有undo log ,里面保存数据改动之前的信息,如果回滚了根据这个来恢复,但是这种方式每次修改数据都要写磁盘性能较差,所以现在是undo log和redo log一起用,减少磁盘写次数,如下图:

数据的修改,都只保存到了内存中,最终在事务提交,给客户端返回commit成功前,把undo log 和redo log保存到磁盘里,实际上是把undo log 写到了redo log里然后采用追加的方式,而不是修改的方式,顺序写到磁盘里的,这样会大大提升写入速度。
问题一:数据还没持久化到磁盘里,那下一次查询来了查到的不是旧值吗?
不是,因为下一次查询来了先在内存里查,查到的是修改后的新值。
问题二:数据没持久化,断电了不就没了吗?怎么保证的持久性?
数据没持久化但是redo log里的新值持久化了,断电重启从redo log 里把新值拿出来更新磁盘就行了。
问题三:假如每提交,在某个过程rollback了怎么办?
简单,旧值都在undo log里呢 根据undo log把内存改回去就完了,磁盘里没改,不用恢复。
二。分布式事务:
多个服务,每个服务有自己的数据库,每个服务对自己的数据库是满足ACID的,但是多个服务串联起来我希望整个一次链路要么全运行成功,要么全运行失败,本地事务就解决不了了。比如生成订单-->支付>减少库存,三个步骤只要有一个失败前面的全要回滚,仅靠每个服务自己的本地事务肯定是不够的,所以需要分布式事务。
只要是分布式系统,就一定有三个指标:CAP,并且这三个是无法全部满足的,而既然分布式,那么P是必须有的,所以只能满足AP或者CP。
一致性,就是我写进去的东西,再读出来必须跟我写进去的时候一致。
可用性,就是我发的请求,服务端必须要给我响应,对错不管,但是必须得给我返回相应。
对于一个分布式系统,假如AB两台机器,通过通讯进行数据同步,本来两台里面寸的都是x=1,客户端给A发请求把x改成了2,但是AB之间通信出了问题了,或者延迟比较高还没来得及同步,客户端把请求发给B去取x的值,在这种情况下,你要么返回错的值x=1,这就是满足了可用性(A),但是不满足一致性(C),要么直接就给客户端返回异常,来保证自己没有返回错的值,那么就只满足了一致性(C),而没有满足(A),所以A和C无法同时满足,他们俩是矛盾的。

妥协之下,出现了BASE理论,也就是说,AB两台机器同步的时间尽量缩短,最终能保证两台机器里数据一样(最终一致性),但是在同步的这段时间内服务是不可用的(基本可用)

怎么实现这个呢,就是通过两阶段提交:

事务控制器来通知每个服务让他们分别运行,但是运行成功之后,先别提交,三个服务统一把运行结果提交给控制器,控制器发现他们都没问题了,就接着告诉他们可以提交了,然后他们三个再各自提交。

如果其中一个异常,那控制器就通知其他人别提交,让他们都回滚就行了。
这种二阶段提交可以满足强一致并且效率也比较高,但是如果某个阶段某个服务阻塞了就得大家一起等,可能会卡死。
为了解决上面的问题,出现了TCC(Try--Confirm/Cancel)
字面理解,先尝试,然后再根据是否成功决定confirm还是cancel,跟前面的二阶段提交好像没区别,但实际上:Try的操作完了之后是直接提交的,不会等待其他服务,confirm和cancel也是,那么这样不就没法回滚了吗?也不是,尝试、提交、回滚这三个操作跟本地事务是不一样的,try是预留资源,confirm是真正的修改资源,cancel是把预留的资源释放掉。也就是下图红绿蓝三个每个都是一个单独的事务。

举个例子:


这样做的优势是不要等别人了,但是缺点是比较麻烦,代码侵入比较高,本来扣款就一个动作,现在try得写一个逻辑,confirm得写一个,cancel得写一个。而且这种模式不是强一致。
能不能结合上面两种的优点,又没有他们的缺点呢?有,那就是seata的AT模式:

一阶段也是直接提交,然后二阶段托管框架会检查每个服务的事务成功了没有,如果都成功了,就什么都不用管了。如果有失败的,那就需要反向操作会滚回去,但是反向操作的代码是不需要人来写的,是框架自动完成的。那么框架到底干了一个什么事儿呢?就是监控了每个服务的sql,把执行前和执行后应该有的结果记在自己的log里,类似于undo log 和redo log,然后第二阶段拿redo log去和数据库执行后的结果对比,如果不一致就用undo log回滚。在回滚前,也会检查对比后得到的结果跟当前结果是否相同,如果不相同说明可能有人在后面又操作了这条数据,那么就需要人工介入了。
这样的话就兼具了不阻塞和不需要程序员写大量代码的优点,缺陷就是过程要消耗很多性能。

seata中有这么三个重要的角色:


