(1)TCC模式
前面学了XA和AT模式,这两种模式最终都能实现一致性,和隔离性,XA是强一致,AT是最终一致,隔离性呢XA是在第一阶段不提交,基于事务本身的特性来完成隔离,AT则是加了全局锁,锁定资源去隔离事务,本质上来看这两种都是加锁来实现的,只要加了锁都会有性能的损耗,如果追求的是极致的性能,就需要使用其他的办法,TCC 模式就是性能的体现,他不需要去加锁,
TCC是基于人工编码来实现的,不想AT模式自动实现的,在第一阶段形成快照,二阶段才能恢复,它多了一个生成快照的逻辑,性能有损耗,这一点TCC胜过了AT模式
成功,扣减冻结金额 -资源预留
回滚
做反向操作,冻结金额减三十,余额加三十
可以看到TCC模式,在做了在第一阶段完成了资源预留之后,第二阶段不管是Confirm还是Cancel都是在操作自己预留的这份资源,就导致了TCC跟AT模式有很大的区别
在第一阶段,两种模式都是个提交各自的事务,很快释放数据库锁,在性能上都非常的好,在第一阶段都会有有可能成功,有可能失败,造成数据不一致,只有在第二阶段完成了Confirm和Cancel之后才有可能才能保证数据的最终一致,它是最终一致,会有中间状态
AT模式是需要加锁去实现隔离的,需要在第一阶段跟第二阶段持有全局锁在一二阶段之间其他事务是不能操作这个资源的,从而确保安全,在TCC模式下是不需要隔离的,因为在第一阶段每个事务冻结的金额是不一样的,每个事务回滚还是提交操作的都是本事务自己冻结余额,不会影响,不需要加锁第二阶段各自操作各自预留的资源,互不影响,TCC模式不需要加锁,就实现了隔离,比AT模式好了很多很多
(2)TCC案例
并不是所有的事务都适合TCC模式来实现,想下单的逻辑,是新增的逻辑,怎么去资源预留,它不适合TCC模式,用AT模式就很好,第一阶段新增了,第二阶段回滚,另外一个事务,它也要做新增它跟前一个事务没有关系,扣减库存的服务可以用ACC模式来实现,我们这里演示金额扣减服务
幂等性我们有一个业务接口,你调用我一次也好多次也好,最终达成的效果是一致的,不会因为重复调用出现问题,这叫做幂等性
怎么避免业务悬挂呢,在执行try的时候判断是否回滚过,回滚过try就不能够执行
判断执行空回滚呢?在执行cancel的时候判断try是否执行了,try没有执行,做空回滚
需要在数据库里记录当前事务的状态当前的事务状态在try状态啊还是confirm状态啊,cancel状态啊,只有知道事务的状态才能做空回滚和避免业务悬挂
新建表:冻结金额表
BussinessActionContextParameter注解:这个注解标记的参数将来放到一个上下文对象里BussinessActionContext,通过这个对象都可以拿到这个参数
创建新的接口:AccountTCCService接口:
创建余额表:用它表示资源的预留,锁定,事务状态
实体类:
Mapper
创建实现类:
用户余额表:中的money字段添加了using 表示没有负号,这个余额不会扣成负数,当扣成负数时会直接报错,事务自动回滚,因此余额不用做判断了
所以在方法try中直接扣除余额,不会做余额判断了
修改Try在其中做悬挂处理
Confirm逻辑:
回滚:userID Money可以从加入到全局的参数中获取,也可以查询数据库从数据中获取:
修改回滚,在其中做回滚判断,和幂等处理:
这里的userID从全局参数中获取,然后再toString一下
修改Controller:
修改成AccountTCCService
重启服务:
使用Postman发送请求:发送正确的参数
库存表从6到4
订单多了一条:
余额减200:
余额冻结表:没有数据,因为操作是正确的
异常数据:
库存没变:
订单没有增加:
余额没变:
余额冻结表多了一条记录,state为2回滚状态
(3)Saga模式
Saga最大的缺点没有隔离性,事务与事务之间有可能出现脏写的一二阶段之间,既没有全局锁,也没有冻结资源,它是由隔离的安全问题的
它会逐个的去执行事务,在这个流程中只有有事务出现问题它会反向依次执行补偿逻辑,从而保证整个事务的状态一致性,典型的分阶段提交
事务执行是可以基于事件驱动的,一个事件完成执行下一个事件,事件驱动好处是吞吐能力比较强,他不会阻塞和等待事件到了就去干某一键事情 ,但是事件什么时候去执行,时间时不确定的,时效性没有那么强
这种长事务的解决方案,时效性比较差一些,比较适合用于事务跨度比较大的业务,比如跨银行的业务调用啊转账等等,业务比较复杂的场景,一般情况下用不到
AT用的最多,TCC和XA选做一些补充就够了