fescar Q&A

一、前言

前面对fescar的源码做了一个分析,核心的流程基本已经有一个比较清晰的认知。这篇文章主要是对fescar的一些设计原理和目前看到一些问题做一个解答。鉴于本人没有跟开发者沟通过,有部分结论可能不一定准确。

二、Q&A

Q1.fescar解决了什么问题?

引用fescar的wiki上的说明:

Fescar 是 阿里巴巴 开源的 分布式事务中间件,以 高效 并且对业务 0 侵入 的方式,解决 微服务 场景下面临的分布式事务问题。

  1. 解决分布式事务问题
  2. 对业务0入侵
Q2.fescar如何实现?核心组件有哪些?

业界上分布式一致的解决方案主要有两种。

  1. 两段提交(2PC)
  2. paxos协议(包括raft协议等变体)
    fescar 使用的是2PC来实现。


    image
  • Transaction Coordinator(TC): 全局和分支事务的状态的保持,驱动这个全局的提交和回滚.
  • Transaction Manager(TM): 明确全局事务的范围:开始一个全局事务,提交或者回滚一个全局事务.
  • Resource Manager(RM): 管理分支事务工作资源,告诉TC,注册这个分支事务和上报分支事务的状态,驱动分支事务的的提交和回滚。
image
Q3.如何实现对业务0入侵

两段提交最核心的问题是如何实业务的自动回滚,feacar的解决方案是通过在RM上扩展原有的mysql连接代理,实现监听业务的sql。从而生成对应的回滚sql。
注意:由于需要在db更新前后增加一个select for update来获取变更前后的行值,因此业务要自行考虑这种方式对自己的业务是否允许。

Q4.xid如何生成?xid又是如何传输的?

TM向TC声明的一个事务开始的时候就会生成xid,所以xid是由TC生成的,TC如何保证xid的唯一?详细参考:
xid分析
事实上fescar的官方的xid方案是不能支持HA的,需要自己做扩展。

上面已经介绍了TM和TC的xid的生成过程。那么xid是如何在TM和RM,以及RM和RM之间传递的呢?
其实很简单,在dubbo基础上增加一个TransactionPropagationFilter来实现。

public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        String xid = RootContext.getXID();
        String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");
        }
        boolean bind = false;
        if (xid != null) {
            RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
        } else {
            if (rpcXid != null) {
                RootContext.bind(rpcXid);
                bind = true;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("bind[" + rpcXid + "] to RootContext");
                }
            }
        }

RootContext获取xid成功,则会在请求协议的时候带上TX_XID这个属性。否则会读取请求体中的TX_XID写入RootContext。最终会让xid这个属性在请求的过程中一层层往下传递。

Q5.TC的global session和branch session如何存储?

源码详细分析:https://www.jianshu.com/p/051ff5640e1b
TC负责对整个事务的生命周期(状态)进行维护,因此global session和branch session是保存在TC上的。而fecar的官方例子session是保存在内存的,线上环境使用的时候需要自己扩展。

Q6.回滚操作如何保证数据的一致性

第一阶段提交到第二阶段提交中间数据被修改了怎么办?(ABA问题?)

这里的答案是用锁来解决。
fescar官方文档提到fescar在第一阶段提交的时候已经把锁释放掉了。其实feacar会在分支事务开始的时候会加锁,这个锁的状态是保存在TC。事实上官方说的释放锁只是把DB行锁释放,分支事务锁还是会持续保存。

public interface LockManager {

    boolean acquireLock(BranchSession branchSession) throws TransactionException;

    boolean isLockable(long transactionId, String resourceId, String lockKey) throws TransactionException;
}

这里存在3个问题。

  1. 这里缺少释放锁的操作,难道是做定时自动释放?难道不应该在第二段提交的时候把锁释放
  2. 官方提供的实现同样是基于内存的方案。需要做HA的自行进行改造
  3. 如果有一个分支事务的资源(db)有部分功能不接入fescar,会不会有问题?

对于问题3,我理解是fescar的使用场景的讨论,如果某个业务有一部分接入fescar,一部分不接入,则锁无法限制在第一次提交和回滚之间数据不会被修改的场景。因此我个人认为需要使用fescar必须将某个业务下的所有操作都要求统一接入到fescar,不管其中某个操作是否涉及到分布式事务。

Q8.TC的日志存储和查询

fescar使用自己定义的二进制格式进行日志保存,TransactionWriteStore,负责对事务session写入记录日志。目前只有保存到本地磁盘,线上使用需要一个统一的地方进行保存。而且查询日志的功能也需要自行开发。

Q9.TM如何决定何时需要回滚?

public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {

        // 1. get or create a transaction
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();

        // 2. begin transaction
        try {
            tx.begin(business.timeout(), business.name());

        } catch (TransactionException txe) {
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.BeginFailure);

        }

        Object rs = null;
        try {

            // Do Your Business
            rs = business.execute();

        } catch (Throwable ex) {

            // 3. any business exception, rollback.
            try {
                tx.rollback();

                // 3.1 Successfully rolled back
                throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);

            } catch (TransactionException txe) {
                // 3.2 Failed to rollback
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.RollbackFailure, ex);

            }

        }

        // 4. everything is fine, commit.
        try {
            tx.commit();

        } catch (TransactionException txe) {
            // 4.1 Failed to commit
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.CommitFailure);

        }
        return rs;
    }

当TM执行业务逻辑抛出异常就会触发回滚操作。这意味着如果TM把分支事务的异常catch了,或者分支事务返回一个失败的返回码,但是TM没有对其进行正常的处理,就不能正常地触发回滚行为。
详见:https://www.jianshu.com/p/6547aa46452b

Q10.支持的数据库

目前仅支持mysql,当然你也可以加入到其中对其他数据库进行支持。

你可能感兴趣的:(fescar Q&A)