分布式事务—Seata
# 分布式事务—Seata
分布式事务:分布式系统下,一个业务跨越了多个微服务进行数据库操作,此时每个微服务都是一个分支事务,需要保证所有微服务上的事务最终状态都要保持一致。
最终一致性:各分支事务分别执行提交,如果有不一致的情况再恢复数据
强一致思想:各分支事务执行完业务不提交,等待彼此结果,然后再统一提交或者回滚。
# 1.CAP定理
consistency一致性,availability可用性,partition tolerance分区容错性(因为网络原因某个节点和其它节点失去连接,形成独立分区,数据同步失败)。在网络中分区容错无法避免,要么CP(拒绝所有对该节点访问的请求,这样用户就只会访问那些同步的分区。缺点在于没有满足可用性,因为断连节点依旧是健康节点),要么AP(正常访问所有节点,但是因为分区无法实现数据同步,因此不满足一致性)。
ES集群:三个节点中各保存一片数据+其它片的备份数据,当某个节点挂掉时,ES集群会把那个节点上的所有数据迁移到剩余两个节点上,然后剔除掉挂掉的节点。因此是CP。
# 2.Seata
TC:协调分支事务和全局事务(需要一个数据库/redis用于保存TM和RM的注册信息)
TM:提交/回滚分支事务
RM:管理分支事务的资源
配置seata的tc服务:先配置事务组,相当于把所有分支事务都归到一个组管理,然后在配置事务组和cluster集群的映射关系。
# 3.四种模式
XA,AT属于分段提交事务模型
# XA
数据强一致性,但是执行完sql后不提交占用数据库锁,性能差可用性降低,耗时久。

给发起全局事务的入口方法(该方法中会调用执行分支事务)添加@GlobalTransactional注解
# AT
执行完sql后立马提交。由于一阶段提交过了,因此2.3阶段提交无效直接删除log。相比于XA模式优点在于异步,AT是最终一致,中间状态可能不一致。

问题:AT模式存在脏写问题。过程:事务1获取DB锁,保存快照,让money减10,提交事务释放锁——>事务2获取DB锁,让money减10,提交事务释放锁——>事务1获取DB锁,根据快照回滚恢复数据。此时会把事务2的操作也会回滚覆盖掉,丢失更新。
解决
- 引入全局锁,事务1释放DB锁之前获取全局锁(只能有一个事务获取全局锁;且仅有获取全局锁的事务才能提交事务,回滚)
- seata保存两次快照after-image更新后快照,before-image更新前快照。把当前数据库数据和更新后快照进行对比,如果一样说明更新完后没有被修改(乐观锁的思想);如果不一样说明被人工篡改。
配置:需要把undo_log快照表导入到所有微服务关联的数据库中(回滚由RM进行),用于保存记录数据快照。然后将lock_table(全局锁由TC维护与创建)导入到TC服务相关联的数据库中。
# TCC
Try:资源预留,资源锁定。冻结资源+可用资源=总资源。
Confirm:业务执行,提交,并清空冻结资源。
Cancel:回滚
优点:一阶段执行完后直接提交事务,释放数据库资源;且无需生成快照,无需使用全局锁。属于最终一致性。
缺点:需要编写TCC接口。
- 空回滚
当某个事务try阻塞时,可能导致全局事务超时,触发二阶段的cancel回滚操作,这时候还没执行try操作的微服务节点此不能做回滚,因为并没有操作数据库。
- 业务悬挂
阻塞的事务畅通了之后,再跑回去重新执行Try锁定资源,而此时整个全局事务已经结束,没有后续的CC阶段。
- 编程注意
应该避免事务空回滚后的try操作。在数据库应该用一个表保存事务id和状态。同时为了保证幂等性,仅仅第一次方法执行生效,后面的调用次数直接全部return。
为了保证不出现空回滚,在cancel中先根据事务id查找记录,如果为空说明该事务没有进行try操作,不能继续进行空回滚,并插入一条新的回滚事务记录。
为了避免业务悬挂,在try中要先基于事务xid查找数据库看事务表中是否有数据,如果有说明该事务已经执行过了,不能继续try,当且仅当查不到全局事务时才进行try。
@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money")int money);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
2
3
4
5
6
7
8
9
10
# Saga
一阶段执行完直接提交本地事务,二阶段如果失败则通过编写补偿业务来回滚。
缺点:事务之间没有隔离性,容易出现脏写。