分布式事物

1. MySQL XA方案

MySQL从5.7开始加入了分布式事务的支持。MySQL XA中拥有两种角色:

  • RM(Resource Manager):用于直接执行本地事务的提交和回滚。在分布式集群中,一台MySQL服务器就是一个RM。
  • TM(Transaction Manager):TM是分布式事务的核心管理者。事务管理器与每个RM进行通信,协调并完成分布式事务的处理。发起一个分布式事务的MySQL客户端就是一个TM。

XA的两阶段提交分为Prepare阶段和Commit阶段,过程如下:

  1. 阶段一为准备(prepare)阶段。即所有的RM锁住需要的资源,在本地执行这个事务(执行sql,写redo/undo log等),但不提交,然后向Transaction Manager报告已准备就绪。
  2. 阶段二为提交阶段(commit)。当Transaction Manager确认所有参与者都ready后,向所有参与者发送commit命令。

如下图所示:

MySQL XA拥有严重的性能问题。一个数据库的事务和多个数据库间的XA事务性能对比可发现,性能差10倍左右。另外,XA过程中会长时间的占用资源(加锁)直到两阶段提交完成才释放资源。

2. Seata

Seata的分布式事务解决方案是业务层面的解决方案,只依赖于单台数据库的事务能力,特点是:效率高;原因是:生成反SQL(事务回滚时执行)、以空间换时间的思路。

AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本。

TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。(对业务代码有一定的侵入性, TCC 模式无 AT 模式的全局行锁)

Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。

XA模式是分布式强一致性的解决方案,但性能低而使用较少。

seata 的 AT 模式,采用的是大量运用在数据库软件的 Write Ahead Log 思想,即把事务的信息以事务日志的方式记录下来。
这种处理方式,实际上是对传统两阶段提交的一种改进和优化。
1、主要有几个关键点:
传统两阶段提交协议是阻塞协议,性能差; 传统两阶段提交协议高可用性不好 ; 传统两阶段提交协议的全局事务隔离机制不支持 ; 根据八二原则,80% 的涉及到全局事务的业务是能正常完成并提交的。 因此,在 AT 模式下,seata 采取的做法是,一个事务分支的数据库操作执行完后,马上进行本地事务的提交,从而释放相关的数据库 资源。

分支事务中数据的 本地锁 由本地事务管理,在分支事务 Phase1 结束时释放。 同时,随着本地事务结束,连接 也得以释放。
分支事务中数据的 全局锁 在事务协调器侧管理,在决议 Phase2 全局提交时,全局锁马上可以释放。只有在决议全 局回滚的情况下,全局锁 才被持有至分支的 Phase2 结束。
2、 本地事务执行流程
在进行本地提交的前提是,seata 会解析 SQL,获取数据库表的元数据,根绝 SQL 类型,选择性地生成数据的前置镜像和后置镜像, 保存在 undo_log 表中,并且要求与保存 undo_log 与业务 SQL 在同一个本地事务内。
这就保证了:
如果一个本地事务被提交,那么必定对应着相应的 undo_log
如果保存 undo_log 保存失败,那么业务 SQL 也会失败
 
3、 全局事务提交流程
因为每个分支事务的本地事务都已经被提交,所以如果全局事务能够顺利进行到“提交“这一阶段,那么意味着所有事务分支的本地事 务都已经被提交了,数据的一致性已经得到了保证。
这个时候全局事务的提交就变得十分轻量级,就是把 undo_log 对应的记录删掉即可,即使是当时删除失败了,也已经不会影响全局事 务的最终结果,这次删不了,那就待会再删,程序删不了,没事,顶多人工删
4、 全局事务回滚流程
如果全局事务的任何一个事务分支失败了,那么全局事务就进入“回滚“流程,回滚时依据先前保存好数据镜像,将原来的数据回放回 去。如果全局回放成功,那么数据的一致性也就得到了保证,如果回放不成功,那么事务就进入异常。应对异常,可能需要重试,可能需要 人工介入

SeataServer部署:http://seata.io/zh-cn/docs/ops/deploy-by-docker.html,通常结合注册中心使用,避免单点故障。对应脚本参考---https://github.com/seata/seata/tree/develop/script/server/db
SeataClient集成
1.pom添加引用
<!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <version>2.1.0.RELEASE</version>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>${seata.version}</version>
        </dependency>
View Code

2.分别在各业务数据库中创建undo_log表,此表为seata框架使用,sql地址:https://github.com/seata/seata/tree/develop/script/client/at/db

3.配置registry.conf,file.conf。主要是配置中心,redis,mysql连接

4.Service实现,@GlobalTransactional注解用以开启全局事务,@Transactional注解用于分支事务

@Service
@Slf4j
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountInfoMapper accountInfoMapper;

    @Autowired
    private Bank2Client bank2Client;

    @Transactional
    @GlobalTransactional//开启全局事务
    public void updateAccountBalance(String accountNo, Long amount) {
        log.info("bank1 service begin,XID:{}", RootContext.getXID());
        //扣减张三的金额
        accountInfoMapper.updateAccountBalance(accountNo,amount *-1);
        //调用李四微服务,转账
        String transfer = bank2Client.transfer(amount);
        if("fallback".equals(transfer)){
            //调用李四微服务异常
            throw new RuntimeException("调用李四微服务异常");
        }
        if(amount == 2){
            //人为制造异常
            throw new RuntimeException("bank1 make exception..");
        }
    }
}
View Code

 

posted @ 2020-12-22 18:27  刘志斌  阅读(94)  评论(0编辑  收藏  举报